diff --git a/.gitignore b/.gitignore index c8d2ae5..5687d82 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,9 @@ devflow-dev.db-wal apps/df-miniapp/src/manifest.json # 微信小程序 appid(真实 appid 本地保留,不提交仓库) temp/ + +# 本地工具链缓存(protoc 等,不入库) +.tools/ + +# df-im-gateway 运行时数据(冒烟测试残留) +crates/df-im-gateway/data/ diff --git a/Cargo.lock b/Cargo.lock index 366017a..1bf6cb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures 0.3.1", +] + [[package]] name = "ahash" version = "0.8.12" @@ -29,6 +40,40 @@ dependencies = [ "memchr", ] +[[package]] +name = "aioduct" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf59c8d5bf56c75528fd1384b528e8ff576aedf4f8483d1e78235a5e15c380d" +dependencies = [ + "base64 0.22.1", + "bytes", + "coarsetime", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "httparse", + "hyper", + "memchr", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "socket2", + "thiserror 2.0.18", + "tokio", + "url", + "webpki-roots", + "x509-parser", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -59,6 +104,45 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -275,7 +359,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sha1", + "sha1 0.10.6", "sync_wrapper", "tokio", "tokio-tungstenite 0.24.0", @@ -357,6 +441,24 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -503,6 +605,15 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", ] +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.63" @@ -566,6 +677,27 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout", +] + +[[package]] +name = "coarsetime" +version = "0.1.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae63702c5627c75addbfb1ea9d1b3842205a6ffd43e4cce884a2a9eb1828fd7" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "combine" version = "4.6.7" @@ -603,6 +735,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "cookie" version = "0.18.1" @@ -663,6 +801,12 @@ dependencies = [ "libc", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -672,6 +816,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -706,6 +859,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "cssparser" version = "0.36.0" @@ -810,6 +972,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.8" @@ -948,6 +1124,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "df-im-gateway" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "df-tunnel", + "futures-util", + "larksuite-oapi-sdk-rs", + "rusqlite", + "serde", + "serde_json", + "tempfile", + "tokio", + "tokio-tungstenite 0.23.1", + "tracing", + "tracing-subscriber", +] + [[package]] name = "df-mcp" version = "0.1.0" @@ -1077,8 +1272,19 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", ] [[package]] @@ -1214,6 +1420,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + [[package]] name = "embed-resource" version = "3.0.9" @@ -1361,6 +1573,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -1992,6 +2210,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.10.1" @@ -2252,6 +2479,16 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding", + "hybrid-array", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2277,6 +2514,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -2411,6 +2657,38 @@ dependencies = [ "zeroize", ] +[[package]] +name = "larksuite-oapi-sdk-rs" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c1fcaec70e6e1c98183aa4c57f08c7165403b5dece93713545d8a18104683b" +dependencies = [ + "aes", + "aioduct", + "base64 0.22.1", + "bytes", + "cbc", + "flate2", + "futures-util", + "hex", + "http", + "percent-encoding", + "prost", + "prost-build", + "rustls", + "serde", + "serde_json", + "sha1 0.11.0", + "sha2 0.11.0", + "subtle", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.26.2", + "tracing", + "url", + "uuid", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2608,6 +2886,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2650,6 +2934,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "native-tls" version = "0.2.18" @@ -2710,6 +3000,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify-rust" version = "4.18.1" @@ -3030,6 +3330,15 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -3173,6 +3482,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.14.0", +] + [[package]] name = "phf" version = "0.13.1" @@ -3404,6 +3723,58 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck 0.5.0", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.117", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quick-xml" version = "0.39.4" @@ -3771,6 +4142,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "1.1.4" @@ -3798,6 +4178,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.14.1" @@ -4188,8 +4589,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -4199,8 +4611,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -4613,7 +5036,7 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "syn 2.0.117", "tauri-utils", "thiserror 2.0.18", @@ -5080,6 +5503,22 @@ dependencies = [ "tungstenite 0.24.0", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.26.2", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -5461,7 +5900,7 @@ dependencies = [ "log", "native-tls", "rand 0.8.6", - "sha1", + "sha1 0.10.6", "thiserror 1.0.69", "utf-8", ] @@ -5479,11 +5918,30 @@ dependencies = [ "httparse", "log", "rand 0.8.6", - "sha1", + "sha1 0.10.6", "thiserror 1.0.69", "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.4", + "rustls", + "rustls-pki-types", + "sha1 0.10.6", + "thiserror 2.0.18", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.3" @@ -5714,6 +6172,15 @@ dependencies = [ "wit-bindgen 0.51.0", ] +[[package]] +name = "wasix" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae86f02046da16a333a9129d31451423e1657737ecdafed4193838a5f54c5cfe" +dependencies = [ + "wasi", +] + [[package]] name = "wasm-bindgen" version = "0.2.123" @@ -6610,7 +7077,7 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2", + "sha2 0.10.9", "soup3", "tao-macros", "thiserror 2.0.18", @@ -6645,6 +7112,23 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + [[package]] name = "xdg-home" version = "1.3.0" @@ -6715,7 +7199,7 @@ dependencies = [ "rand 0.8.6", "serde", "serde_repr", - "sha1", + "sha1 0.10.6", "static_assertions", "tracing", "uds_windows", diff --git a/crates/df-im-gateway/Cargo.toml b/crates/df-im-gateway/Cargo.toml new file mode 100644 index 0000000..c2ad0d7 --- /dev/null +++ b/crates/df-im-gateway/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "df-im-gateway" +version = "0.1.0" +edition = "2021" +description = "IM 网关:飞书/企微/钉钉/Web 四端远程接入 DevFlow AI Chat(WS Commander 连 u-relay)" + +[dependencies] +anyhow = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +rusqlite = { workspace = true } +tokio = { workspace = true } +async-trait = { workspace = true } +tracing = { workspace = true } +df-tunnel = { path = "../df-tunnel" } + +# WS(与 df-tunnel 同款本地版本声明,workspace 无这几项) +tokio-tungstenite = { version = "0.23", features = ["native-tls"] } +futures-util = "0.3" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +# 飞书 SDK(长连接 + 归一化消息;channel 自动连带 ws+messaging) +larksuite-oapi-sdk-rs = { version = "0.3.12", features = ["channel"] } + +[dev-dependencies] +tempfile = "3" diff --git a/crates/df-im-gateway/src/adapters.rs b/crates/df-im-gateway/src/adapters.rs new file mode 100644 index 0000000..ca2d9d9 --- /dev/null +++ b/crates/df-im-gateway/src/adapters.rs @@ -0,0 +1,251 @@ +//! 平台适配器(P0 预留项 ④:trait 分层,统一 InboundEvent/OutboundAction) +//! +//! 网关核心只认识这两个类型,四端差异(飞书卡片/企微 Markdown/钉钉卡片/Web JSON)收敛在各 adapter。 +//! P0 只实现 Web,飞书/企微/钉钉留 trait 位(P1/P2/P4 各自落地,不动核心)。 + +use async_trait::async_trait; +use tokio::sync::mpsc; + +/// 平台入站事件(IM 平台 → 网关,已归一化) +#[derive(Debug, Clone)] +pub enum InboundEvent { + /// 用户发消息 + Message { + platform: String, + chat_id: String, + user_id: String, + text: String, + }, + /// 会话切换(/new 或菜单) + NewSession { platform: String, chat_id: String }, + /// 审批按钮回调 + Approval { + platform: String, + chat_id: String, + approval_id: String, + approved: bool, + }, +} + +/// 平台出站动作(网关 → IM 平台,已归一化) +#[derive(Debug, Clone)] +pub enum OutboundAction { + /// 发/更新 AI 流式文本(全量内容,适配器自选卡片或文本) + StreamText { chat_id: String, text: String, done: bool }, + /// 发送审批卡片(同意/拒绝按钮) + ApprovalCard { chat_id: String, approval_id: String, title: String, detail: String }, + /// 发系统提示 + Notice { chat_id: String, text: String }, +} + +/// 平台适配器 +#[async_trait] +pub trait PlatformAdapter: Send + Sync { + /// 平台名(feishu/wecom/dingtalk/web,与 session_map.platform 对应)。 + /// 核心循环经 platform_index 注册表间接使用。 + #[allow(dead_code)] + fn name(&self) -> &str; + /// 启动适配器(长连接/回调服务器/WS),入站事件从 events 通道流出。阻塞运行。 + async fn run(&self, events: mpsc::Sender) -> anyhow::Result<()>; + + /// 出站动作分发(网关核心调用) + async fn dispatch(&self, action: OutboundAction) -> anyhow::Result<()>; + + /// 清理平台的流式消息状态(网关在新建会话映射时调用,兜底上轮异常中止的残留)。 + /// 默认空实现:无流式覆写语义的平台(Web 直接推帧)无需关心。 + async fn reset_stream(&self, _chat_id: &str) {} +} + +/// Web Chat 适配器(网关直接托管浏览器 WS,无三方依赖) +pub mod web { + use std::collections::HashMap; + use std::sync::Arc; + + use async_trait::async_trait; + use futures_util::{SinkExt, StreamExt}; + use tokio::sync::{mpsc, Mutex}; + use tokio_tungstenite::tungstenite::Message; + + use super::{InboundEvent, OutboundAction, PlatformAdapter}; + + /// 浏览器连接注册表(chat_id → ws 发送通道) + type WsRegistry = Arc>>>; + + /// Web 适配器:浏览器 WS 直连网关 0.0.0.0:9191(accept 协议见 handle_web_ws) + #[derive(Clone)] + pub struct WebAdapter { + registry: WsRegistry, + } + + impl WebAdapter { + pub fn new() -> Self { + Self { registry: Arc::new(Mutex::new(HashMap::new())) } + } + } + + impl Default for WebAdapter { + fn default() -> Self { + Self::new() + } + } + + #[async_trait] + impl PlatformAdapter for WebAdapter { + fn name(&self) -> &str { + "web" + } + + async fn run(&self, events: mpsc::Sender) -> anyhow::Result<()> { + let listener = tokio::net::TcpListener::bind("0.0.0.0:9191").await?; + tracing::info!("[web] WS 服务监听 0.0.0.0:9191"); + loop { + let (stream, _peer) = listener.accept().await?; + let registry = self.registry.clone(); + let events = events.clone(); + tokio::spawn(async move { + if let Err(e) = handle_web_ws(stream, registry, events).await { + tracing::warn!("[web] 浏览器连接异常退出: {e}"); + } + }); + } + } + + async fn dispatch(&self, action: OutboundAction) -> anyhow::Result<()> { + match action { + OutboundAction::StreamText { chat_id, text, done } => { + let frame = serde_json::json!({ + "type": if done { "stream_done" } else { "stream_delta" }, + "text": text, + }); + self.send_ws(&chat_id, &frame.to_string()).await + } + OutboundAction::ApprovalCard { chat_id, approval_id, title, detail } => { + let frame = serde_json::json!({ + "type": "approval", + "approval_id": approval_id, + "title": title, + "detail": detail, + }); + self.send_ws(&chat_id, &frame.to_string()).await + } + OutboundAction::Notice { chat_id, text } => { + let frame = serde_json::json!({ "type": "notice", "text": text }); + self.send_ws(&chat_id, &frame.to_string()).await + } + } + } + } + + impl WebAdapter { + async fn send_ws(&self, chat_id: &str, text: &str) -> anyhow::Result<()> { + let registry = self.registry.lock().await; + if let Some(tx) = registry.get(chat_id) { + tx.send(Message::Text(text.to_string())).await?; + } + Ok(()) + } + } + + /// 单个浏览器 WS 会话。 + /// + /// 协议(浏览器 → 网关): + /// ```json + /// 首帧绑定: {"type":"hello","chat_id":"...","user_id":"..."} + /// 消息: {"type":"message","text":"..."} + /// 重置会话: {"type":"new_session"} + /// 审批: {"type":"approval","approval_id":"...","approved":true|false} + /// ``` + /// 网关 → 浏览器:stream_delta / stream_done / approval / notice(见 dispatch)。 + async fn handle_web_ws( + stream: tokio::net::TcpStream, + registry: WsRegistry, + events: mpsc::Sender, + ) -> anyhow::Result<()> { + let ws = tokio_tungstenite::accept_async(stream).await?; + let (mut sink, mut upstream) = ws.split(); + + // 首帧 hello 绑定 chat_id + let first = upstream.next().await.ok_or_else(|| anyhow::anyhow!("浏览器连接即断"))??; + let hello: serde_json::Value = match &first { + Message::Text(t) => serde_json::from_str(t)?, + _ => anyhow::bail!("首帧非文本"), + }; + let chat_id = hello + .get("chat_id") + .and_then(|v| v.as_str()) + .unwrap_or("web-anon") + .to_string(); + let user_id = hello.get("user_id").and_then(|v| v.as_str()).unwrap_or("anon").to_string(); + + // 注册发送通道:出站帧经 ws_tx → forward 任务写 socket + let (ws_tx, mut ws_rx) = mpsc::channel::(64); + registry.lock().await.insert(chat_id.clone(), ws_tx); + + tracing::info!("[web] 浏览器接入 chat_id={chat_id} user={user_id}"); + + let forward = tokio::spawn(async move { + while let Some(frame) = ws_rx.recv().await { + if sink.send(frame).await.is_err() { + break; + } + } + }); + + let result: anyhow::Result<()> = async { + while let Some(msg) = upstream.next().await { + match msg? { + Message::Text(t) => { + let v: serde_json::Value = serde_json::from_str(&t).unwrap_or_default(); + match v.get("type").and_then(|x| x.as_str()).unwrap_or("") { + "message" => { + let text = v.get("text").and_then(|x| x.as_str()).unwrap_or("").to_string(); + events + .send(InboundEvent::Message { + platform: "web".into(), + chat_id: chat_id.clone(), + user_id: user_id.clone(), + text, + }) + .await + .map_err(|_| anyhow::anyhow!("核心入站通道关闭"))?; + } + "new_session" => { + events + .send(InboundEvent::NewSession { + platform: "web".into(), + chat_id: chat_id.clone(), + }) + .await + .map_err(|_| anyhow::anyhow!("核心入站通道关闭"))?; + } + "approval" => { + let approval_id = + v.get("approval_id").and_then(|x| x.as_str()).unwrap_or("").to_string(); + let approved = v.get("approved").and_then(|x| x.as_bool()).unwrap_or(false); + events + .send(InboundEvent::Approval { + platform: "web".into(), + chat_id: chat_id.clone(), + approval_id, + approved, + }) + .await + .map_err(|_| anyhow::anyhow!("核心入站通道关闭"))?; + } + _ => {} + } + } + Message::Close(_) => break, + _ => {} + } + } + Ok(()) + } + .await; + + registry.lock().await.remove(&chat_id); + forward.abort(); + tracing::info!("[web] 浏览器断开 chat_id={chat_id}"); + result + } +} diff --git a/crates/df-im-gateway/src/commander.rs b/crates/df-im-gateway/src/commander.rs new file mode 100644 index 0000000..6f95dc1 --- /dev/null +++ b/crates/df-im-gateway/src/commander.rs @@ -0,0 +1,188 @@ +//! WS Commander 客户端(连 u-relay /ws/commander) +//! +//! 与桌面端 df-tunnel(Agent 角色)对称:网关作为 Commander 发 TunnelCommand、收 TunnelEvent。 +//! 协议类型直接复用 df_tunnel::events(编译期一致)。 +//! +//! 连接生命周期:hello {kind:"commander", channel_id, token} → (relay 校验) → 双向消息。 +//! 断线重连:指数退避(1/2/4/8/16/30s 封顶,与桌面端 df-tunnel 同款策略)。 +//! +//! 注:relay 对同 channel 多 Commander 的投递行为(P0 必验项 ①)待实测—— +//! 过渡期约定同 channel 单活跃控制端。 + +use std::time::Duration; + +use futures_util::{SinkExt, StreamExt}; +use tokio::sync::mpsc; +use tokio_tungstenite::tungstenite::Message; +use tracing::{info, warn}; + +use df_tunnel::events::{Hello, HelloKind, RelayControlError}; +use df_tunnel::{TunnelCommand, TunnelEvent, TunnelMessage}; + +/// 指数退避序列(秒,封顶 30) +fn backoff_secs(attempt: u32) -> u64 { + [1, 2, 4, 8, 16, 30] + .get(attempt.min(5) as usize) + .copied() + .unwrap_or(30) +} + +/// 启动 Commander 连接循环(阻塞直到 shutdown)。重连在内部循环,指令/事件双向透传。 +/// +/// * `relay_url` — ws://host:9180/ws/commander +/// * `channel_id` — 配对的桌面端 channel +/// * `token` — U_RELAY_TOKEN 同源 +/// * `ev_tx` — 事件出口(桌面端 → 网关核心) +/// * `cmd_rx` — 指令入口(网关核心 → 桌面端) +/// * `shutdown` — 关闭信号 +pub async fn run_commander( + relay_url: String, + channel_id: String, + token: String, + ev_tx: mpsc::Sender, + mut cmd_rx: mpsc::Receiver, + shutdown: tokio::sync::watch::Receiver, +) -> anyhow::Result<()> { + let mut attempt: u32 = 0; + loop { + if *shutdown.borrow() { + info!("[commander] 收到关闭信号,退出"); + return Ok(()); + } + match connect_once(&relay_url, &channel_id, &token, &ev_tx, &mut cmd_rx, shutdown.clone()).await { + Ok(()) => { + info!("[commander] 连接正常关闭"); + return Ok(()); + } + Err(e) => { + let wait = backoff_secs(attempt); + attempt += 1; + warn!("[commander] 连接断开({e}),{}s 后重连(第 {attempt} 次)", wait); + let mut shutdown_w = shutdown.clone(); + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(wait)) => {} + _ = shutdown_w.changed() => return Ok(()), + } + } + } + } +} + +/// 单次连接会话(hello 握手 + 消息循环),断开/出错返回 Err 触发外层重连 +async fn connect_once( + relay_url: &str, + channel_id: &str, + token: &str, + ev_tx: &mpsc::Sender, + cmd_rx: &mut mpsc::Receiver, + mut shutdown: tokio::sync::watch::Receiver, +) -> anyhow::Result<()> { + let (ws, _resp) = tokio_tungstenite::connect_async(relay_url).await?; + let (mut sink, mut stream) = ws.split(); + + // hello 握手(kind=commander;字段与 u-relay::relay::Hello 严格一致) + let hello = Hello { + kind: HelloKind::Commander, + channel_id: channel_id.to_string(), + token: token.to_string(), + }; + sink.send(Message::Text(serde_json::to_string(&hello)?)).await?; + + // 等待 hello_ack(首条文本帧;control error 帧则报错) + let ack = tokio::time::timeout(Duration::from_secs(10), stream.next()).await + .map_err(|_| anyhow::anyhow!("hello_ack 10s 超时"))? + .ok_or_else(|| anyhow::anyhow!("连接在握手期关闭"))?; + match ack? { + Message::Text(t) => { + // relay 控制帧:{"kind":"control","error":"..."} → 握手被拒 + if let Ok(ctrl_err) = serde_json::from_str::(&t) { + if ctrl_err.kind == "control" { + anyhow::bail!("握手被拒: {}", ctrl_err.error); + } + } + info!("[commander] hello_ack: channel={channel_id} 握手成功"); + } + other => anyhow::bail!("hello_ack 非文本帧: {other:?}"), + } + + // 心跳:30s ping(relay 90s 读死线) + let mut ping = tokio::time::interval(Duration::from_secs(30)); + ping.tick().await; // 首个立即返回,跳过 + + // 消息循环 + loop { + tokio::select! { + _ = shutdown.changed() => { + let _ = sink.close().await; + return Ok(()); + } + _ = ping.tick() => { + sink.send(Message::Ping(vec![])).await?; + } + cmd = cmd_rx.recv() => { + let Some(cmd) = cmd else { + // 核心侧通道关闭:正常收尾 + let _ = sink.close().await; + return Ok(()); + }; + let frame = TunnelMessage::Command(cmd); + sink.send(Message::Text(serde_json::to_string(&frame)?)).await?; + } + msg = stream.next() => { + match msg { + Some(Ok(Message::Text(t))) => { + match serde_json::from_str::(&t) { + Ok(TunnelMessage::Event(e)) => { + if ev_tx.send(e).await.is_err() { + return Err(anyhow::anyhow!("核心事件通道关闭")); + } + } + Ok(TunnelMessage::Command(_)) => { + warn!("[commander] commander 收到 Command 帧(协议错向,忽略)"); + } + Err(_) => warn!("[commander] 无法解析帧(非 TunnelMessage,忽略): {} 字节", t.len()), + } + } + Some(Ok(Message::Ping(p))) => { let _ = sink.send(Message::Pong(p)).await; } + Some(Ok(_)) => {} + Some(Err(e)) => return Err(e.into()), + None => return Err(anyhow::anyhow!("stream 结束")), + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn backoff_sequence() { + assert_eq!(backoff_secs(0), 1); + assert_eq!(backoff_secs(1), 2); + assert_eq!(backoff_secs(2), 4); + assert_eq!(backoff_secs(3), 8); + assert_eq!(backoff_secs(4), 16); + assert_eq!(backoff_secs(5), 30); + assert_eq!(backoff_secs(99), 30, "封顶 30s"); + } + + #[test] + fn tunnel_message_roundtrip() { + let cmd = TunnelMessage::Command(TunnelCommand::Send { + conv_id: "c1".into(), + content: "你好".into(), + }); + let json = serde_json::to_string(&cmd).unwrap(); + assert!(json.contains("\"command\"")); + let back: TunnelMessage = serde_json::from_str(&json).unwrap(); + match back { + TunnelMessage::Command(TunnelCommand::Send { conv_id, content }) => { + assert_eq!(conv_id, "c1"); + assert_eq!(content, "你好"); + } + _ => panic!("roundtrip 失败"), + } + } +} diff --git a/crates/df-im-gateway/src/config.rs b/crates/df-im-gateway/src/config.rs new file mode 100644 index 0000000..0cc0119 --- /dev/null +++ b/crates/df-im-gateway/src/config.rs @@ -0,0 +1,143 @@ +//! 网关配置(env 加载,凭证不进 git) +//! +//! 配置来源:环境变量 > 默认值(gateway.toml 文件加载为后续增强,当前未实现)。 +//! u-relay token 与桌面端 df-tunnel 同源(env U_RELAY_TOKEN)。 + +use serde::Deserialize; + +/// 网关全局配置 +#[derive(Debug, Clone, Deserialize)] +pub struct GatewayConfig { + /// u-relay WS 地址(生产 u-relay.1216.top,本地 http://127.0.0.1:9180) + pub relay_url: String, + /// relay 配对 token(env U_RELAY_TOKEN 同源) + pub relay_token: String, + /// channel 路由表:IM 侧默认走 default_channel;按前缀路由的多桌面端配置(预留项 ⑥) + #[serde(default)] + pub channels: Vec, + /// 飞书应用凭证(未配置则该平台禁用) + #[serde(default)] + pub feishu: Option, + /// TextDelta 节流窗口毫秒(默认 800,IM 频控友好) + #[serde(default = "default_throttle_ms")] + pub throttle_ms: u64, + /// 用户白名单(空=放行所有,生产部署务必显式配置) + /// + /// 注:历史注释「空=拒绝所有」与 main.rs 实现(空=放行)矛盾,走查 2026-10-04 定为 + /// 空=放行——否则首次启动零配置时飞书/Web 全部拒绝,连 banner 警告都没有,反而 + /// 更难发现配置缺失。生产风险由 main.rs 启动 banner 常驻警告 + 白名单缺失提示兜底。 + #[serde(default)] + pub whitelist: Vec, +} + +/// channel 路由项:IM 指令前缀 → 桌面端 channel +#[derive(Debug, Clone, Deserialize)] +pub struct ChannelRoute { + /// 指令前缀(如 "/dev2 xxx" 路由到 chan-home);空串=默认 channel + pub prefix: String, + /// u-relay channel_id(桌面端 df-tunnel 配置的同一 channel) + pub channel_id: String, +} + +/// 飞书企业自建应用凭证 +#[derive(Debug, Clone, Deserialize)] +pub struct FeishuConfig { + pub app_id: String, + pub app_secret: String, + /// 长连接模式事件订阅(免公网回调;当前恒 true,保留显式开关供回调模式扩展) + #[allow(dead_code)] + pub long_connection: bool, +} + +fn default_throttle_ms() -> u64 { + 800 +} + +impl GatewayConfig { + /// 从 env + 可选 toml 文件构建。缺失关键项(relay_url/token)返回 Err。 + pub fn load() -> anyhow::Result { + let relay_url = std::env::var("GATEWAY_RELAY_URL") + .unwrap_or_else(|_| "ws://127.0.0.1:9180/ws/commander".to_string()); + let relay_token = std::env::var("GATEWAY_RELAY_TOKEN") + .or_else(|_| std::env::var("U_RELAY_TOKEN")) + .map_err(|_| anyhow::anyhow!("缺少 GATEWAY_RELAY_TOKEN / U_RELAY_TOKEN 环境变量"))?; + + let feishu = match ( + std::env::var("FEISHU_APP_ID"), + std::env::var("FEISHU_APP_SECRET"), + ) { + (Ok(app_id), Ok(app_secret)) => Some(FeishuConfig { + app_id, + app_secret, + long_connection: true, + }), + _ => None, + }; + + let whitelist = std::env::var("GATEWAY_WHITELIST") + .map(|v| v.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()) + .unwrap_or_default(); + + let default_channel = std::env::var("GATEWAY_DEFAULT_CHANNEL").unwrap_or_default(); + let channels = if default_channel.is_empty() { + Vec::new() + } else { + vec![ChannelRoute { prefix: String::new(), channel_id: default_channel }] + }; + + Ok(Self { + relay_url, + relay_token, + channels, + feishu, + throttle_ms: default_throttle_ms(), + whitelist, + }) + } + + /// 查指令应路由到的 channel(前缀匹配,空前缀=兜底默认) + pub fn route(&self, command_text: &str) -> Option<&str> { + // 精确前缀优先,空前缀兜底 + self.channels + .iter() + .find(|c| !c.prefix.is_empty() && command_text.starts_with(&c.prefix)) + .map(|c| c.channel_id.as_str()) + .or_else(|| self.channels.iter().find(|c| c.prefix.is_empty()).map(|c| c.channel_id.as_str())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn route_prefix_priority() { + let cfg = GatewayConfig { + relay_url: String::new(), + relay_token: String::new(), + channels: vec![ + ChannelRoute { prefix: "/dev2".into(), channel_id: "chan-home".into() }, + ChannelRoute { prefix: String::new(), channel_id: "chan-work".into() }, + ], + feishu: None, + throttle_ms: 800, + whitelist: vec![], + }; + assert_eq!(cfg.route("/dev2 修个 bug"), Some("chan-home")); + assert_eq!(cfg.route("普通指令"), Some("chan-work")); + assert_eq!(cfg.route("/dev2"), Some("chan-home")); + } + + #[test] + fn route_empty_when_no_channels() { + let cfg = GatewayConfig { + relay_url: String::new(), + relay_token: String::new(), + channels: vec![], + feishu: None, + throttle_ms: 800, + whitelist: vec![], + }; + assert_eq!(cfg.route("任何"), None); + } +} diff --git a/crates/df-im-gateway/src/feishu.rs b/crates/df-im-gateway/src/feishu.rs new file mode 100644 index 0000000..d797ea5 --- /dev/null +++ b/crates/df-im-gateway/src/feishu.rs @@ -0,0 +1,350 @@ +//! 飞书适配器(P2):长连接事件 + 归一化消息 + 卡片审批 +//! +//! 依赖 larksuite-oapi-sdk-rs features=["channel"](自动连带 ws+messaging)。 +//! 长连接:飞书←→网关出站直连,不经 u-relay(u-relay 仅穿 NAT 段到桌面端)。 +//! +//! 验证记录(2026-10-04,SDK 0.3.12 源码核对): +//! - 卡片回调:ChannelBuilder::on_card_action(NormalizedCardAction) +//! 内部走 dispatcher.on_card_action_trigger(channel/handler.rs:112), +//! 即新版 card.action.trigger 事件通道,长连接可收(遗留验证项 ✅)。 +//! - 发卡片:SendInput.card(String) → INTERACTIVE 类型(messaging.rs:752)。 +//! - 流式更新:channel_messaging().send() 拿 message_id 后 edit_text() 覆写全文。 + +use std::collections::HashMap; +use std::sync::Arc; + +use async_trait::async_trait; +use larksuite_oapi_sdk_rs::card::v1::{ + Action, ActionComponent, Button, ButtonType, Card, CardDocument, Div, Element, Header, + Markdown, +}; +use larksuite_oapi_sdk_rs::channel::{Channel, ChannelPolicy}; +use larksuite_oapi_sdk_rs::{EventDispatcher, LarkClient, LarkError, RequestOption}; +use tokio::sync::mpsc::Sender; + +use crate::adapters::{InboundEvent, OutboundAction, PlatformAdapter}; +use crate::config::FeishuConfig; + +/// 飞书适配器:channel 归一化消息 + 卡片审批,单飞书应用单实例 +#[derive(Clone)] +pub struct FeishuAdapter { + client: LarkClient, + /// 流式会话注册表:chat_id → 已发出的流式消息 message_id(用于 edit_text 覆写) + streams: Arc>>, +} + +impl FeishuAdapter { + pub fn new(cfg: &FeishuConfig) -> anyhow::Result { + let client = LarkClient::builder(&cfg.app_id, &cfg.app_secret).build()?; + Ok(Self { + client, + streams: Arc::new(tokio::sync::Mutex::new(HashMap::new())), + }) + } + + /// 构造失败时的离线哨兵:client 用占位凭证(不启动 run,dispatch 记日志即弃) + pub fn offline() -> Self { + let client = LarkClient::builder("offline", "offline").build() + .expect("占位凭证构造 LarkClient 不发网络请求,必成功"); + Self { + client, + streams: Arc::new(tokio::sync::Mutex::new(HashMap::new())), + } + } +} + +#[async_trait] +impl PlatformAdapter for FeishuAdapter { + fn name(&self) -> &str { + "feishu" + } + + /// 兜底清理流式残留(走查 #3):桌面端异常中止时无 Completed 事件, + /// streams entry 永驻导致下轮首帧错覆写旧消息。核心循环在新建会话映射时调用。 + async fn reset_stream(&self, chat_id: &str) { + self.streams.lock().await.remove(chat_id); + } + + async fn run(&self, events: Sender) -> anyhow::Result<()> { + // 长连接模式:空 verification_token/encrypt_key(免公网回调) + let dispatcher = EventDispatcher::new("", ""); + let client = self.client.clone(); + + // 入站事件通道复制两份,分别给消息/卡片回调闭包 + let events_for_msg = events.clone(); + let events_for_card = events; + + let channel: Channel<'static> = { + // Channel<'a> 借用 client;用 Box::leak 提升为 'static(适配器生命周期即进程生命周期) + let leaked: &'static LarkClient = Box::leak(Box::new(client)); + Channel::builder(leaked, dispatcher) + .policy(ChannelPolicy::default().require_mention(false)) + .on_message(move |msg| { + let events = events_for_msg.clone(); + async move { + handle_inbound_message(events, msg).await; + Ok(()) + } + }) + .on_card_action(move |action| { + let events = events_for_card.clone(); + async move { + handle_card_action(events, action).await; + Ok(()) + } + }) + .build() + }; + + tracing::info!("[feishu] 长连接启动(免公网回调)"); + channel.start().await.map_err(|e: LarkError| anyhow::anyhow!("飞书长连接退出: {e}"))?; + Ok(()) + } + + async fn dispatch(&self, action: OutboundAction) -> anyhow::Result<()> { + let opt = &RequestOption::default(); + match action { + OutboundAction::StreamText { chat_id, text, done } => { + // 两段式:锁内只取/写 message_id(纯内存操作),网络 IO 在锁外执行。 + // 修复走查 #2:同 chat 仍串行(顺序语义),不同 chat 不再被同一把锁挡住。 + let existing_id: Option = { + let streams = self.streams.lock().await; + streams.get(&chat_id).cloned() + }; + if let Some(message_id) = existing_id { + let res = self.client.channel_messaging().edit_text(&message_id, &text, opt).await; + if let Err(e) = res { + tracing::warn!("[feishu] 流式更新失败(消息可能已被撤回/超时): {e},回退新发"); + self.streams.lock().await.remove(&chat_id); + } + } else { + // 首帧:发新文本消息,记录 message_id 供后续覆写 + let input = larksuite_oapi_sdk_rs::channel::SendInput { + receive_id: Some(chat_id.clone()), + text: Some(text), + ..Default::default() + }; + let send_result = self.client.channel_messaging().send(&input, opt).await; + match send_result { + Ok(sr) if !sr.message_id.is_empty() => { + self.streams.lock().await.insert(chat_id.clone(), sr.message_id); + } + Ok(_) => { + tracing::warn!("[feishu] 发送流式首帧未返回 message_id"); + } + Err(e) => { + tracing::error!("[feishu] 发送流式首帧失败: {e}"); + } + } + } + if done { + self.streams.lock().await.remove(&chat_id); + } + Ok(()) + } + OutboundAction::ApprovalCard { chat_id, approval_id, title, detail } => { + let card = build_approval_card(&approval_id, &title, &detail); + let document = CardDocument::new(card) + .map_err(|e| anyhow::anyhow!("审批卡片校验失败: {e}"))?; + // encoded_content 是 SDK pub(crate);直接序列化 card(校验已过) + let content = serde_json::to_string(document.card()) + .map_err(|e| anyhow::anyhow!("卡片序列化失败: {e}"))?; + let input = larksuite_oapi_sdk_rs::channel::SendInput { + receive_id: Some(chat_id), + card: Some(content), + ..Default::default() + }; + self.client + .channel_messaging() + .send(&input, opt) + .await + .map_err(|e| anyhow::anyhow!("审批卡片发送失败: {e}"))?; + Ok(()) + } + OutboundAction::Notice { chat_id, text } => { + let input = larksuite_oapi_sdk_rs::channel::SendInput { + receive_id: Some(chat_id), + text: Some(text), + ..Default::default() + }; + self.client + .channel_messaging() + .send(&input, opt) + .await + .map_err(|e| anyhow::anyhow!("通知发送失败: {e}"))?; + Ok(()) + } + } + } +} + +/// 入站消息:群聊需 @机器人(mentioned_bot)或单聊直发 +async fn handle_inbound_message(events: Sender, msg: larksuite_oapi_sdk_rs::channel::NormalizedMessage) { + // 只处理文本(text 优先,回退 content) + if msg.message_type != "text" { + tracing::debug!("[feishu] 忽略非文本消息 type={}", msg.message_type); + return; + } + // 群聊必须 @机器人;单聊(p2p)直发 + if msg.chat_type == "group" && !msg.mentioned_bot { + tracing::debug!("[feishu] 群聊未 @机器人,忽略"); + return; + } + let text = msg.text.clone().unwrap_or_default().trim().to_string(); + if text.is_empty() { + return; + } + // 指令:/new 重置会话 + if text == "/new" { + let _ = events + .send(InboundEvent::NewSession { + platform: "feishu".into(), + chat_id: msg.chat_id.clone(), + }) + .await; + return; + } + let user_id = msg + .sender + .user_id + .as_ref() + .and_then(|u| u.open_id.clone()) + .unwrap_or_else(|| "unknown".into()); + let _ = events + .send(InboundEvent::Message { + platform: "feishu".into(), + chat_id: msg.chat_id, + user_id, + text, + }) + .await; +} + +/// 卡片按钮回调:action_value 携带 approval_id + approved +async fn handle_card_action(events: Sender, action: larksuite_oapi_sdk_rs::channel::NormalizedCardAction) { + // 按钮约定的 value 形态:{"approval_id": "...", "approved": true|false} + let approval_id = action + .action_value + .get("approval_id") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(); + let approved = action + .action_value + .get("approved") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + if approval_id.is_empty() { + tracing::warn!("[feishu] 卡片回调缺 approval_id,忽略"); + return; + } + let chat_id = action.open_chat_id.clone().unwrap_or_default(); + let _ = events + .send(InboundEvent::Approval { + platform: "feishu".into(), + chat_id, + approval_id, + approved, + }) + .await; +} + +/// 构造审批卡片(Card JSON 1.0):标题 + 详情 + 同意/拒绝按钮 +fn build_approval_card(approval_id: &str, title: &str, detail: &str) -> Card { + let detail_md = format!("{}\n\n**审批 ID**: `{approval_id}`", escape_md(detail)); + let to_json_value = |v: serde_json::Value| -> larksuite_oapi_sdk_rs::JsonValue { + serde_json::from_value(serde_json::to_value(v).expect("serde_json::Value 可逆")) + .expect("按钮 value 结构兼容") + }; + let actions = Action::new() + .action(ActionComponent::Button( + Button::new(larksuite_oapi_sdk_rs::card::v1::Text::plain("✅ 同意")) + .button_type(ButtonType::Primary) + .value(to_json_value(serde_json::json!({"approval_id": approval_id, "approved": true}))), + )) + .action(ActionComponent::Button( + Button::new(larksuite_oapi_sdk_rs::card::v1::Text::plain("❌ 拒绝")) + .button_type(ButtonType::Danger) + .value(to_json_value(serde_json::json!({"approval_id": approval_id, "approved": false}))), + )); + Card::new() + .header(Header::new(title)) + .element(Element::Div(Div::new( + larksuite_oapi_sdk_rs::card::v1::Text::lark_md(detail_md), + ))) + .element(Element::Markdown(Markdown::new("---"))) + .element(Element::Action(actions)) +} + +/// Markdown 转义(飞书卡片 lark_md 语法) +fn escape_md(s: &str) -> String { + s.replace('\\', "\\\\") + .replace('*', "\\*") + .replace('_', "\\_") + .replace('`', "\\`") + .replace('[', "\\[") + .replace(']', "\\]") + .replace('<', "\\<") + .replace('>', "\\>") + .replace('(', "\\(") + .replace(')', "\\)") + .replace('#', "\\#") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::FeishuConfig; + + /// 构造冒烟:new/offline 均不发网络请求,验证适配器可实例化 + #[test] + fn feishu_adapter_constructs() { + let cfg = FeishuConfig { + app_id: "cli_smoke".into(), + app_secret: "secret".into(), + long_connection: true, + }; + let adapter = FeishuAdapter::new(&cfg).expect("适配器构造应成功"); + assert_eq!(adapter.name(), "feishu"); + + let offline = FeishuAdapter::offline(); + assert_eq!(offline.name(), "feishu"); + } + + /// 审批卡片构造 + 协议校验(CardDocument::new 是发送前的强校验门槛) + #[test] + fn approval_card_passes_protocol_validation() { + let card = build_approval_card("appr-123", "工具审批请求", "run_command: cargo test"); + CardDocument::new(card).expect("审批卡片应通过 Card JSON 1.0 校验"); + } + + /// 卡片构造包含同意/拒绝按钮及 approval_id 载荷 + #[test] + fn approval_card_has_buttons_and_payload() { + let card = build_approval_card("appr-456", "标题", "详情"); + let json = serde_json::to_value(&card).unwrap(); + let elements = json["elements"].as_array().expect("elements 数组"); + // 最后一个元素是 Action 行,内含 2 个按钮 + let action = elements.last().unwrap(); + let buttons = action["actions"].as_array().expect("actions 数组"); + assert_eq!(buttons.len(), 2); + let values: Vec = buttons + .iter() + .map(|b| b["value"]["approval_id"].as_str().unwrap_or_default().to_string()) + .collect(); + assert!(values.iter().all(|v| v == "appr-456")); + let approved_flags: Vec = buttons + .iter() + .map(|b| b["value"]["approved"].as_bool().unwrap_or(false)) + .collect(); + assert!(approved_flags.contains(&true) && approved_flags.contains(&false)); + } + + /// Markdown 转义:卡片详情中的 markdown 语法字符被转义 + #[test] + fn escape_md_works() { + assert_eq!(escape_md("a*b_c`d\\e"), "a\\*b\\_c\\`d\\\\e"); + // 走查 #7:链接/标题/引用语法字符也须转义,防用户内容注入卡片结构 + assert_eq!(escape_md("[x](http://e)#h (y)"), "\\[x\\]\\(http://e\\)\\#h \\\\(y\\)"); + } +} diff --git a/crates/df-im-gateway/src/main.rs b/crates/df-im-gateway/src/main.rs new file mode 100644 index 0000000..e92fe96 --- /dev/null +++ b/crates/df-im-gateway/src/main.rs @@ -0,0 +1,255 @@ +//! df-im-gateway 主入口:装配 config + store + throttle + commander + adapters +//! +//! 骨架版(P0):Web 适配器真实可跑,事件流全链路打通。 +//! 用法: +//! ```text +//! df-im-gateway.exe # 默认配置(env) +//! GATEWAY_RELAY_URL=... GATEWAY_RELAY_TOKEN=... \ +//! GATEWAY_DEFAULT_CHANNEL=chan-work df-im-gateway.exe +//! ``` + +mod adapters; +mod commander; +mod config; +mod feishu; +mod store; +mod throttle; + +use std::path::PathBuf; + +use tokio::sync::mpsc; + +use adapters::{InboundEvent, OutboundAction, PlatformAdapter}; +use config::GatewayConfig; +use store::SessionStore; +use throttle::DeltaThrottle; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .init(); + + let cfg = GatewayConfig::load()?; + if cfg.channels.is_empty() { + anyhow::bail!("未配置任何 channel 路由(GATEWAY_DEFAULT_CHANNEL),无法启动"); + } + + let data_dir = std::env::var("GATEWAY_DATA_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("data")); + std::fs::create_dir_all(&data_dir)?; + let store = SessionStore::open(&data_dir.join("sessions.db"))?; + + info_banner(&cfg); + + // ── 核心事件总线:适配器 → 核心 ───────────────────────────── + let (inbound_tx, mut inbound_rx) = mpsc::channel::(256); + + // Web 适配器(四端首个,P0) + let web_adapter = adapters::web::WebAdapter::new(); + { + let web_run = web_adapter.clone(); + let ev_tx = inbound_tx.clone(); + tokio::spawn(async move { + if let Err(e) = web_run.run(ev_tx).await { + tracing::error!("[web] 适配器退出: {e}"); + } + }); + } + + // 飞书适配器(P2):FEISHU_APP_ID/SECRET 配置时启用长连接 + let feishu_adapter = cfg.feishu.as_ref().map(|f| match feishu::FeishuAdapter::new(f) { + Ok(adapter) => { + let feishu_run = adapter.clone(); + let ev_tx = inbound_tx.clone(); + tokio::spawn(async move { + if let Err(e) = feishu_run.run(ev_tx).await { + tracing::error!("[feishu] 适配器退出: {e}"); + } + }); + tracing::info!("[feishu] 适配器已启动(长连接)"); + adapter + } + Err(e) => { + tracing::error!("[feishu] 适配器构造失败(跳过): {e}"); + feishu::FeishuAdapter::offline() + } + }); + + // ── Commander 连接(u-relay)──────────────────────────────── + let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false); + let default_channel = cfg + .channels + .iter() + .find(|c| c.prefix.is_empty()) + .map(|c| c.channel_id.clone()) + .unwrap_or_else(|| cfg.channels[0].channel_id.clone()); + + // 事件流:commander 任务 → 核心循环 + let (ev_tx, mut ev_rx) = mpsc::channel::(256); + // 指令流:核心循环 → commander 任务(透传到 WS) + let (cmd_tx, cmd_rx) = mpsc::channel::(256); + + { + let relay_url = cfg.relay_url.clone(); + let token = cfg.relay_token.clone(); + let channel = default_channel.clone(); + tokio::spawn(async move { + if let Err(e) = commander::run_commander(relay_url, channel, token, ev_tx, cmd_rx, shutdown_rx).await { + tracing::error!("[commander] 连接循环退出: {e}"); + } + }); + } + + // ── 核心分发循环(单 select,持有全部状态,无跨线程共享)───── + let mut throttle = DeltaThrottle::new(cfg.throttle_ms); + // 审批幂等表:approval_id → 已处理标记(P0 必验项 ②:防 pending→approved 二次点击重复转发) + let mut handled_approvals: std::collections::HashSet = std::collections::HashSet::new(); + // 平台注册表(走查 #4):显式 platform → 下标映射,替代硬编码 "feishu"=>1 与 + // adapters vec 构建顺序的隐式耦合。新增平台(企微/钉钉)只在此处注册一次。 + let mut adapters: Vec> = vec![Box::new(web_adapter.clone())]; + let mut platform_index: std::collections::HashMap = + std::collections::HashMap::from([("web".to_string(), 0usize)]); + if let Some(fs) = feishu_adapter { + platform_index.insert("feishu".to_string(), adapters.len()); + adapters.push(Box::new(fs)); + } + let platform_of = |platform_or_conv: &str| -> usize { + // conv_id 形如 "platform:chat_id",前缀即平台;未知平台回退 web(0) + let platform = platform_or_conv.split(':').next().unwrap_or("web"); + *platform_index.get(platform).unwrap_or(&0) + }; + // 停止信号 + let shutdown_ctrl = tokio::signal::ctrl_c(); + tokio::pin!(shutdown_ctrl); + let cfg_loop = cfg.clone(); + + loop { + tokio::select! { + _ = &mut shutdown_ctrl => { + tracing::info!("收到 Ctrl+C,优雅退出"); + let _ = shutdown_tx.send(true); + break; + } + // ── TunnelEvent(桌面端 → 网关)── + ev = ev_rx.recv() => { + let Some(ev) = ev else { break }; + match ev { + df_tunnel::TunnelEvent::TextDelta { conv_id, delta } => { + // 出站按 conv_id 前缀(platform:chat_id)路由回原平台 + let flush = throttle.feed(&conv_id, &delta); + if let Some(text) = flush { + let idx = platform_of(&conv_id); + let _ = adapters[idx] + .dispatch(OutboundAction::StreamText { chat_id: conv_id, text, done: false }) + .await; + } + } + df_tunnel::TunnelEvent::Completed { conv_id, .. } => { + let tail = throttle.finish(&conv_id); + let full = tail.unwrap_or_default(); + let idx = platform_of(&conv_id); + let _ = adapters[idx] + .dispatch(OutboundAction::StreamText { chat_id: conv_id, text: full, done: true }) + .await; + } + df_tunnel::TunnelEvent::Approval { conv_id, approval_id, description } => { + let idx = platform_of(&conv_id); + let _ = adapters[idx] + .dispatch(OutboundAction::ApprovalCard { + chat_id: conv_id, + approval_id, + title: "工具审批请求".into(), + detail: description, + }) + .await; + } + df_tunnel::TunnelEvent::ToolCall { .. } => { + // P0 骨架不转发 ToolCall 到 IM(卡片噪音),仅日志 + tracing::debug!("[core] ToolCall 事件(骨架忽略)"); + } + } + } + // ── InboundEvent(IM → 网关)── + inbound = inbound_rx.recv() => { + let Some(ev) = inbound else { break }; + match ev { + InboundEvent::Message { platform, chat_id, user_id, text } => { + // 回复适配器:按来源平台回路由 + let reply_idx = platform_of(&platform); + if !cfg_loop.whitelist.is_empty() && !cfg_loop.whitelist.contains(&user_id) { + let _ = adapters[reply_idx] + .dispatch(OutboundAction::Notice { chat_id, text: "⛔ 你不在白名单内".into() }) + .await; + continue; + } + let Some(channel_id) = cfg_loop.route(&text) else { + let _ = adapters[reply_idx] + .dispatch(OutboundAction::Notice { chat_id, text: "⚠️ 无可用 channel".into() }) + .await; + continue; + }; + // 会话映射:无则新建(conv_id = platform:chat_id,Web 端 chat_id 同源) + let conv_id = match store.get_conv(&platform, &chat_id) { + Some(m) => m.conv_id, + None => { + let conv = format!("{platform}:{chat_id}"); + let _ = store.set_conv(&platform, &chat_id, &conv, channel_id); + // 走查 #3:新建会话时清理流式残留(上轮异常中止无 + // Completed 事件,streams entry 永驻会错覆写旧消息) + adapters[reply_idx].reset_stream(&chat_id).await; + conv + } + }; + let _ = cmd_tx + .send(df_tunnel::TunnelCommand::Send { conv_id, content: text }) + .await; + } + InboundEvent::NewSession { platform, chat_id } => { + let _ = store.remove(&platform, &chat_id); + let reply_idx = platform_of(&platform); + let _ = adapters[reply_idx] + .dispatch(OutboundAction::Notice { chat_id, text: "🔄 已重置会话".into() }) + .await; + } + InboundEvent::Approval { platform, chat_id, approval_id, approved } => { + // 幂等:同 approval_id 只处理首次(P0 必验项 ②) + if handled_approvals.contains(&approval_id) { + tracing::debug!("[core] 审批 {approval_id} 已处理,忽略重复点击"); + continue; + } + handled_approvals.insert(approval_id.clone()); + let conv_id = store + .get_conv(&platform, &chat_id) + .map(|m| m.conv_id) + .unwrap_or_else(|| format!("{platform}:{chat_id}")); + let _ = cmd_tx + .send(df_tunnel::TunnelCommand::Approve { conv_id, approval_id, accepted: approved }) + .await; + } + } + } + } + } + + Ok(()) +} + +fn info_banner(cfg: &GatewayConfig) { + println!("╔══════════════════════════════════════════╗"); + println!("║ df-im-gateway v0.1.0 (P0 骨架) ║"); + println!("╚══════════════════════════════════════════╝"); + println!(" relay : {}", cfg.relay_url); + println!(" channels : {} 条路由", cfg.channels.len()); + println!(" throttle : {}ms", cfg.throttle_ms); + println!(" whitelist : {} 用户", cfg.whitelist.len()); + if cfg.whitelist.is_empty() { + println!(" ⚠️ 白名单为空:任何人可用本网关(开发默认)。生产部署请设置 GATEWAY_WHITELIST!"); + } + println!(" feishu : {}", if cfg.feishu.is_some() { "已配置" } else { "未配置(跳过)" }); + println!(" web ws : 0.0.0.0:9191"); +} diff --git a/crates/df-im-gateway/src/store.rs b/crates/df-im-gateway/src/store.rs new file mode 100644 index 0000000..93ade3c --- /dev/null +++ b/crates/df-im-gateway/src/store.rs @@ -0,0 +1,151 @@ +//! 会话映射存储(IM chat_id ↔ DevFlow conv_id,SQLite + WAL) +//! +//! 建表即带 owner_user_id/platform 列(P0 预留项 ⑤,当前恒默认值, +//! S2 多人场景免迁表)。 +//! +//! 表结构: +//! ```sql +//! CREATE TABLE IF NOT EXISTS session_map ( +//! platform TEXT NOT NULL, -- feishu/wecom/dingtalk/web +//! chat_id TEXT NOT NULL, -- IM 侧会话(单聊 user_id / 群聊 chat_id) +//! conv_id TEXT NOT NULL, -- DevFlow 会话 ID +//! owner_user_id TEXT NOT NULL DEFAULT '',-- 预留 S2 多人归属(当前恒 '') +//! channel_id TEXT NOT NULL DEFAULT '',-- 路由到的桌面端 channel(预留项 ⑥) +//! created_at INTEGER NOT NULL, +//! updated_at INTEGER NOT NULL, +//! PRIMARY KEY (platform, chat_id) +//! ); +//! ``` + +use std::path::Path; +use std::sync::Arc; + +use rusqlite::Connection; + +/// 单条会话映射(字段预留 S2 多人/多桌面端,当前核心只读 conv_id) +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct SessionMap { + pub platform: String, + pub chat_id: String, + pub conv_id: String, + pub owner_user_id: String, + pub channel_id: String, +} + +#[derive(Clone)] +pub struct SessionStore { + conn: Arc>, +} + +impl SessionStore { + /// 打开(或创建)库文件并确保表存在。WAL 模式(与桌面端一致)。 + pub fn open(path: &Path) -> anyhow::Result { + let conn = Connection::open(path)?; + conn.pragma_update(None, "journal_mode", "WAL")?; + conn.execute_batch( + "CREATE TABLE IF NOT EXISTS session_map ( + platform TEXT NOT NULL, + chat_id TEXT NOT NULL, + conv_id TEXT NOT NULL, + owner_user_id TEXT NOT NULL DEFAULT '', + channel_id TEXT NOT NULL DEFAULT '', + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + PRIMARY KEY (platform, chat_id) + );", + )?; + Ok(Self { conn: Arc::new(std::sync::Mutex::new(conn)) }) + } + + /// 内存库(测试/演示) + #[allow(dead_code)] + pub fn open_in_memory() -> anyhow::Result { + let conn = Connection::open_in_memory()?; + conn.execute_batch( + "CREATE TABLE IF NOT EXISTS session_map ( + platform TEXT NOT NULL, chat_id TEXT NOT NULL, conv_id TEXT NOT NULL, + owner_user_id TEXT NOT NULL DEFAULT '', channel_id TEXT NOT NULL DEFAULT '', + created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, + PRIMARY KEY (platform, chat_id) + );", + )?; + Ok(Self { conn: Arc::new(std::sync::Mutex::new(conn)) }) + } + + /// 查 IM 会话映射的 conv_id(未映射返回 None) + pub fn get_conv(&self, platform: &str, chat_id: &str) -> Option { + let conn = self.conn.lock().ok()?; + conn.query_row( + "SELECT platform, chat_id, conv_id, owner_user_id, channel_id FROM session_map \ + WHERE platform = ?1 AND chat_id = ?2", + [platform, chat_id], + |row| { + Ok(SessionMap { + platform: row.get(0)?, + chat_id: row.get(1)?, + conv_id: row.get(2)?, + owner_user_id: row.get(3)?, + channel_id: row.get(4)?, + }) + }, + ) + .ok() + } + + /// 写/更新映射(upsert) + pub fn set_conv(&self, platform: &str, chat_id: &str, conv_id: &str, channel_id: &str) -> anyhow::Result<()> { + let conn = self.conn.lock().map_err(|e| anyhow::anyhow!("store 锁中毒: {e}"))?; + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_millis() as i64) + .unwrap_or(0); + conn.execute( + "INSERT INTO session_map (platform, chat_id, conv_id, owner_user_id, channel_id, created_at, updated_at) \ + VALUES (?1, ?2, ?3, '', ?4, ?5, ?5) \ + ON CONFLICT(platform, chat_id) DO UPDATE SET conv_id = ?3, channel_id = ?4, updated_at = ?5", + rusqlite::params![platform, chat_id, conv_id, channel_id, now], + )?; + Ok(()) + } + + /// 删除映射(桌面端会话已删,悬空清理) + pub fn remove(&self, platform: &str, chat_id: &str) -> anyhow::Result<()> { + let conn = self.conn.lock().map_err(|e| anyhow::anyhow!("store 锁中毒: {e}"))?; + conn.execute("DELETE FROM session_map WHERE platform = ?1 AND chat_id = ?2", [platform, chat_id])?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn upsert_and_get() { + let store = SessionStore::open_in_memory().unwrap(); + store.set_conv("feishu", "oc_123", "conv_abc", "chan-work").unwrap(); + let m = store.get_conv("feishu", "oc_123").unwrap(); + assert_eq!(m.conv_id, "conv_abc"); + assert_eq!(m.channel_id, "chan-work"); + assert_eq!(m.owner_user_id, ""); + + // upsert 覆盖 + store.set_conv("feishu", "oc_123", "conv_new", "chan-home").unwrap(); + let m = store.get_conv("feishu", "oc_123").unwrap(); + assert_eq!(m.conv_id, "conv_new"); + + // 删除后悬空 + store.remove("feishu", "oc_123").unwrap(); + assert!(store.get_conv("feishu", "oc_123").is_none()); + } + + #[test] + fn platform_isolated() { + let store = SessionStore::open_in_memory().unwrap(); + store.set_conv("feishu", "c1", "conv1", "ch").unwrap(); + store.set_conv("wecom", "c1", "conv2", "ch").unwrap(); + assert_eq!(store.get_conv("feishu", "c1").unwrap().conv_id, "conv1"); + assert_eq!(store.get_conv("wecom", "c1").unwrap().conv_id, "conv2"); + } +} diff --git a/crates/df-im-gateway/src/throttle.rs b/crates/df-im-gateway/src/throttle.rs new file mode 100644 index 0000000..1a113f3 --- /dev/null +++ b/crates/df-im-gateway/src/throttle.rs @@ -0,0 +1,103 @@ +//! TextDelta 节流聚合器(IM 频控防护) +//! +//! IM 平台消息 API 频控(飞书 ~50 QPS 应用级、企微 200 次/分钟),逐 delta 刷卡片必被限流。 +//! 策略:per-conv 聚合缓冲 + 时间窗(默认 800ms)批量 flush;Completed 时强制冲刷尾包。 +//! +//! 设计要点: +//! - 每个 key 一个缓冲,互不干扰 +//! - 纯函数式:feed() 返回 Option<全量文本>(窗口到期才返回),不持有回调/不 await +//! - flush 语义:全量文本(IM 卡片更新是「全量内容」语义,非增量) +//! - finish() 冲刷尾包并清缓冲;abort() 丢弃缓冲(错误/停止,不输出残文) + +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +/// 单 key 缓冲 +struct ConvBuffer { + /// 累积全量文本 + full_text: String, + /// 上次 flush 时刻 + last_flush: Instant, +} + +/// 节流聚合器 +pub struct DeltaThrottle { + window: Duration, + buffers: HashMap, +} + +impl DeltaThrottle { + pub fn new(window_ms: u64) -> Self { + Self { window: Duration::from_millis(window_ms), buffers: HashMap::new() } + } + + /// 喂入 delta。窗口到期则返回 Some(全量文本)(调用方负责外发),否则 None。 + pub fn feed(&mut self, key: &str, delta: &str) -> Option { + let now = Instant::now(); + let buf = self.buffers.entry(key.to_string()).or_insert_with(|| ConvBuffer { + full_text: String::new(), + last_flush: now, + }); + buf.full_text.push_str(delta); + if buf.last_flush.elapsed() >= self.window { + buf.last_flush = now; + Some(buf.full_text.clone()) + } else { + None + } + } + + /// 会话完成:强制冲刷尾包 + 清缓冲。返回 Some(全量文本) 或(空会话)None。 + pub fn finish(&mut self, key: &str) -> Option { + self.buffers.remove(key).map(|buf| buf.full_text) + } + + /// 丢弃缓冲(错误/停止,不输出残文) + /// + /// 注:当前 TunnelEvent 无 Error/Stopped 变体,核心循环暂无调用点; + /// df-tunnel Phase2 扩展后接入。 + #[allow(dead_code)] + pub fn abort(&mut self, key: &str) { + self.buffers.remove(key); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn throttle_window_batch() { + let mut t = DeltaThrottle::new(60_000); // 60s 窗口:模拟窗口永不到期 + assert_eq!(t.feed("k", "你"), None); + assert_eq!(t.feed("k", "好"), None); + assert_eq!(t.feed("k", "!"), None); + assert_eq!(t.finish("k"), Some("你好!".to_string()), "finish 冲刷全量"); + } + + #[test] + fn window_elapsed_flushes() { + let mut t = DeltaThrottle::new(0); // 0ms 窗口:每次 feed 都到期 + assert_eq!(t.feed("k", "a"), Some("a".to_string())); + assert_eq!(t.feed("k", "b"), Some("ab".to_string())); + // 缓冲仍在 finish 前的最后 flush 版本;finish 拿走剩余 + assert_eq!(t.finish("k"), Some("ab".to_string())); + } + + #[test] + fn abort_drops() { + let mut t = DeltaThrottle::new(60_000); + t.feed("k", "残文"); + t.abort("k"); + assert_eq!(t.finish("k"), None, "abort 后缓冲不存在"); + } + + #[test] + fn keys_isolated() { + let mut t = DeltaThrottle::new(60_000); + t.feed("a", "1"); + t.feed("b", "2"); + assert_eq!(t.finish("a"), Some("1".to_string())); + assert_eq!(t.finish("b"), Some("2".to_string())); + } +} diff --git a/docs/02-架构设计/INDEX.md b/docs/02-架构设计/INDEX.md index 33b1159..1d6e31b 100644 --- a/docs/02-架构设计/INDEX.md +++ b/docs/02-架构设计/INDEX.md @@ -91,6 +91,7 @@ | [后续批次测试用例设计-2026-07-01.md](./专项设计/后续批次测试用例设计-2026-07-01.md) | 📐 测试 | 多Agent并行/worktree隔离/仲裁合并/模板系统/Git-CI/审批政策 的单元+集成+端到端测试用例 | | [测试落地实施方案-2026-07-01.md](./专项设计/测试落地实施方案-2026-07-01.md) | 📐 测试 | 各测试用例的落地方式(Rust 单元/集成/vitest/E2E 手动)、文件位置、Mock 策略、CI 自动化路径 | | [devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md](./专项设计/devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md) | 📐 规划(待评审) | 飞书/企微/钉钉/自定义Web 远程接入 AI Chat:新增 df-im-gateway(WS Commander)复用 u-relay+df-tunnel,桌面端零改动。含必要性分析(§三A)、破坏性分析(§三B)、演进预留(§十 S1-S4:多人/引擎上云/新端)。P0网关→P1 Web→P2飞书→P3钉钉→P4企微 | +| [三方IM-Rust接入与SDK调研-2026-10-04.md](./专项设计/三方IM-Rust接入与SDK调研-2026-10-04.md) | ✅ 调研完成 | 三平台 Rust SDK 选型:飞书 larksuite-oapi-sdk-rs / 钉钉 dingtalk-stream / 企微 wecom-aibot-rust-sdk。**重大发现:企微智能机器人走 WS 长连接免公网回调,三平台统一形态**。含统一 adapter 映射表与 5 项落地风险清单 | --- diff --git a/docs/02-架构设计/专项设计/.research/crates-dingtalk.json b/docs/02-架构设计/专项设计/.research/crates-dingtalk.json new file mode 100644 index 0000000..6f6283d --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/crates-dingtalk.json @@ -0,0 +1 @@ +{"crates":[{"id":"dingtalk","name":"dingtalk","updated_at":"2021-01-08T16:55:40.915241Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2019-12-17T15:51:33.309330Z","downloads":28650,"recent_downloads":93,"default_version":"2.0.2","num_versions":21,"yanked":false,"max_version":"2.0.2","newest_version":"2.0.2","max_stable_version":"2.0.2","description":"DingTalk Robot Util, Send text/markdown/link messages using DingTalk robot, 钉钉机器人","homepage":null,"documentation":null,"repository":"https://git.hatter.ink/hatter/dingtalk","links":{"version_downloads":"/api/v1/crates/dingtalk/downloads","versions":"/api/v1/crates/dingtalk/versions","owners":"/api/v1/crates/dingtalk/owners","owner_team":"/api/v1/crates/dingtalk/owner_team","owner_user":"/api/v1/crates/dingtalk/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk/reverse_dependencies"},"exact_match":true,"trustpub_only":false},{"id":"dingtalk-sdk","name":"dingtalk-sdk","updated_at":"2026-03-10T08:05:38.008550Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2024-09-10T15:00:12.223157Z","downloads":4170,"recent_downloads":121,"default_version":"1.0.9","num_versions":10,"yanked":false,"max_version":"1.0.9","newest_version":"1.0.9","max_stable_version":"1.0.9","description":"Dingtalk SDK for Rust.","homepage":"https://github.com/lvillis/dingtalk-sdk-rust","documentation":"https://docs.rs/dingtalk-sdk","repository":"https://github.com/lvillis/dingtalk-sdk-rust","links":{"version_downloads":"/api/v1/crates/dingtalk-sdk/downloads","versions":"/api/v1/crates/dingtalk-sdk/versions","owners":"/api/v1/crates/dingtalk-sdk/owners","owner_team":"/api/v1/crates/dingtalk-sdk/owner_team","owner_user":"/api/v1/crates/dingtalk-sdk/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk-sdk/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"warpin-dingtalk","name":"warpin-dingtalk","updated_at":"2026-07-16T12:26:53.151958Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-07-16T04:14:46.934080Z","downloads":37,"recent_downloads":37,"default_version":"0.2.6","num_versions":2,"yanked":false,"max_version":"0.2.6","newest_version":"0.2.6","max_stable_version":"0.2.6","description":"DingTalk OpenAPI client primitives for Warpin applications","homepage":null,"documentation":null,"repository":"https://github.com/time-origin/warpin-rs-common","links":{"version_downloads":"/api/v1/crates/warpin-dingtalk/downloads","versions":"/api/v1/crates/warpin-dingtalk/versions","owners":"/api/v1/crates/warpin-dingtalk/owners","owner_team":"/api/v1/crates/warpin-dingtalk/owner_team","owner_user":"/api/v1/crates/warpin-dingtalk/owner_user","reverse_dependencies":"/api/v1/crates/warpin-dingtalk/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"matrix-bridge-dingtalk","name":"matrix-bridge-dingtalk","updated_at":"2026-02-28T20:16:16.675109Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-02-28T20:16:16.675109Z","downloads":25,"recent_downloads":3,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"A bridge between Matrix and DingTalk written in Rust","homepage":null,"documentation":null,"repository":"https://github.com/palpo-im/matrix-bridge-dingtalk","links":{"version_downloads":"/api/v1/crates/matrix-bridge-dingtalk/downloads","versions":"/api/v1/crates/matrix-bridge-dingtalk/versions","owners":"/api/v1/crates/matrix-bridge-dingtalk/owners","owner_team":"/api/v1/crates/matrix-bridge-dingtalk/owner_team","owner_user":"/api/v1/crates/matrix-bridge-dingtalk/owner_user","reverse_dependencies":"/api/v1/crates/matrix-bridge-dingtalk/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dingtalk-stream-sdk","name":"dingtalk-stream-sdk","updated_at":"2026-04-29T03:22:31.151289Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-23T14:50:39.042188Z","downloads":251,"recent_downloads":60,"default_version":"0.2.5","num_versions":7,"yanked":false,"max_version":"0.2.5","newest_version":"0.2.5","max_stable_version":"0.2.5","description":"DingTalk Stream SDK for Rust","homepage":null,"documentation":null,"repository":"https://github.com/wenhaozhao/dingtalk-stream-sdk-rust","links":{"version_downloads":"/api/v1/crates/dingtalk-stream-sdk/downloads","versions":"/api/v1/crates/dingtalk-stream-sdk/versions","owners":"/api/v1/crates/dingtalk-stream-sdk/owners","owner_team":"/api/v1/crates/dingtalk-stream-sdk/owner_team","owner_user":"/api/v1/crates/dingtalk-stream-sdk/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk-stream-sdk/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dingtalk-stream","name":"dingtalk-stream","updated_at":"2026-03-26T14:00:10.104022Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-07T10:28:29.122380Z","downloads":4664,"recent_downloads":3459,"default_version":"0.1.7","num_versions":8,"yanked":false,"max_version":"0.1.7","newest_version":"0.1.7","max_stable_version":"0.1.7","description":"DingTalk Stream SDK for Rust — WebSocket-based bot messaging, interactive cards, Graph API and event subscription","homepage":"https://github.com/spensercai/dingtalk-stream-sdk-rs","documentation":"https://docs.rs/dingtalk-stream","repository":"https://github.com/spensercai/dingtalk-stream-sdk-rs","links":{"version_downloads":"/api/v1/crates/dingtalk-stream/downloads","versions":"/api/v1/crates/dingtalk-stream/versions","owners":"/api/v1/crates/dingtalk-stream/owners","owner_team":"/api/v1/crates/dingtalk-stream/owner_team","owner_user":"/api/v1/crates/dingtalk-stream/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk-stream/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dingtalk-cli","name":"dingtalk-cli","updated_at":"2024-09-10T15:00:56.585910Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2024-09-10T15:00:56.585910Z","downloads":1241,"recent_downloads":5,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"CLI tools for dingtalk.","homepage":"https://github.com/lvillis/dingtalk-sdk-rust","documentation":"https://docs.rs/dingtalk-cli","repository":"https://github.com/lvillis/dingtalk-sdk-rust","links":{"version_downloads":"/api/v1/crates/dingtalk-cli/downloads","versions":"/api/v1/crates/dingtalk-cli/versions","owners":"/api/v1/crates/dingtalk-cli/owners","owner_team":"/api/v1/crates/dingtalk-cli/owner_team","owner_user":"/api/v1/crates/dingtalk-cli/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk-cli/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"async-dingtalk","name":"async-dingtalk","updated_at":"2025-02-19T11:19:08.937095Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2025-02-16T07:22:19.659350Z","downloads":2379,"recent_downloads":17,"default_version":"1.0.2","num_versions":3,"yanked":false,"max_version":"1.0.2","newest_version":"1.0.2","max_stable_version":"1.0.2","description":"Dingtalk SDK for Rust","homepage":null,"documentation":null,"repository":"https://github.com/cleveng/dingtalk.git","links":{"version_downloads":"/api/v1/crates/async-dingtalk/downloads","versions":"/api/v1/crates/async-dingtalk/versions","owners":"/api/v1/crates/async-dingtalk/owners","owner_team":"/api/v1/crates/async-dingtalk/owner_team","owner_user":"/api/v1/crates/async-dingtalk/owner_user","reverse_dependencies":"/api/v1/crates/async-dingtalk/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dingtalk-stream-sdk-rust","name":"dingtalk-stream-sdk-rust","updated_at":"2024-02-06T13:42:26.068660Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2024-02-06T13:42:26.068660Z","downloads":2074,"recent_downloads":163,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"An **UNOFFICIAL** Rust SDK focused on the development of DingTalk robots.","homepage":"https://github.com/avalon1610/dingtalk-stream-sdk-rust","documentation":null,"repository":"https://github.com/avalon1610/dingtalk-stream-sdk-rust","links":{"version_downloads":"/api/v1/crates/dingtalk-stream-sdk-rust/downloads","versions":"/api/v1/crates/dingtalk-stream-sdk-rust/versions","owners":"/api/v1/crates/dingtalk-stream-sdk-rust/owners","owner_team":"/api/v1/crates/dingtalk-stream-sdk-rust/owner_team","owner_user":"/api/v1/crates/dingtalk-stream-sdk-rust/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk-stream-sdk-rust/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dingtalk-rs","name":"dingtalk-rs","updated_at":"2022-12-16T05:41:28.290689Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2022-07-01T07:26:26.219925Z","downloads":19640,"recent_downloads":64,"default_version":"0.1.16","num_versions":16,"yanked":false,"max_version":"0.1.16","newest_version":"0.1.16","max_stable_version":"0.1.16","description":"钉钉Rust版SDK","homepage":null,"documentation":null,"repository":"https://github.com/shenshouer/dingtalk-rs","links":{"version_downloads":"/api/v1/crates/dingtalk-rs/downloads","versions":"/api/v1/crates/dingtalk-rs/versions","owners":"/api/v1/crates/dingtalk-rs/owners","owner_team":"/api/v1/crates/dingtalk-rs/owner_team","owner_user":"/api/v1/crates/dingtalk-rs/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk-rs/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dingtalk_robot","name":"dingtalk_robot","updated_at":"2026-04-08T08:50:02.554052Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2023-05-02T09:26:49.198702Z","downloads":4136,"recent_downloads":51,"default_version":"0.3.0","num_versions":4,"yanked":false,"max_version":"0.3.0","newest_version":"0.3.0","max_stable_version":"0.3.0","description":"DingTalk Robot","homepage":null,"documentation":null,"repository":"https://github.com/rise0chen/dingtalk","links":{"version_downloads":"/api/v1/crates/dingtalk_robot/downloads","versions":"/api/v1/crates/dingtalk_robot/versions","owners":"/api/v1/crates/dingtalk_robot/owners","owner_team":"/api/v1/crates/dingtalk_robot/owner_team","owner_user":"/api/v1/crates/dingtalk_robot/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk_robot/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"cloudiful-notifier","name":"cloudiful-notifier","updated_at":"2026-09-12T15:43:37.414123Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-06-29T16:15:15.226816Z","downloads":505,"recent_downloads":468,"default_version":"0.3.2","num_versions":6,"yanked":false,"max_version":"0.3.2","newest_version":"0.3.2","max_stable_version":"0.3.2","description":"Async notification delivery library for ntfy, generic webhooks, DingTalk robots, and SMTP email.","homepage":"https://github.com/cloudiful/notifier","documentation":"https://docs.rs/cloudiful-notifier","repository":"https://github.com/cloudiful/notifier","links":{"version_downloads":"/api/v1/crates/cloudiful-notifier/downloads","versions":"/api/v1/crates/cloudiful-notifier/versions","owners":"/api/v1/crates/cloudiful-notifier/owners","owner_team":"/api/v1/crates/cloudiful-notifier/owner_team","owner_user":"/api/v1/crates/cloudiful-notifier/owner_user","reverse_dependencies":"/api/v1/crates/cloudiful-notifier/reverse_dependencies"},"exact_match":false,"trustpub_only":true},{"id":"dingtalk-stream-sdk-rust-macro","name":"dingtalk-stream-sdk-rust-macro","updated_at":"2024-02-06T13:41:50.241005Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2024-02-06T13:41:50.241005Z","downloads":2401,"recent_downloads":198,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"Proc Macro crate for dingtalk-stream-sdk-rust","homepage":"https://github.com/avalon1610/dingtalk-stream-sdk-rust","documentation":null,"repository":"https://github.com/avalon1610/dingtalk-stream-sdk-rust","links":{"version_downloads":"/api/v1/crates/dingtalk-stream-sdk-rust-macro/downloads","versions":"/api/v1/crates/dingtalk-stream-sdk-rust-macro/versions","owners":"/api/v1/crates/dingtalk-stream-sdk-rust-macro/owners","owner_team":"/api/v1/crates/dingtalk-stream-sdk-rust-macro/owner_team","owner_user":"/api/v1/crates/dingtalk-stream-sdk-rust-macro/owner_user","reverse_dependencies":"/api/v1/crates/dingtalk-stream-sdk-rust-macro/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"clawbro","name":"clawbro","updated_at":"2026-03-22T14:47:45.371481Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-19T00:50:55.761956Z","downloads":171,"recent_downloads":42,"default_version":"0.1.10","num_versions":7,"yanked":false,"max_version":"0.1.10","newest_version":"0.1.10","max_stable_version":"0.1.10","description":"ClawBro AI runtime and CLI","homepage":null,"documentation":null,"repository":"https://github.com/fishers6/clawbro","links":{"version_downloads":"/api/v1/crates/clawbro/downloads","versions":"/api/v1/crates/clawbro/versions","owners":"/api/v1/crates/clawbro/owners","owner_team":"/api/v1/crates/clawbro/owner_team","owner_user":"/api/v1/crates/clawbro/owner_user","reverse_dependencies":"/api/v1/crates/clawbro/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"nanobot","name":"nanobot","updated_at":"2026-02-22T16:34:29.494962Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-02-09T03:14:15.456948Z","downloads":431,"recent_downloads":29,"default_version":"0.2.19","num_versions":21,"yanked":false,"max_version":"0.2.19","newest_version":"0.2.19","max_stable_version":"0.2.19","description":"Rust port of nanobot, a lightweight personal AI assistant with tools and multi-channel gateway support.","homepage":"https://github.com/citizenll/nanobot-rs","documentation":"https://docs.rs/nanobot","repository":"https://github.com/citizenll/nanobot-rs","links":{"version_downloads":"/api/v1/crates/nanobot/downloads","versions":"/api/v1/crates/nanobot/versions","owners":"/api/v1/crates/nanobot/owners","owner_team":"/api/v1/crates/nanobot/owner_team","owner_user":"/api/v1/crates/nanobot/owner_user","reverse_dependencies":"/api/v1/crates/nanobot/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"edgecrab-gateway","name":"edgecrab-gateway","updated_at":"2026-07-24T15:28:25.177858Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-04-05T16:44:43.253313Z","downloads":576,"recent_downloads":213,"default_version":"0.12.1","num_versions":21,"yanked":false,"max_version":"0.12.1","newest_version":"0.12.1","max_stable_version":"0.12.1","description":"Multi-platform messaging gateway for the EdgeCrab agent","homepage":null,"documentation":null,"repository":null,"links":{"version_downloads":"/api/v1/crates/edgecrab-gateway/downloads","versions":"/api/v1/crates/edgecrab-gateway/versions","owners":"/api/v1/crates/edgecrab-gateway/owners","owner_team":"/api/v1/crates/edgecrab-gateway/owner_team","owner_user":"/api/v1/crates/edgecrab-gateway/owner_user","reverse_dependencies":"/api/v1/crates/edgecrab-gateway/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"lingshu-gateway","name":"lingshu-gateway","updated_at":"2026-07-13T12:05:02.959527Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-07-13T12:05:02.959527Z","downloads":50,"recent_downloads":50,"default_version":"0.10.0","num_versions":1,"yanked":false,"max_version":"0.10.0","newest_version":"0.10.0","max_stable_version":"0.10.0","description":"Multi-platform messaging gateway for the Lingshu agent","homepage":null,"documentation":null,"repository":null,"links":{"version_downloads":"/api/v1/crates/lingshu-gateway/downloads","versions":"/api/v1/crates/lingshu-gateway/versions","owners":"/api/v1/crates/lingshu-gateway/owners","owner_team":"/api/v1/crates/lingshu-gateway/owner_team","owner_user":"/api/v1/crates/lingshu-gateway/owner_user","reverse_dependencies":"/api/v1/crates/lingshu-gateway/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"zeroclawlabs","name":"zeroclawlabs","updated_at":"2026-04-08T07:41:34.554936Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-14T17:05:57.214498Z","downloads":3578,"recent_downloads":522,"default_version":"0.6.9","num_versions":30,"yanked":false,"max_version":"0.6.9","newest_version":"0.6.9","max_stable_version":"0.6.9","description":"Zero overhead. Zero compromise. 100% Rust. The fastest, smallest AI assistant.","homepage":null,"documentation":null,"repository":"https://github.com/zeroclaw-labs/zeroclaw","links":{"version_downloads":"/api/v1/crates/zeroclawlabs/downloads","versions":"/api/v1/crates/zeroclawlabs/versions","owners":"/api/v1/crates/zeroclawlabs/owners","owner_team":"/api/v1/crates/zeroclawlabs/owner_team","owner_user":"/api/v1/crates/zeroclawlabs/owner_user","reverse_dependencies":"/api/v1/crates/zeroclawlabs/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"pingap-webhook","name":"pingap-webhook","updated_at":"2026-10-01T02:18:28.122982Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2025-05-11T01:32:10.985830Z","downloads":1340,"recent_downloads":63,"default_version":"0.15.0","num_versions":5,"yanked":false,"max_version":"0.15.0","newest_version":"0.15.0","max_stable_version":"0.15.0","description":"Webhook for pingap","homepage":"https://github.com/vicanso/pingap","documentation":null,"repository":"https://github.com/vicanso/pingap","links":{"version_downloads":"/api/v1/crates/pingap-webhook/downloads","versions":"/api/v1/crates/pingap-webhook/versions","owners":"/api/v1/crates/pingap-webhook/owners","owner_team":"/api/v1/crates/pingap-webhook/owner_team","owner_user":"/api/v1/crates/pingap-webhook/owner_user","reverse_dependencies":"/api/v1/crates/pingap-webhook/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"autobuild","name":"autobuild","updated_at":"2025-06-05T03:27:15.368668Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2025-05-30T10:26:49.446105Z","downloads":2590,"recent_downloads":24,"default_version":"0.1.4","num_versions":5,"yanked":false,"max_version":"0.1.4","newest_version":"0.1.4","max_stable_version":"0.1.4","description":"A tool for automatic building and publishing based on git changes","homepage":null,"documentation":null,"repository":"https://github.com/w2moon/autobuild","links":{"version_downloads":"/api/v1/crates/autobuild/downloads","versions":"/api/v1/crates/autobuild/versions","owners":"/api/v1/crates/autobuild/owners","owner_team":"/api/v1/crates/autobuild/owner_team","owner_user":"/api/v1/crates/autobuild/owner_user","reverse_dependencies":"/api/v1/crates/autobuild/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dingding","name":"dingding","updated_at":"2026-09-22T06:16:02.185687Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-05-27T08:57:02.938197Z","downloads":73,"recent_downloads":50,"default_version":"0.1.3","num_versions":3,"yanked":false,"max_version":"0.1.3","newest_version":"0.1.3","max_stable_version":"0.1.3","description":"DingTalk SDK and bot framework for Rust.","homepage":null,"documentation":null,"repository":"https://github.com/lvillis/dingding","links":{"version_downloads":"/api/v1/crates/dingding/downloads","versions":"/api/v1/crates/dingding/versions","owners":"/api/v1/crates/dingding/owners","owner_team":"/api/v1/crates/dingding/owner_team","owner_user":"/api/v1/crates/dingding/owner_user","reverse_dependencies":"/api/v1/crates/dingding/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"xbot","name":"xbot","updated_at":"2026-07-14T15:23:50.720907Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-05-09T05:01:14.117924Z","downloads":187,"recent_downloads":44,"default_version":"0.1.9","num_versions":9,"yanked":false,"max_version":"0.1.9","newest_version":"0.1.9","max_stable_version":"0.1.9","description":"Rust-native autonomous bot runtime for chat automation, tools, scheduled work, and multi-channel delivery","homepage":"https://github.com/guoqingbao/xbot#readme","documentation":null,"repository":"https://github.com/guoqingbao/xbot","links":{"version_downloads":"/api/v1/crates/xbot/downloads","versions":"/api/v1/crates/xbot/versions","owners":"/api/v1/crates/xbot/owners","owner_team":"/api/v1/crates/xbot/owner_team","owner_user":"/api/v1/crates/xbot/owner_user","reverse_dependencies":"/api/v1/crates/xbot/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"casdog","name":"casdog","updated_at":"2026-03-10T11:50:32.055548Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-02-06T15:42:03.953610Z","downloads":49,"recent_downloads":11,"default_version":"0.1.2","num_versions":2,"yanked":false,"max_version":"0.1.2","newest_version":"0.1.2","max_stable_version":"0.1.2","description":"Casdog IAM/SSO server - Casdoor-compatible identity and access management","homepage":"https://github.com/taidge/casdog","documentation":null,"repository":"https://github.com/taidge/casdog","links":{"version_downloads":"/api/v1/crates/casdog/downloads","versions":"/api/v1/crates/casdog/versions","owners":"/api/v1/crates/casdog/owners","owner_team":"/api/v1/crates/casdog/owner_team","owner_user":"/api/v1/crates/casdog/owner_user","reverse_dependencies":"/api/v1/crates/casdog/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"adaclaw","name":"adaclaw","updated_at":"2026-03-01T10:26:53.580284Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-01T10:26:53.580284Z","downloads":35,"recent_downloads":8,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"Lightweight, secure, multi-channel Rust AI Agent Runtime","homepage":null,"documentation":null,"repository":"https://github.com/worldflat21-lang/AdaClaw","links":{"version_downloads":"/api/v1/crates/adaclaw/downloads","versions":"/api/v1/crates/adaclaw/versions","owners":"/api/v1/crates/adaclaw/owners","owner_team":"/api/v1/crates/adaclaw/owner_team","owner_user":"/api/v1/crates/adaclaw/owner_user","reverse_dependencies":"/api/v1/crates/adaclaw/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"nydus-rs","name":"nydus-rs","updated_at":"2023-03-02T05:40:03.895822Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2022-06-19T01:47:59.420274Z","downloads":4889,"recent_downloads":4,"default_version":"2.2.0","num_versions":3,"yanked":false,"max_version":"2.2.0","newest_version":"2.2.0","max_stable_version":"2.2.0","description":"Nydus Image Service","homepage":"https://nydus.dev/","documentation":null,"repository":"https://github.com/dragonflyoss/image-service","links":{"version_downloads":"/api/v1/crates/nydus-rs/downloads","versions":"/api/v1/crates/nydus-rs/versions","owners":"/api/v1/crates/nydus-rs/owners","owner_team":"/api/v1/crates/nydus-rs/owner_team","owner_user":"/api/v1/crates/nydus-rs/owner_user","reverse_dependencies":"/api/v1/crates/nydus-rs/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"pushx","name":"pushx","updated_at":"2025-04-20T14:30:43.936625Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2025-04-20T14:30:43.936625Z","downloads":701,"recent_downloads":2,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"A simple unified push library for DingTalk, extendable to Feishu, etc.","homepage":null,"documentation":null,"repository":null,"links":{"version_downloads":"/api/v1/crates/pushx/downloads","versions":"/api/v1/crates/pushx/versions","owners":"/api/v1/crates/pushx/owners","owner_team":"/api/v1/crates/pushx/owner_team","owner_user":"/api/v1/crates/pushx/owner_user","reverse_dependencies":"/api/v1/crates/pushx/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dragonfly-api","name":"dragonfly-api","updated_at":"2026-09-10T02:55:39.654372Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2023-07-18T04:38:03.852203Z","downloads":308094,"recent_downloads":20396,"default_version":"2.3.8","num_versions":312,"yanked":false,"max_version":"2.3.8","newest_version":"2.3.8","max_stable_version":"2.3.8","description":"Canonical location of the Dragonfly API definition","homepage":"https://d7y.io","documentation":null,"repository":"https://github.com/dragonflyoss/api","links":{"version_downloads":"/api/v1/crates/dragonfly-api/downloads","versions":"/api/v1/crates/dragonfly-api/versions","owners":"/api/v1/crates/dragonfly-api/owners","owner_team":"/api/v1/crates/dragonfly-api/owner_team","owner_user":"/api/v1/crates/dragonfly-api/owner_user","reverse_dependencies":"/api/v1/crates/dragonfly-api/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"openlark-auth","name":"openlark-auth","updated_at":"2026-07-27T09:31:51.450489Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-18T12:55:17.843172Z","downloads":7802,"recent_downloads":4276,"default_version":"0.20.0","num_versions":9,"yanked":false,"max_version":"0.20.0","newest_version":"0.20.0","max_stable_version":"0.20.0","description":"OpenLark 认证模块 - 提供令牌管理、缓存、验证和刷新功能","homepage":null,"documentation":"https://docs.rs/openlark","repository":"https://github.com/foxzool/openlark","links":{"version_downloads":"/api/v1/crates/openlark-auth/downloads","versions":"/api/v1/crates/openlark-auth/versions","owners":"/api/v1/crates/openlark-auth/owners","owner_team":"/api/v1/crates/openlark-auth/owner_team","owner_user":"/api/v1/crates/openlark-auth/owner_user","reverse_dependencies":"/api/v1/crates/openlark-auth/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"dragonfly-client","name":"dragonfly-client","updated_at":"2026-09-22T03:55:21.719106Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2023-07-18T06:17:47.914460Z","downloads":180227,"recent_downloads":1453,"default_version":"1.5.7","num_versions":271,"yanked":false,"max_version":"1.5.7","newest_version":"1.5.7","max_stable_version":"1.5.7","description":"Dragonfly client written in Rust","homepage":"https://d7y.io/","documentation":null,"repository":"https://github.com/dragonflyoss/client.git","links":{"version_downloads":"/api/v1/crates/dragonfly-client/downloads","versions":"/api/v1/crates/dragonfly-client/versions","owners":"/api/v1/crates/dragonfly-client/owners","owner_team":"/api/v1/crates/dragonfly-client/owner_team","owner_user":"/api/v1/crates/dragonfly-client/owner_user","reverse_dependencies":"/api/v1/crates/dragonfly-client/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"ceresdb-client","name":"ceresdb-client","updated_at":"2023-07-05T08:34:23.973835Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2023-02-28T09:59:21.614424Z","downloads":26237,"recent_downloads":1263,"default_version":"1.0.2","num_versions":3,"yanked":false,"max_version":"1.0.2","newest_version":"1.0.2","max_stable_version":"1.0.2","description":"Rust implementation of CeresDB client.","homepage":null,"documentation":null,"repository":"https://github.com/CeresDB/ceresdb-client-rs","links":{"version_downloads":"/api/v1/crates/ceresdb-client/downloads","versions":"/api/v1/crates/ceresdb-client/versions","owners":"/api/v1/crates/ceresdb-client/owners","owner_team":"/api/v1/crates/ceresdb-client/owner_team","owner_user":"/api/v1/crates/ceresdb-client/owner_user","reverse_dependencies":"/api/v1/crates/ceresdb-client/reverse_dependencies"},"exact_match":false,"trustpub_only":false}],"meta":{"total":41,"next_page":"?q=dingtalk&per_page=30&seek=W2ZhbHNlLDAuMSwxMjYzLDc5Njg2Ml0","prev_page":null}} \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/crates-search.ps1 b/docs/02-架构设计/专项设计/.research/crates-search.ps1 new file mode 100644 index 0000000..d6096a9 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/crates-search.ps1 @@ -0,0 +1,8 @@ +$queries = @('feishu', 'lark', 'larksuite') +foreach ($q in $queries) { + Write-Output "===== query: $q =====" + $r = Invoke-RestMethod -Uri "https://crates.io/api/v1/crates?q=$q&per_page=20" -Headers @{ 'User-Agent' = 'devflow-research' } + $r.crates | ForEach-Object { + "{0,-22} v{1,-10} dl={2,-8} upd={3} repo={4}" -f $_.id, $_.max_version, $_.downloads, ($_.updated_at -split 'T')[0], $_.repository + } +} diff --git a/docs/02-架构设计/专项设计/.research/crates-wecom.json b/docs/02-架构设计/专项设计/.research/crates-wecom.json new file mode 100644 index 0000000..2082966 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/crates-wecom.json @@ -0,0 +1 @@ +{"crates":[{"id":"wecom","name":"wecom","updated_at":"2022-03-27T16:09:24.714973Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2022-03-27T16:09:24.714973Z","downloads":2309,"recent_downloads":47,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"Asnyc Client for WeCom","homepage":"https://github.com/zwpaper/wecom","documentation":"https://github.com/zwpaper/wecom","repository":"https://github.com/zwpaper/wecom","links":{"version_downloads":"/api/v1/crates/wecom/downloads","versions":"/api/v1/crates/wecom/versions","owners":"/api/v1/crates/wecom/owners","owner_team":"/api/v1/crates/wecom/owner_team","owner_user":"/api/v1/crates/wecom/owner_user","reverse_dependencies":"/api/v1/crates/wecom/reverse_dependencies"},"exact_match":true,"trustpub_only":false},{"id":"wecom-pro","name":"wecom-pro","updated_at":"2026-06-07T07:27:12.496092Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-04-02T04:54:46.557502Z","downloads":103,"recent_downloads":19,"default_version":"0.2.0","num_versions":5,"yanked":false,"max_version":"0.2.0","newest_version":"0.2.0","max_stable_version":"0.2.0","description":"企业微信命令行工具 - 多 Bot 支持版本,从 WecomTeam/wecom-cli fork 而来","homepage":"https://github.com/Liangdi/wecom-pro","documentation":null,"repository":"https://github.com/Liangdi/wecom-pro","links":{"version_downloads":"/api/v1/crates/wecom-pro/downloads","versions":"/api/v1/crates/wecom-pro/versions","owners":"/api/v1/crates/wecom-pro/owners","owner_team":"/api/v1/crates/wecom-pro/owner_team","owner_user":"/api/v1/crates/wecom-pro/owner_user","reverse_dependencies":"/api/v1/crates/wecom-pro/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecom-aibot-rust-sdk","name":"wecom-aibot-rust-sdk","updated_at":"2026-04-07T06:54:49.590296Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-04-07T06:32:08.127691Z","downloads":346,"recent_downloads":106,"default_version":"1.0.2","num_versions":3,"yanked":false,"max_version":"1.0.2","newest_version":"1.0.2","max_stable_version":"1.0.2","description":"企业微信智能机器人 Rust SDK —— 基于 WebSocket 长连接通道,提供消息收发、流式回复、模板卡片、事件回调、文件下载解密等核心能力","homepage":null,"documentation":null,"repository":"https://github.com/camark/wecom-aibot-rust-sdk","links":{"version_downloads":"/api/v1/crates/wecom-aibot-rust-sdk/downloads","versions":"/api/v1/crates/wecom-aibot-rust-sdk/versions","owners":"/api/v1/crates/wecom-aibot-rust-sdk/owners","owner_team":"/api/v1/crates/wecom-aibot-rust-sdk/owner_team","owner_user":"/api/v1/crates/wecom-aibot-rust-sdk/owner_user","reverse_dependencies":"/api/v1/crates/wecom-aibot-rust-sdk/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecom-agent","name":"wecom-agent","updated_at":"2024-03-20T15:34:07.079037Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2024-02-18T03:58:57.659113Z","downloads":21876,"recent_downloads":37,"default_version":"0.1.16","num_versions":17,"yanked":false,"max_version":"0.1.16","newest_version":"0.1.16","max_stable_version":"0.1.16","description":"企业微信API的轻封装,让消息发送更加便捷。","homepage":null,"documentation":null,"repository":"https://github.com/wandering-ai/wecom-agent","links":{"version_downloads":"/api/v1/crates/wecom-agent/downloads","versions":"/api/v1/crates/wecom-agent/versions","owners":"/api/v1/crates/wecom-agent/owners","owner_team":"/api/v1/crates/wecom-agent/owner_team","owner_user":"/api/v1/crates/wecom-agent/owner_user","reverse_dependencies":"/api/v1/crates/wecom-agent/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecom-cli","name":"wecom-cli","updated_at":"2026-05-27T09:45:01.816454Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-04-02T03:59:37.617834Z","downloads":138,"recent_downloads":23,"default_version":"0.1.9","num_versions":7,"yanked":false,"max_version":"0.1.9","newest_version":"0.1.9","max_stable_version":"0.1.9","description":"The official CLI for WeCom — 企业微信命令行工具,让人类和 AI Agent 都能在终端中操作企业微信","homepage":"https://github.com/WecomTeam/wecom-cli","documentation":null,"repository":"https://github.com/WecomTeam/wecom-cli","links":{"version_downloads":"/api/v1/crates/wecom-cli/downloads","versions":"/api/v1/crates/wecom-cli/versions","owners":"/api/v1/crates/wecom-cli/owners","owner_team":"/api/v1/crates/wecom-cli/owner_team","owner_user":"/api/v1/crates/wecom-cli/owner_user","reverse_dependencies":"/api/v1/crates/wecom-cli/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"rdjksp-wecom-cli","name":"rdjksp-wecom-cli","updated_at":"2026-06-26T11:07:54.342735Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-06-26T11:07:54.342735Z","downloads":67,"recent_downloads":22,"default_version":"0.1.9","num_versions":1,"yanked":false,"max_version":"0.1.9","newest_version":"0.1.9","max_stable_version":"0.1.9","description":"The official CLI for WeCom — 企业微信命令行工具,让人类和 AI Agent 都能在终端中操作企业微信","homepage":null,"documentation":null,"repository":"https://github.com/RongdujiKsp/wecom-cli","links":{"version_downloads":"/api/v1/crates/rdjksp-wecom-cli/downloads","versions":"/api/v1/crates/rdjksp-wecom-cli/versions","owners":"/api/v1/crates/rdjksp-wecom-cli/owners","owner_team":"/api/v1/crates/rdjksp-wecom-cli/owner_team","owner_user":"/api/v1/crates/rdjksp-wecom-cli/owner_user","reverse_dependencies":"/api/v1/crates/rdjksp-wecom-cli/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecom-crypto","name":"wecom-crypto","updated_at":"2024-04-06T08:50:49.813776Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2024-02-10T14:32:14.606092Z","downloads":13049,"recent_downloads":29,"default_version":"0.1.9","num_versions":10,"yanked":false,"max_version":"0.1.9","newest_version":"0.1.9","max_stable_version":"0.1.9","description":"企业微信API数据加解密库。","homepage":null,"documentation":null,"repository":"https://github.com/wandering-ai/wecom-crypto","links":{"version_downloads":"/api/v1/crates/wecom-crypto/downloads","versions":"/api/v1/crates/wecom-crypto/versions","owners":"/api/v1/crates/wecom-crypto/owners","owner_team":"/api/v1/crates/wecom-crypto/owner_team","owner_user":"/api/v1/crates/wecom-crypto/owner_user","reverse_dependencies":"/api/v1/crates/wecom-crypto/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wxkefu-rs","name":"wxkefu-rs","updated_at":"2026-01-13T16:35:26.954917Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2025-11-06T08:28:44.483775Z","downloads":187,"recent_downloads":15,"default_version":"0.1.5","num_versions":5,"yanked":false,"max_version":"0.1.5","newest_version":"0.1.5","max_stable_version":"0.1.5","description":"WeChat Customer Service (WeCom Kf) client for Rust: token, encrypted callback, message sync/send, media, welcome, recall, customer profiles, and Kf account management.","homepage":"https://github.com/ooiai/wxkefu-rs","documentation":"https://docs.rs/wxkefu-rs","repository":"https://github.com/ooiai/wxkefu-rs","links":{"version_downloads":"/api/v1/crates/wxkefu-rs/downloads","versions":"/api/v1/crates/wxkefu-rs/versions","owners":"/api/v1/crates/wxkefu-rs/owners","owner_team":"/api/v1/crates/wxkefu-rs/owner_team","owner_user":"/api/v1/crates/wxkefu-rs/owner_user","reverse_dependencies":"/api/v1/crates/wxkefu-rs/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecom_bot","name":"wecom_bot","updated_at":"2023-05-04T06:14:27.289760Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2023-03-06T15:45:04.566687Z","downloads":3804,"recent_downloads":8,"default_version":"0.2.0","num_versions":3,"yanked":false,"max_version":"0.2.0","newest_version":"0.2.0","max_stable_version":"0.2.0","description":"A WeCom Bot API lib for sending group messages","homepage":null,"documentation":null,"repository":null,"links":{"version_downloads":"/api/v1/crates/wecom_bot/downloads","versions":"/api/v1/crates/wecom_bot/versions","owners":"/api/v1/crates/wecom_bot/owners","owner_team":"/api/v1/crates/wecom_bot/owner_team","owner_user":"/api/v1/crates/wecom_bot/owner_user","reverse_dependencies":"/api/v1/crates/wecom_bot/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecom-rs","name":"wecom-rs","updated_at":"2022-11-14T03:34:44.126154Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2022-06-29T08:24:35.819874Z","downloads":21198,"recent_downloads":41,"default_version":"0.1.17","num_versions":18,"yanked":false,"max_version":"0.1.17","newest_version":"0.1.17","max_stable_version":"0.1.17","description":"企业微信Rust版SDK","homepage":null,"documentation":null,"repository":"https://github.com/shenshouer/wecom-rs","links":{"version_downloads":"/api/v1/crates/wecom-rs/downloads","versions":"/api/v1/crates/wecom-rs/versions","owners":"/api/v1/crates/wecom-rs/owners","owner_team":"/api/v1/crates/wecom-rs/owner_team","owner_user":"/api/v1/crates/wecom-rs/owner_user","reverse_dependencies":"/api/v1/crates/wecom-rs/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecom-transport","name":"wecom-transport","updated_at":"2026-08-17T03:39:13.381663Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-08-17T03:39:13.381663Z","downloads":19,"recent_downloads":19,"default_version":"1.1.0","num_versions":1,"yanked":false,"max_version":"1.1.0","newest_version":"1.1.0","max_stable_version":"1.1.0","description":"Transport layer for the WeCom CLI: HTTP backend, endpoint catalog and long-task polling","homepage":"https://github.com/WecomTeam/wecom-cli","documentation":null,"repository":"https://github.com/WecomTeam/wecom-cli","links":{"version_downloads":"/api/v1/crates/wecom-transport/downloads","versions":"/api/v1/crates/wecom-transport/versions","owners":"/api/v1/crates/wecom-transport/owners","owner_team":"/api/v1/crates/wecom-transport/owner_team","owner_user":"/api/v1/crates/wecom-transport/owner_user","reverse_dependencies":"/api/v1/crates/wecom-transport/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"diving","name":"diving","updated_at":"2026-10-04T11:25:35.731253Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-06-27T12:06:12.910347Z","downloads":31,"recent_downloads":12,"default_version":"0.11.1","num_versions":2,"yanked":false,"max_version":"0.11.1","newest_version":"0.11.1","max_stable_version":"0.11.1","description":"Analyze Docker image layers to find wasted space, leaked secrets, and bloat — terminal UI + web, no daemon or root required.","homepage":"https://github.com/vicanso/diving-rs","documentation":null,"repository":"https://github.com/vicanso/diving-rs","links":{"version_downloads":"/api/v1/crates/diving/downloads","versions":"/api/v1/crates/diving/versions","owners":"/api/v1/crates/diving/owners","owner_team":"/api/v1/crates/diving/owner_team","owner_user":"/api/v1/crates/diving/owner_user","reverse_dependencies":"/api/v1/crates/diving/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"loveyu-kit","name":"loveyu-kit","updated_at":"2026-07-23T06:47:42.638834Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-07-20T08:07:45.167418Z","downloads":47,"recent_downloads":47,"default_version":"0.2.0","num_versions":2,"yanked":false,"max_version":"0.2.0","newest_version":"0.2.0","max_stable_version":"0.2.0","description":"面向 Axum 项目的可组合 API 工具库","homepage":null,"documentation":"https://docs.rs/loveyu-kit","repository":"https://github.com/loveyu233/simple-api","links":{"version_downloads":"/api/v1/crates/loveyu-kit/downloads","versions":"/api/v1/crates/loveyu-kit/versions","owners":"/api/v1/crates/loveyu-kit/owners","owner_team":"/api/v1/crates/loveyu-kit/owner_team","owner_user":"/api/v1/crates/loveyu-kit/owner_user","reverse_dependencies":"/api/v1/crates/loveyu-kit/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"hi-push","name":"hi-push","updated_at":"2022-10-19T02:21:07.726563Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2022-10-19T02:21:07.726563Z","downloads":1576,"recent_downloads":3,"default_version":"0.0.1","num_versions":1,"yanked":false,"max_version":"0.0.1","newest_version":"0.0.1","max_stable_version":"0.0.1","description":"Third-party push service integration library. Include apns,fcm,xiaomi,huawei,wecom and email.","homepage":null,"documentation":null,"repository":null,"links":{"version_downloads":"/api/v1/crates/hi-push/downloads","versions":"/api/v1/crates/hi-push/versions","owners":"/api/v1/crates/hi-push/owners","owner_team":"/api/v1/crates/hi-push/owner_team","owner_user":"/api/v1/crates/hi-push/owner_user","reverse_dependencies":"/api/v1/crates/hi-push/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"pingap-webhook","name":"pingap-webhook","updated_at":"2026-10-01T02:18:28.122982Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2025-05-11T01:32:10.985830Z","downloads":1340,"recent_downloads":63,"default_version":"0.15.0","num_versions":5,"yanked":false,"max_version":"0.15.0","newest_version":"0.15.0","max_stable_version":"0.15.0","description":"Webhook for pingap","homepage":"https://github.com/vicanso/pingap","documentation":null,"repository":"https://github.com/vicanso/pingap","links":{"version_downloads":"/api/v1/crates/pingap-webhook/downloads","versions":"/api/v1/crates/pingap-webhook/versions","owners":"/api/v1/crates/pingap-webhook/owners","owner_team":"/api/v1/crates/pingap-webhook/owner_team","owner_user":"/api/v1/crates/pingap-webhook/owner_user","reverse_dependencies":"/api/v1/crates/pingap-webhook/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wechat_work_crypto","name":"wechat_work_crypto","updated_at":"2026-01-23T16:37:46.981820Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-01-23T16:37:46.981820Z","downloads":34,"recent_downloads":5,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"Pure Rust implementation of WeChat Work (企业微信) message encryption/decryption library","homepage":"https://gitcode.net/dnrops/wechat_work_crypto","documentation":"https://docs.rs/wechat_work_crypto","repository":"https://gitcode.net/dnrops/wechat_work_crypto","links":{"version_downloads":"/api/v1/crates/wechat_work_crypto/downloads","versions":"/api/v1/crates/wechat_work_crypto/versions","owners":"/api/v1/crates/wechat_work_crypto/owners","owner_team":"/api/v1/crates/wechat_work_crypto/owner_team","owner_user":"/api/v1/crates/wechat_work_crypto/owner_user","reverse_dependencies":"/api/v1/crates/wechat_work_crypto/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wechat-crypto","name":"wechat-crypto","updated_at":"2023-05-12T09:39:50.683560Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2023-05-11T15:10:00.192504Z","downloads":4213,"recent_downloads":19,"default_version":"0.1.2","num_versions":3,"yanked":false,"max_version":"0.1.2","newest_version":"0.1.2","max_stable_version":"0.1.2","description":"微信开发者API数据加解密工具 Wechat / Wecom encrypt / decrypt ","homepage":null,"documentation":"https://docs.rs/wechat-crypto","repository":"https://github.com/hjin-me/wechat-proxy","links":{"version_downloads":"/api/v1/crates/wechat-crypto/downloads","versions":"/api/v1/crates/wechat-crypto/versions","owners":"/api/v1/crates/wechat-crypto/owners","owner_team":"/api/v1/crates/wechat-crypto/owner_team","owner_user":"/api/v1/crates/wechat-crypto/owner_user","reverse_dependencies":"/api/v1/crates/wechat-crypto/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecomx-transport","name":"wecomx-transport","updated_at":"2026-08-30T02:02:43.032848Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-08-30T02:02:43.032848Z","downloads":127,"recent_downloads":127,"default_version":"1.2.0","num_versions":1,"yanked":false,"max_version":"1.2.0","newest_version":"1.2.0","max_stable_version":"1.2.0","description":"Transport layer for the WeCom CLI: HTTP backend, endpoint catalog and long-task polling","homepage":"https://github.com/WecomTeam/wecom-cli","documentation":null,"repository":"https://github.com/WecomTeam/wecom-cli","links":{"version_downloads":"/api/v1/crates/wecomx-transport/downloads","versions":"/api/v1/crates/wecomx-transport/versions","owners":"/api/v1/crates/wecomx-transport/owners","owner_team":"/api/v1/crates/wecomx-transport/owner_team","owner_user":"/api/v1/crates/wecomx-transport/owner_user","reverse_dependencies":"/api/v1/crates/wecomx-transport/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecomx","name":"wecomx","updated_at":"2026-08-30T02:04:36.274505Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-08-30T02:04:36.274505Z","downloads":99,"recent_downloads":99,"default_version":"1.2.0","num_versions":1,"yanked":false,"max_version":"1.2.0","newest_version":"1.2.0","max_stable_version":"1.2.0","description":"Core library behind the WeCom CLI: schema-driven command tree, discovery and directives","homepage":"https://github.com/WecomTeam/wecom-cli","documentation":null,"repository":"https://github.com/WecomTeam/wecom-cli","links":{"version_downloads":"/api/v1/crates/wecomx/downloads","versions":"/api/v1/crates/wecomx/versions","owners":"/api/v1/crates/wecomx/owners","owner_team":"/api/v1/crates/wecomx/owner_team","owner_user":"/api/v1/crates/wecomx/owner_user","reverse_dependencies":"/api/v1/crates/wecomx/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecomx-auth","name":"wecomx-auth","updated_at":"2026-08-30T02:04:50.348959Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-08-30T02:04:50.348959Z","downloads":99,"recent_downloads":99,"default_version":"1.2.0","num_versions":1,"yanked":false,"max_version":"1.2.0","newest_version":"1.2.0","max_stable_version":"1.2.0","description":"Credential storage and token provider for WeCom authentication","homepage":"https://github.com/WecomTeam/wecom-cli","documentation":null,"repository":"https://github.com/WecomTeam/wecom-cli","links":{"version_downloads":"/api/v1/crates/wecomx-auth/downloads","versions":"/api/v1/crates/wecomx-auth/versions","owners":"/api/v1/crates/wecomx-auth/owners","owner_team":"/api/v1/crates/wecomx-auth/owner_team","owner_user":"/api/v1/crates/wecomx-auth/owner_user","reverse_dependencies":"/api/v1/crates/wecomx-auth/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"wecomx-runtime","name":"wecomx-runtime","updated_at":"2026-08-30T02:05:14.238456Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-08-30T02:05:14.238456Z","downloads":84,"recent_downloads":84,"default_version":"1.2.0","num_versions":1,"yanked":false,"max_version":"1.2.0","newest_version":"1.2.0","max_stable_version":"1.2.0","description":"Authenticated transport and client builder for WeCom","homepage":"https://github.com/WecomTeam/wecom-cli","documentation":null,"repository":"https://github.com/WecomTeam/wecom-cli","links":{"version_downloads":"/api/v1/crates/wecomx-runtime/downloads","versions":"/api/v1/crates/wecomx-runtime/versions","owners":"/api/v1/crates/wecomx-runtime/owners","owner_team":"/api/v1/crates/wecomx-runtime/owner_team","owner_user":"/api/v1/crates/wecomx-runtime/owner_user","reverse_dependencies":"/api/v1/crates/wecomx-runtime/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"xbot","name":"xbot","updated_at":"2026-07-14T15:23:50.720907Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-05-09T05:01:14.117924Z","downloads":187,"recent_downloads":44,"default_version":"0.1.9","num_versions":9,"yanked":false,"max_version":"0.1.9","newest_version":"0.1.9","max_stable_version":"0.1.9","description":"Rust-native autonomous bot runtime for chat automation, tools, scheduled work, and multi-channel delivery","homepage":"https://github.com/guoqingbao/xbot#readme","documentation":null,"repository":"https://github.com/guoqingbao/xbot","links":{"version_downloads":"/api/v1/crates/xbot/downloads","versions":"/api/v1/crates/xbot/versions","owners":"/api/v1/crates/xbot/owners","owner_team":"/api/v1/crates/xbot/owner_team","owner_user":"/api/v1/crates/xbot/owner_user","reverse_dependencies":"/api/v1/crates/xbot/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"hi-auth","name":"hi-auth","updated_at":"2023-02-23T01:45:00.926578Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2023-02-08T18:32:47.310327Z","downloads":5022,"recent_downloads":12,"default_version":"0.0.4","num_versions":4,"yanked":false,"max_version":"0.0.4","newest_version":"0.0.4","max_stable_version":"0.0.4","description":"Social account user info libary, include Github,Wechat and Google.","homepage":null,"documentation":null,"repository":"https://github.com/hikit-io/hikit-rs/tree/main/hi-auth","links":{"version_downloads":"/api/v1/crates/hi-auth/downloads","versions":"/api/v1/crates/hi-auth/versions","owners":"/api/v1/crates/hi-auth/owners","owner_team":"/api/v1/crates/hi-auth/owner_team","owner_user":"/api/v1/crates/hi-auth/owner_user","reverse_dependencies":"/api/v1/crates/hi-auth/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"jsonrepair-rs","name":"jsonrepair-rs","updated_at":"2026-09-26T15:18:18.890750Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-24T10:33:25.764936Z","downloads":82285,"recent_downloads":79381,"default_version":"0.2.5","num_versions":8,"yanked":false,"max_version":"0.2.5","newest_version":"0.2.5","max_stable_version":"0.2.5","description":"Repair broken JSON — fix quotes, commas, comments, trailing content, and 30+ other issues","homepage":null,"documentation":null,"repository":"https://github.com/majiayu000/jsonrepair-rs","links":{"version_downloads":"/api/v1/crates/jsonrepair-rs/downloads","versions":"/api/v1/crates/jsonrepair-rs/versions","owners":"/api/v1/crates/jsonrepair-rs/owners","owner_team":"/api/v1/crates/jsonrepair-rs/owner_team","owner_user":"/api/v1/crates/jsonrepair-rs/owner_user","reverse_dependencies":"/api/v1/crates/jsonrepair-rs/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"sparrow-cli","name":"sparrow-cli","updated_at":"2026-06-16T15:13:40.884207Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-06-05T22:37:37.189754Z","downloads":431,"recent_downloads":73,"default_version":"0.10.3","num_versions":24,"yanked":false,"max_version":"0.10.3","newest_version":"0.10.3","max_stable_version":"0.10.3","description":"A local-first Rust agent cockpit — route, run, replay, rewind","homepage":null,"documentation":null,"repository":"https://github.com/ucav/Sparrow","links":{"version_downloads":"/api/v1/crates/sparrow-cli/downloads","versions":"/api/v1/crates/sparrow-cli/versions","owners":"/api/v1/crates/sparrow-cli/owners","owner_team":"/api/v1/crates/sparrow-cli/owner_team","owner_user":"/api/v1/crates/sparrow-cli/owner_user","reverse_dependencies":"/api/v1/crates/sparrow-cli/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"rsclaw","name":"rsclaw","updated_at":"2026-07-03T02:55:58.250682Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-25T08:29:45.993974Z","downloads":166,"recent_downloads":26,"default_version":"2026.6.26","num_versions":8,"yanked":false,"max_version":"2026.6.26","newest_version":"2026.6.26","max_stable_version":"2026.6.26","description":"AI Agent Engine Compatible with OpenClaw","homepage":null,"documentation":null,"repository":"https://github.com/rsclaw-ai/rsclaw","links":{"version_downloads":"/api/v1/crates/rsclaw/downloads","versions":"/api/v1/crates/rsclaw/versions","owners":"/api/v1/crates/rsclaw/owners","owner_team":"/api/v1/crates/rsclaw/owner_team","owner_user":"/api/v1/crates/rsclaw/owner_user","reverse_dependencies":"/api/v1/crates/rsclaw/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"adaclaw","name":"adaclaw","updated_at":"2026-03-01T10:26:53.580284Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2026-03-01T10:26:53.580284Z","downloads":35,"recent_downloads":8,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"Lightweight, secure, multi-channel Rust AI Agent Runtime","homepage":null,"documentation":null,"repository":"https://github.com/worldflat21-lang/AdaClaw","links":{"version_downloads":"/api/v1/crates/adaclaw/downloads","versions":"/api/v1/crates/adaclaw/versions","owners":"/api/v1/crates/adaclaw/owners","owner_team":"/api/v1/crates/adaclaw/owner_team","owner_user":"/api/v1/crates/adaclaw/owner_user","reverse_dependencies":"/api/v1/crates/adaclaw/reverse_dependencies"},"exact_match":false,"trustpub_only":false},{"id":"spacex_sdk","name":"spacex_sdk","updated_at":"2022-12-07T08:08:02.810002Z","versions":null,"keywords":null,"categories":null,"badges":[],"created_at":"2022-12-07T08:08:02.810002Z","downloads":1524,"recent_downloads":2,"default_version":"0.1.0","num_versions":1,"yanked":false,"max_version":"0.1.0","newest_version":"0.1.0","max_stable_version":"0.1.0","description":"spacex sdk","homepage":null,"documentation":"https://docs.rs/spacex_sdk","repository":"https://github.com/jiawei397/spacex_sdk","links":{"version_downloads":"/api/v1/crates/spacex_sdk/downloads","versions":"/api/v1/crates/spacex_sdk/versions","owners":"/api/v1/crates/spacex_sdk/owners","owner_team":"/api/v1/crates/spacex_sdk/owner_team","owner_user":"/api/v1/crates/spacex_sdk/owner_user","reverse_dependencies":"/api/v1/crates/spacex_sdk/reverse_dependencies"},"exact_match":false,"trustpub_only":false}],"meta":{"total":28,"next_page":null,"prev_page":null}} \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/dingtalk-stream-readme.md b/docs/02-架构设计/专项设计/.research/dingtalk-stream-readme.md new file mode 100644 index 0000000..76f48c5 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/dingtalk-stream-readme.md @@ -0,0 +1,168 @@ +# DingTalk Stream SDK for Rust + +[![Crates.io](https://img.shields.io/crates/v/dingtalk-stream.svg)](https://crates.io/crates/dingtalk-stream) +[![docs.rs](https://docs.rs/dingtalk-stream/badge.svg)](https://docs.rs/dingtalk-stream) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Rust](https://img.shields.io/badge/rust-%3E%3D1.85.0-orange.svg)](https://www.rust-lang.org) + +钉钉 Stream 模式 SDK 的 Rust 实现,完整对齐 Python 版 [dingtalk-stream-sdk-python](https://github.com/open-dingtalk/dingtalk-stream-sdk-python)。 + +> 对齐的 Python SDK 版本: commit [`6381a7b`](https://github.com/open-dingtalk/dingtalk-stream-sdk-python/commit/6381a7bacb0353e1e0cd425688a02ff785fda44c) + +## 功能特性 + +- 基于 WebSocket 的钉钉 Stream 协议连接(自动重连、60s Keepalive) +- 聊天机器人消息收发(文本、Markdown、卡片) +- 交互式卡片(Markdown / AI 流式 / 按钮 / 轮播 / RPA 插件) +- Graph API 支持 +- 事件订阅 +- OAuth2 Token 自动管理(带缓存与 5 分钟提前刷新) +- 统一 async/await(基于 tokio) + +## 快速开始 + +将依赖添加到 `Cargo.toml`: + +```toml +[dependencies] +dingtalk-stream = "0.1" +``` + +最小示例: + +```rust +use async_trait::async_trait; +use dingtalk_stream::*; + +struct MyHandler; + +#[async_trait] +impl CallbackHandler for MyHandler { + async fn process(&self, message: &messages::frames::MessageBody) -> (u16, String) { + println!("Received: {}", message.data); + (AckMessage::STATUS_OK, "OK".to_owned()) + } +} + +fn main() { + let credential = Credential::new("your_client_id", "your_client_secret"); + let handler = MyHandler; + + let mut client = DingTalkStreamClient::builder(credential) + .register_callback_handler(ChatbotMessage::TOPIC, handler) + .build(); + + client.start_forever().unwrap(); +} +``` + +更多示例见 [`examples/`](examples/) 目录。 + +## 项目结构 + +``` +src/ +├── lib.rs # 公共 API 导出 +├── client.rs # DingTalkStreamClient + Builder +├── credential.rs # Credential(Debug 遮蔽 secret) +├── error.rs # 统一错误类型 +├── handlers/ # Handler traits (Callback/Event/System/Chatbot/Graph) +├── messages/ # 消息类型 (Frames/Chatbot/CardCallback/Graph) +├── card/ # 卡片系统 (Replier/Instances/Templates) +└── transport/ # 传输层 (HTTP/Token/WebSocket) +``` + +## 文档 + +- [整体架构图](docs/architecture.md) +- [连接生命周期时序图](docs/connection-lifecycle.md) +- [卡片操作时序图](docs/card-operations.md) + +## 端到端测试 + +1. 复制配置模板并填入凭证: + ```bash + cp examples/secret.example.toml examples/secret.toml + # 编辑 examples/secret.toml,填入 client_id 和 client_secret + ``` + +2. 运行测试机器人: + ```bash + cargo run --example e2e_test + ``` + +3. 带媒体下载测试: + ```bash + cargo run --example e2e_test -- --download-dir /tmp/dingtalk_downloads + ``` + +4. 通过钉钉向机器人发送消息测试: + - `ping` → 回复 pong + - `echo ` → 回声 + - `card` → 发送 Markdown 卡片 + - `info` → 显示消息详情 + +## 与 Python SDK 的设计差异 + +| Python | Rust | 说明 | +|---|---|---| +| sync + async 双版本 | 统一 async | 消除代码重复 | +| 类继承 | Trait + 组合 | Rust 惯用模式 | +| ThreadPoolExecutor | tokio::spawn | 更高效的异步调度 | +| dict 动态类型 | 强类型 struct + serde | 编译期类型安全 | + +## Rust SDK 独有功能 + +以下功能为 Rust SDK 独有,Python SDK 中不存在。同步 Python SDK 时请勿删除(代码中均有 `Rust SDK exclusive` 标注)。 + +### 扩展消息类型 + +Rust SDK 支持解析钉钉机器人可接收的全部 6 种消息类型,Python SDK 仅支持 text / richText / picture: + +| 消息类型 | 结构体 | 场景限制 | +|---------|--------|---------| +| audio 语音 | `AudioContent` | 仅单聊 | +| file 文件 | `FileContent` | 仅单聊 | +| video 视频 | `VideoContent` | 仅单聊 | + +```rust +let msg = ChatbotMessage::from_value(&json)?; +if let Some(fc) = &msg.file_content { + println!("文件名: {:?}, 下载码: {:?}", fc.file_name, fc.download_code); +} +``` + +### 统一下载码提取 + +`get_all_download_codes()` 一次性获取消息中所有媒体的下载码,覆盖 picture / richText 图片 / audio / file / video: + +```rust +for (media_type, download_code) in msg.get_all_download_codes() { + let url = replier.get_image_download_url(&download_code).await?; + let bytes = replier.download_bytes(&url).await?; +} +``` + +### 文件下载 + +| 方法 | 说明 | +|------|------| +| `download_bytes(url)` | 下载文件字节内容 | +| `download_bytes_with_limit(url, max_size)` | 带大小限制的流式下载(Content-Length 预检 + 累计检查) | + +### 主动单聊消息 + +`send_oto_message()` 通过 OpenAPI 向指定用户发送单聊消息(对应 `POST /v1.0/robot/oToMessages/batchSend`): + +```rust +replier.send_oto_message(user_id, "sampleText", r#"{"content":"hello"}"#).await?; +``` + +## 环境要求 + +- Rust ≥ 1.85.0(edition 2024) +- tokio 异步运行时 + +## License + +MIT diff --git a/docs/02-架构设计/专项设计/.research/feishu-sdk-readme.md b/docs/02-架构设计/专项设计/.research/feishu-sdk-readme.md new file mode 100644 index 0000000..f650644 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/feishu-sdk-readme.md @@ -0,0 +1,285 @@ +# Feishu SDK Rust + +[![Crates.io](https://img.shields.io/crates/v/feishu-sdk.svg)](https://crates.io/crates/feishu-sdk) +[![Documentation](https://docs.rs/feishu-sdk/badge.svg)](https://docs.rs/feishu-sdk) +[![License](https://img.shields.io/crates/l/feishu-sdk.svg)](https://github.com/your-repo/feishu-sdk-rust) + +> Notice: This is a community-maintained crate and is not an official Feishu/Lark SDK. + +Rust SDK for Feishu/Lark Open Platform API. Provides a complete, type-safe interface to interact with Feishu APIs. + +## Features + +- **Complete API Coverage**: 1448+ operations from Feishu Open Platform +- **Token Management**: Automatic app_access_token and tenant_access_token handling +- **Event Processing**: Event subscription and card callback handling +- **WebSocket Support**: Long connection for real-time messaging +- **HTTP Server**: Built-in Salvo server for webhook handling +- **Type Safety**: Generated operation constants and typed APIs +- **Async/Await**: Full async support with tokio runtime +- **Pluggable Components**: Customizable cache, logger, serializer, and HTTP client +- **Code Generation**: Operation wrappers + typed model stubs from endpoint catalog + +## Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +feishu-sdk = "0.1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +``` + +### Optional Features + +```toml +[dependencies] +feishu-sdk = { version = "0.1", features = ["server", "websocket"] } +``` + +- `server`: Enable HTTP server for event handling (Salvo) +- `websocket`: Enable WebSocket client for long connections + +## Quick Start + +### Basic Usage + +```rust +use feishu_sdk::core::{Config, FEISHU_BASE_URL}; +use feishu_sdk::Client; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let config = Config::builder("app_id", "app_secret") + .base_url(FEISHU_BASE_URL) + .build(); + + let client = Client::new(config)?; + + // Get chat list + let resp = client + .operation("im.v1.chat.list") + .query_param("page_size", "20") + .send() + .await?; + + println!("Status: {}", resp.status); + println!("Body: {}", String::from_utf8_lossy(&resp.body)); + + Ok(()) +} +``` + +### Send Message + +```rust +use feishu_sdk::core::Config; +use feishu_sdk::Client; + +async fn send_message(client: &Client, chat_id: &str) -> Result<(), Box> { + let message = serde_json::json!({ + "receive_id": chat_id, + "msg_type": "text", + "content": r#"{"text":"Hello!"}"# + }); + + let resp = client + .operation("im.v1.messages.create") + .path_param("receive_id_type", "chat_id") + .body_json(&message)? + .send() + .await?; + + println!("Message sent: {:?}", resp.json_value()?); + Ok(()) +} +``` + +### Event Handling Server + +```rust +use feishu_sdk::core::{noop_logger, Config}; +use feishu_sdk::event::{EventDispatcher, EventDispatcherConfig}; +use feishu_sdk::server::FeishuServer; + +#[tokio::main] +async fn main() { + let event_config = EventDispatcherConfig::new() + .verification_token("your_token"); + + let dispatcher = EventDispatcher::new(event_config, noop_logger()); + + // Register event handlers + dispatcher.on_event("im.message.receive_v1", |event| { + Box::pin(async move { + println!("Received message: {:?}", event); + Ok(None) + }) + }); + + // Start server + let server = FeishuServer::new(dispatcher).port(8080); + server.run().await; +} +``` + +### WebSocket Client + +```rust +use feishu_sdk::core::noop_logger; +use feishu_sdk::ws::{WebSocketClient, WsConfig}; +use std::time::Duration; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let config = WsConfig::new("wss://ws.feishu.cn/...") + .heartbeat_interval(Duration::from_secs(30)) + .reconnect_delay(Duration::from_secs(5)); + + let client = WebSocketClient::new(config, noop_logger()).await?; + + // Check connection status + if client.is_connected().await { + println!("Connected to WebSocket"); + } + + Ok(()) +} +``` + +## Configuration + +### Basic Configuration + +```rust +use feishu_sdk::core::{Config, LogLevel, FEISHU_BASE_URL, LARK_BASE_URL}; +use std::time::Duration; + +let config = Config::builder("app_id", "app_secret") + .base_url(FEISHU_BASE_URL) // or LARK_BASE_URL for international + .log_level(LogLevel::Debug) // Set log level + .request_timeout(Duration::from_secs(30)) // Request timeout + .enable_token_cache(true) // Enable token caching + .build(); +``` + +### Marketplace App Configuration + +```rust +let config = Config::builder("app_id", "app_secret") + .marketplace_app() // Enable marketplace mode + .app_ticket("your_app_ticket") // Required for marketplace apps + .build(); +``` + +### Helpdesk Configuration + +```rust +let config = Config::builder("app_id", "app_secret") + .helpdesk("helpdesk_id", "helpdesk_token") + .build(); +``` + +### Client Options + +```rust +use feishu_sdk::client::{ClientBuilder, with_http_client, with_log_level, with_request_timeout, with_serializer}; +use feishu_sdk::core::{Config, LogLevel}; + +let client = ClientBuilder::new(Config::builder("app_id", "app_secret").build()) + .with(with_log_level(LogLevel::Debug)) + .with(with_request_timeout(Duration::from_secs(60))) + .with(with_http_client(custom_http_client)) + .with(with_serializer(custom_serializer)) + .build()?; +``` + +## API Reference + +### Operation IDs + +Operations use the format: `{service}.{version}.{resource}.{method}` + +Examples: +- `im.v1.messages.create` - Send message +- `im.v1.chat.list` - List chats +- `contact.v3.user.get` - Get user info +- `auth.v3.tenant_access_token.internal.post` - Get tenant token + +### Typed API Wrappers + +```rust +use feishu_sdk::api::all_services; + +// Get chat info +let builder = all_services::im::v1::chat::get(&client, "oc_xxx") + .query_param("user_id_type", "open_id"); + +// List users +let builder = all_services::contact::v3::user::list(&client) + .query_param("department_id", "0") + .query_param("page_size", "50"); +``` + +## Examples + +See the `examples/` directory: + +- `get_token.rs` - Get tenant access token +- `send_message.rs` - Send a message +- `get_user.rs` - Get user information +- `im_v1_message.rs` - IM message operations +- `drive_v1_file.rs` - Drive file operations +- `calendar_v4.rs` - Calendar operations +- `docx_v1.rs` - Docx document operations +- `sheets_v3.rs` - Sheets operations +- `bitable_v1.rs` - Bitable operations +- `approval_v4.rs` - Approval operations +- `event_server.rs` - Event handling server +- `card_handler.rs` - Card callback server +- `websocket_client.rs` - WebSocket client +- `full_app.rs` - End-to-end app example (client + event + card) + +Run examples: + +```bash +# Set environment variables +export FEISHU_APP_ID=your_app_id +export FEISHU_APP_SECRET=your_app_secret + +# Run example +cargo run --example get_token + +# Run server example (requires server feature) +cargo run --features server --example event_server + +# Run websocket example (requires websocket feature) +cargo run --features websocket --example websocket_client +``` + +## Documentation + +- Usage guide: [`docs/USAGE.md`](docs/USAGE.md) +- FAQ: [`docs/FAQ.md`](docs/FAQ.md) +- Migration guide: [`docs/MIGRATION.md`](docs/MIGRATION.md) +- Best practices: [`docs/BEST_PRACTICES.md`](docs/BEST_PRACTICES.md) + +## Error Handling + +```rust +use feishu_sdk::core::Error; + +match client.operation("im.v1.messages.create").send().await { + Ok(resp) => println!("Success: {:?}", resp.json_value()?), + Err(Error::Api(api_error)) => { + println!("API Error: code={}, msg={}", api_error.code, api_error.msg); + } + Err(Error::RequestTimeout(_)) => println!("Request timed out"), + Err(e) => println!("Error: {}", e), +} +``` + +## License + +MIT License + diff --git a/docs/02-架构设计/专项设计/.research/fetch_larksdk.ps1 b/docs/02-架构设计/专项设计/.research/fetch_larksdk.ps1 new file mode 100644 index 0000000..da67c32 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/fetch_larksdk.ps1 @@ -0,0 +1,10 @@ +$ErrorActionPreference = 'Stop' +$zip = "$env:TEMP\larksdk.zip" +$dir = "$env:TEMP\larksdk" +curl.exe -sL "https://goproxy.cn/github.com/larksuite/oapi-sdk-go/v3/@v/v3.12.0.zip" -o $zip +if (Test-Path $dir) { Remove-Item -Recurse -Force $dir } +Expand-Archive -Path $zip -DestinationPath $dir -Force +Write-Host "=== ws 相关文件 ===" +Get-ChildItem -Recurse $dir -Filter *.go | Where-Object { $_.FullName -match '\\ws\\|ws\\.go|longconn' } | ForEach-Object { $_.FullName.Replace($dir, '') } +Write-Host "`n=== 顶层目录 ===" +Get-ChildItem $dir | ForEach-Object { Get-ChildItem $_.FullName -Directory | Select-Object -ExpandProperty Name } | Select-Object -Unique diff --git a/docs/02-架构设计/专项设计/.research/filter-tree.ps1 b/docs/02-架构设计/专项设计/.research/filter-tree.ps1 new file mode 100644 index 0000000..91a672e --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/filter-tree.ps1 @@ -0,0 +1,3 @@ +# 从 larkrs-tree.json 提取关键源文件路径(ws/card/event/channel) +$j = Get-Content 'E:\wk-lab\devflow\docs\02-架构设计\专项设计\.research\larkrs-tree.json' -Raw | ConvertFrom-Json +$j.tree | Where-Object { $_.path -match '(ws|card|event|channel)' -and $_.path -match '\.rs$' } | ForEach-Object { $_.path } diff --git a/docs/02-架构设计/专项设计/.research/issue-130-comments.json b/docs/02-架构设计/专项设计/.research/issue-130-comments.json new file mode 100644 index 0000000..7eab017 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/issue-130-comments.json @@ -0,0 +1 @@ +[{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2014454577","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2014454577","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2014454577,"node_id":"IC_kwDOEiHOJs54EiMx","user":{"login":"maemual","id":2352843,"node_id":"MDQ6VXNlcjIzNTI4NDM=","avatar_url":"https://avatars.githubusercontent.com/u/2352843?v=4","gravatar_id":"","url":"https://api.github.com/users/maemual","html_url":"https://github.com/maemual","followers_url":"https://api.github.com/users/maemual/followers","following_url":"https://api.github.com/users/maemual/following{/other_user}","gists_url":"https://api.github.com/users/maemual/gists{/gist_id}","starred_url":"https://api.github.com/users/maemual/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maemual/subscriptions","organizations_url":"https://api.github.com/users/maemual/orgs","repos_url":"https://api.github.com/users/maemual/repos","events_url":"https://api.github.com/users/maemual/events{/privacy}","received_events_url":"https://api.github.com/users/maemual/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2024-03-22T06:42:29Z","updated_at":"2024-03-22T06:42:53Z","body":"近期卡片新特性较多,人力比较紧张,还在推进中。\r\n可以继续使用旧版的卡片回传交互使用。","author_association":"COLLABORATOR","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2014454577/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2181853241","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2181853241","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2181853241,"node_id":"IC_kwDOEiHOJs6CDHA5","user":{"login":"Martin2877","id":26109420,"node_id":"MDQ6VXNlcjI2MTA5NDIw","avatar_url":"https://avatars.githubusercontent.com/u/26109420?v=4","gravatar_id":"","url":"https://api.github.com/users/Martin2877","html_url":"https://github.com/Martin2877","followers_url":"https://api.github.com/users/Martin2877/followers","following_url":"https://api.github.com/users/Martin2877/following{/other_user}","gists_url":"https://api.github.com/users/Martin2877/gists{/gist_id}","starred_url":"https://api.github.com/users/Martin2877/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Martin2877/subscriptions","organizations_url":"https://api.github.com/users/Martin2877/orgs","repos_url":"https://api.github.com/users/Martin2877/repos","events_url":"https://api.github.com/users/Martin2877/events{/privacy}","received_events_url":"https://api.github.com/users/Martin2877/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2024-06-21T02:13:35Z","updated_at":"2024-06-21T02:13:53Z","body":"\r\n卡片回调一直报这个错误 `json: cannot unmarshal string into Go struct field CardAction.Header of type []string]`","author_association":"NONE","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2181853241/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2260564811","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2260564811","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2260564811,"node_id":"IC_kwDOEiHOJs6GvXtL","user":{"login":"sbaby1997","id":169505350,"node_id":"U_kgDOChpyRg","avatar_url":"https://avatars.githubusercontent.com/u/169505350?v=4","gravatar_id":"","url":"https://api.github.com/users/sbaby1997","html_url":"https://github.com/sbaby1997","followers_url":"https://api.github.com/users/sbaby1997/followers","following_url":"https://api.github.com/users/sbaby1997/following{/other_user}","gists_url":"https://api.github.com/users/sbaby1997/gists{/gist_id}","starred_url":"https://api.github.com/users/sbaby1997/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sbaby1997/subscriptions","organizations_url":"https://api.github.com/users/sbaby1997/orgs","repos_url":"https://api.github.com/users/sbaby1997/repos","events_url":"https://api.github.com/users/sbaby1997/events{/privacy}","received_events_url":"https://api.github.com/users/sbaby1997/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2024-07-31T13:45:20Z","updated_at":"2024-07-31T13:45:20Z","body":"这个问题非常影响使用呀!","author_association":"NONE","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2260564811/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2260581938","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2260581938","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2260581938,"node_id":"IC_kwDOEiHOJs6Gvb4y","user":{"login":"maemual","id":2352843,"node_id":"MDQ6VXNlcjIzNTI4NDM=","avatar_url":"https://avatars.githubusercontent.com/u/2352843?v=4","gravatar_id":"","url":"https://api.github.com/users/maemual","html_url":"https://github.com/maemual","followers_url":"https://api.github.com/users/maemual/followers","following_url":"https://api.github.com/users/maemual/following{/other_user}","gists_url":"https://api.github.com/users/maemual/gists{/gist_id}","starred_url":"https://api.github.com/users/maemual/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maemual/subscriptions","organizations_url":"https://api.github.com/users/maemual/orgs","repos_url":"https://api.github.com/users/maemual/repos","events_url":"https://api.github.com/users/maemual/events{/privacy}","received_events_url":"https://api.github.com/users/maemual/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2024-07-31T13:53:03Z","updated_at":"2024-07-31T13:53:03Z","body":"@sbaby1997 升级最新版 v3.3.0,然后卡片回传交互选“卡片回传交互(旧)”","author_association":"COLLABORATOR","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2260581938/reactions","total_count":1,"+1":1,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2264346828","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2264346828","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2264346828,"node_id":"IC_kwDOEiHOJs6G9zDM","user":{"login":"Martin2877","id":26109420,"node_id":"MDQ6VXNlcjI2MTA5NDIw","avatar_url":"https://avatars.githubusercontent.com/u/26109420?v=4","gravatar_id":"","url":"https://api.github.com/users/Martin2877","html_url":"https://github.com/Martin2877","followers_url":"https://api.github.com/users/Martin2877/followers","following_url":"https://api.github.com/users/Martin2877/following{/other_user}","gists_url":"https://api.github.com/users/Martin2877/gists{/gist_id}","starred_url":"https://api.github.com/users/Martin2877/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Martin2877/subscriptions","organizations_url":"https://api.github.com/users/Martin2877/orgs","repos_url":"https://api.github.com/users/Martin2877/repos","events_url":"https://api.github.com/users/Martin2877/events{/privacy}","received_events_url":"https://api.github.com/users/Martin2877/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2024-08-02T01:54:18Z","updated_at":"2024-08-02T01:55:12Z","body":"All right. 鉴于官方还没修,我这里提出一个临时解决方法,大家可以参考:\r\n\r\n在 `package larkcard` 文件中\r\n\r\n1. 修改 `model.go` 中的 `CardAction`\r\n\r\n```go\r\ntype CardAction struct {\r\n\t*larkevent.EventReq\r\n\tSchema string `json:\"schema\"`\r\n\tHeader struct {\r\n\t\tEventID string `json:\"event_id\"`\r\n\t\tToken string `json:\"token\"`\r\n\t\tCreateTime string `json:\"create_time\"`\r\n\t\tEventType string `json:\"event_type\"`\r\n\t\tTenantKey string `json:\"tenant_key\"`\r\n\t\tAppID string `json:\"app_id\"`\r\n\t} `json:\"header\"`\r\n\r\n\tChallenge string `json:\"challenge\"`\r\n\tToken string `json:\"token\"`\r\n\tType string `json:\"type\"`\r\n\r\n\tEvent struct {\r\n\t\tOperator struct {\r\n\t\t\tTenantKey string `json:\"tenant_key\"`\r\n\t\t\tUserID string `json:\"user_id\"`\r\n\t\t\tOpenID string `json:\"open_id\"`\r\n\t\t\tUnionID string `json:\"union_id\"`\r\n\t\t} `json:\"operator\"`\r\n\t\tToken string `json:\"token\"`\r\n\t\tAction struct {\r\n\t\t\tTag string `json:\"tag\"`\r\n\t\t\tTimezone string `json:\"timezone\"`\r\n\t\t\tFormValue map[string]string `json:\"form_value\"`\r\n\t\t\tName string `json:\"name\"`\r\n\t\t} `json:\"action\"`\r\n\t\tHost string `json:\"host\"`\r\n\t\tContext struct {\r\n\t\t\tOpenMessageID string `json:\"open_message_id\"`\r\n\t\t\tOpenChatID string `json:\"open_chat_id\"`\r\n\t\t} `json:\"context\"`\r\n\t} `json:\"event\"`\r\n}\r\n```\r\n2. 修改`card.go`中的 `VerifySign`方法,使用 eventEncryptKey\r\n\r\n```go\r\n\tif h.eventEncryptKey == \"\" {\r\n return nil\r\n }\r\n\r\n...\r\n\r\n targetSign := Signature(requestTimestamp, requestNonce,h.eventEncryptKey,string(req.Body))\r\n```\r\n\r\n3. 修改`card.go`中的 `Signature` 方法,使用 sha256加密\r\n```go\r\n\th := sha256.New()\r\n```","author_association":"NONE","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2264346828/reactions","total_count":2,"+1":2,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2350798023","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2350798023","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2350798023,"node_id":"IC_kwDOEiHOJs6MHlTH","user":{"login":"crazykun","id":5958797,"node_id":"MDQ6VXNlcjU5NTg3OTc=","avatar_url":"https://avatars.githubusercontent.com/u/5958797?v=4","gravatar_id":"","url":"https://api.github.com/users/crazykun","html_url":"https://github.com/crazykun","followers_url":"https://api.github.com/users/crazykun/followers","following_url":"https://api.github.com/users/crazykun/following{/other_user}","gists_url":"https://api.github.com/users/crazykun/gists{/gist_id}","starred_url":"https://api.github.com/users/crazykun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crazykun/subscriptions","organizations_url":"https://api.github.com/users/crazykun/orgs","repos_url":"https://api.github.com/users/crazykun/repos","events_url":"https://api.github.com/users/crazykun/events{/privacy}","received_events_url":"https://api.github.com/users/crazykun/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2024-09-14T02:43:54Z","updated_at":"2024-09-14T02:43:54Z","body":"新的改成event事件承载了\r\n[v3代码示例](https://github.com/larksuite/oapi-sdk-go/blob/v3_main/sample/event/event.go#L171)\r\n```\r\n.OnP2CardActionTrigger(func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerReponse, error) {\r\n\t\tfmt.Println(\"receive card action\")\r\n\t\tfmt.Println(larkcore.Prettify(event))\r\n\t\treturn nil, nil\r\n\t})\r\n```","author_association":"NONE","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2350798023/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2609615802","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2609615802","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2609615802,"node_id":"IC_kwDOEiHOJs6bi5O6","user":{"login":"kj648","id":15335697,"node_id":"MDQ6VXNlcjE1MzM1Njk3","avatar_url":"https://avatars.githubusercontent.com/u/15335697?v=4","gravatar_id":"","url":"https://api.github.com/users/kj648","html_url":"https://github.com/kj648","followers_url":"https://api.github.com/users/kj648/followers","following_url":"https://api.github.com/users/kj648/following{/other_user}","gists_url":"https://api.github.com/users/kj648/gists{/gist_id}","starred_url":"https://api.github.com/users/kj648/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kj648/subscriptions","organizations_url":"https://api.github.com/users/kj648/orgs","repos_url":"https://api.github.com/users/kj648/repos","events_url":"https://api.github.com/users/kj648/events{/privacy}","received_events_url":"https://api.github.com/users/kj648/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2025-01-23T11:52:59Z","updated_at":"2025-01-23T11:55:20Z","body":"对于卡片消息回调的 Value 应该定义成interface{} . 至少目前的卡片消息回调, 回调事件的值 支持 字符串,如果用字符串传进来 就直接cannot unmarshal string into Go struct XXXXXXX 。为了不出发这个error 只能强制使用 对象结构作为value\nevent *callback.CardActionTriggerEvent\n```go\ntype CallBackAction struct {\n\tValue map[string]interface{} `json:\"value\"`\n\tTag string `json:\"tag\"`\n\tOption string `json:\"option\"`\n\tTimezone string `json:\"timezone\"`\n\tName string `json:\"name\"`\n\tFormValue map[string]interface{} `json:\"form_value\"`\n\tInputValue string `json:\"input_value\"`\n\tOptions []string `json:\"options\"`\n\tChecked bool `json:\"checked\"`\n}\n```","author_association":"NONE","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2609615802/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2724196179","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2724196179","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2724196179,"node_id":"IC_kwDOEiHOJs6iX-9T","user":{"login":"xiaozhi747","id":19251872,"node_id":"MDQ6VXNlcjE5MjUxODcy","avatar_url":"https://avatars.githubusercontent.com/u/19251872?v=4","gravatar_id":"","url":"https://api.github.com/users/xiaozhi747","html_url":"https://github.com/xiaozhi747","followers_url":"https://api.github.com/users/xiaozhi747/followers","following_url":"https://api.github.com/users/xiaozhi747/following{/other_user}","gists_url":"https://api.github.com/users/xiaozhi747/gists{/gist_id}","starred_url":"https://api.github.com/users/xiaozhi747/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xiaozhi747/subscriptions","organizations_url":"https://api.github.com/users/xiaozhi747/orgs","repos_url":"https://api.github.com/users/xiaozhi747/repos","events_url":"https://api.github.com/users/xiaozhi747/events{/privacy}","received_events_url":"https://api.github.com/users/xiaozhi747/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2025-03-14T09:53:22Z","updated_at":"2025-03-14T09:53:22Z","body":"还没修复么?","author_association":"NONE","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2724196179/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2724235531","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-2724235531","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":2724235531,"node_id":"IC_kwDOEiHOJs6iYIkL","user":{"login":"maemual","id":2352843,"node_id":"MDQ6VXNlcjIzNTI4NDM=","avatar_url":"https://avatars.githubusercontent.com/u/2352843?v=4","gravatar_id":"","url":"https://api.github.com/users/maemual","html_url":"https://github.com/maemual","followers_url":"https://api.github.com/users/maemual/followers","following_url":"https://api.github.com/users/maemual/following{/other_user}","gists_url":"https://api.github.com/users/maemual/gists{/gist_id}","starred_url":"https://api.github.com/users/maemual/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maemual/subscriptions","organizations_url":"https://api.github.com/users/maemual/orgs","repos_url":"https://api.github.com/users/maemual/repos","events_url":"https://api.github.com/users/maemual/events{/privacy}","received_events_url":"https://api.github.com/users/maemual/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2025-03-14T10:12:24Z","updated_at":"2025-03-14T10:12:24Z","body":"\n@xiaozhi747 早就支持了新版卡片回调,建议按照 README 文档使用。","author_association":"COLLABORATOR","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/2724235531/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null},{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/3568865279","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130#issuecomment-3568865279","issue_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","id":3568865279,"node_id":"IC_kwDOEiHOJs7UuI__","user":{"login":"eyotang","id":2398680,"node_id":"MDQ6VXNlcjIzOTg2ODA=","avatar_url":"https://avatars.githubusercontent.com/u/2398680?v=4","gravatar_id":"","url":"https://api.github.com/users/eyotang","html_url":"https://github.com/eyotang","followers_url":"https://api.github.com/users/eyotang/followers","following_url":"https://api.github.com/users/eyotang/following{/other_user}","gists_url":"https://api.github.com/users/eyotang/gists{/gist_id}","starred_url":"https://api.github.com/users/eyotang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eyotang/subscriptions","organizations_url":"https://api.github.com/users/eyotang/orgs","repos_url":"https://api.github.com/users/eyotang/repos","events_url":"https://api.github.com/users/eyotang/events{/privacy}","received_events_url":"https://api.github.com/users/eyotang/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2025-11-24T04:13:19Z","updated_at":"2025-11-24T04:13:19Z","body":"> 卡片回调一直报这个错误 `json: cannot unmarshal string into Go struct field CardAction.Header of type []string]`\n\nv3.4.3也有这个问题,请问怎么解决的?header是map[string]string,但是sdk里面定义的是map[string][]string","author_association":"NONE","pin":null,"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/comments/3568865279/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null,"minimized":null}] \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/issue-130-comments.txt b/docs/02-架构设计/专项设计/.research/issue-130-comments.txt new file mode 100644 index 0000000..7f6904d --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/issue-130-comments.txt @@ -0,0 +1,117 @@ +=== maemual @ 2024-03-22T06:42:29Z === +近期卡片新特性较多,人力比较紧张,还在推进中。 +可以继续使用旧版的卡片回传交互使用。 + +=== Martin2877 @ 2024-06-21T02:13:35Z === + +卡片回调一直报这个错误 `json: cannot unmarshal string into Go struct field CardAction.Header of type []string]` + +=== sbaby1997 @ 2024-07-31T13:45:20Z === +这个问题非常影响使用呀! + +=== maemual @ 2024-07-31T13:53:03Z === +@sbaby1997 升级最新版 v3.3.0,然后卡片回传交互选“卡片回传交互(旧)” + +=== Martin2877 @ 2024-08-02T01:54:18Z === +All right. 鉴于官方还没修,我这里提出一个临时解决方法,大家可以参考: + +在 `package larkcard` 文件中 + +1. 修改 `model.go` 中的 `CardAction` + +```go +type CardAction struct { + *larkevent.EventReq + Schema string `json:"schema"` + Header struct { + EventID string `json:"event_id"` + Token string `json:"token"` + CreateTime string `json:"create_time"` + EventType string `json:"event_type"` + TenantKey string `json:"tenant_key"` + AppID string `json:"app_id"` + } `json:"header"` + + Challenge string `json:"challenge"` + Token string `json:"token"` + Type string `json:"type"` + + Event struct { + Operator struct { + TenantKey string `json:"tenant_key"` + UserID string `json:"user_id"` + OpenID string `json:"open_id"` + UnionID string `json:"union_id"` + } `json:"operator"` + Token string `json:"token"` + Action struct { + Tag string `json:"tag"` + Timezone string `json:"timezone"` + FormValue map[string]string `json:"form_value"` + Name string `json:"name"` + } `json:"action"` + Host string `json:"host"` + Context struct { + OpenMessageID string `json:"open_message_id"` + OpenChatID string `json:"open_chat_id"` + } `json:"context"` + } `json:"event"` +} +``` +2. 修改`card.go`中的 `VerifySign`方法,使用 eventEncryptKey + +```go + if h.eventEncryptKey == "" { + return nil + } + +... + + targetSign := Signature(requestTimestamp, requestNonce,h.eventEncryptKey,string(req.Body)) +``` + +3. 修改`card.go`中的 `Signature` 方法,使用 sha256加密 +```go + h := sha256.New() +``` + +=== crazykun @ 2024-09-14T02:43:54Z === +新的改成event事件承载了 +[v3代码示例](https://github.com/larksuite/oapi-sdk-go/blob/v3_main/sample/event/event.go#L171) +``` +.OnP2CardActionTrigger(func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerReponse, error) { + fmt.Println("receive card action") + fmt.Println(larkcore.Prettify(event)) + return nil, nil + }) +``` + +=== kj648 @ 2025-01-23T11:52:59Z === +对于卡片消息回调的 Value 应该定义成interface{} . 至少目前的卡片消息回调, 回调事件的值 支持 字符串,如果用字符串传进来 就直接cannot unmarshal string into Go struct XXXXXXX 。为了不出发这个error 只能强制使用 对象结构作为value +event *callback.CardActionTriggerEvent +```go +type CallBackAction struct { + Value map[string]interface{} `json:"value"` + Tag string `json:"tag"` + Option string `json:"option"` + Timezone string `json:"timezone"` + Name string `json:"name"` + FormValue map[string]interface{} `json:"form_value"` + InputValue string `json:"input_value"` + Options []string `json:"options"` + Checked bool `json:"checked"` +} +``` + +=== xiaozhi747 @ 2025-03-14T09:53:22Z === +还没修复么? + +=== maemual @ 2025-03-14T10:12:24Z === + +@xiaozhi747 早就支持了新版卡片回调,建议按照 README 文档使用。 + +=== eyotang @ 2025-11-24T04:13:19Z === +> 卡片回调一直报这个错误 `json: cannot unmarshal string into Go struct field CardAction.Header of type []string]` + +v3.4.3也有这个问题,请问怎么解决的?header是map[string]string,但是sdk里面定义的是map[string][]string + diff --git a/docs/02-架构设计/专项设计/.research/issue-130.json b/docs/02-架构设计/专项设计/.research/issue-130.json new file mode 100644 index 0000000..8f3a467 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/issue-130.json @@ -0,0 +1 @@ +{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","repository_url":"https://api.github.com/repos/larksuite/oapi-sdk-go","labels_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/labels{/name}","comments_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/comments","events_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/events","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130","id":2201794740,"node_id":"I_kwDOEiHOJs6DPLi0","number":130,"title":"使用新版卡片回调消息格式不匹配,反序列化错误","user":{"login":"lxitgto","id":11627541,"node_id":"MDQ6VXNlcjExNjI3NTQx","avatar_url":"https://avatars.githubusercontent.com/u/11627541?v=4","gravatar_id":"","url":"https://api.github.com/users/lxitgto","html_url":"https://github.com/lxitgto","followers_url":"https://api.github.com/users/lxitgto/followers","following_url":"https://api.github.com/users/lxitgto/following{/other_user}","gists_url":"https://api.github.com/users/lxitgto/gists{/gist_id}","starred_url":"https://api.github.com/users/lxitgto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lxitgto/subscriptions","organizations_url":"https://api.github.com/users/lxitgto/orgs","repos_url":"https://api.github.com/users/lxitgto/repos","events_url":"https://api.github.com/users/lxitgto/events{/privacy}","received_events_url":"https://api.github.com/users/lxitgto/received_events","type":"User","user_view_type":"public","site_admin":false},"labels":[],"state":"closed","locked":false,"assignees":[],"milestone":null,"comments":10,"created_at":"2024-03-22T06:30:32Z","updated_at":"2025-11-24T04:13:19Z","closed_at":"2025-03-14T10:12:28Z","assignee":null,"author_association":"NONE","issue_field_values":[],"type":null,"active_lock_reason":null,"sub_issues_summary":{"total":0,"completed":0,"percent_completed":0},"issue_dependencies_summary":{"blocked_by":0,"total_blocked_by":0,"blocking":0,"total_blocking":0},"body":"使用新版卡片回调消息格式不匹配,反序列化错误。\r\n\r\n`CardAction struct`格式为v1格式,请问什么时候支持新版消息格式?","closed_by":{"login":"maemual","id":2352843,"node_id":"MDQ6VXNlcjIzNTI4NDM=","avatar_url":"https://avatars.githubusercontent.com/u/2352843?v=4","gravatar_id":"","url":"https://api.github.com/users/maemual","html_url":"https://github.com/maemual","followers_url":"https://api.github.com/users/maemual/followers","following_url":"https://api.github.com/users/maemual/following{/other_user}","gists_url":"https://api.github.com/users/maemual/gists{/gist_id}","starred_url":"https://api.github.com/users/maemual/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maemual/subscriptions","organizations_url":"https://api.github.com/users/maemual/orgs","repos_url":"https://api.github.com/users/maemual/repos","events_url":"https://api.github.com/users/maemual/events{/privacy}","received_events_url":"https://api.github.com/users/maemual/received_events","type":"User","user_view_type":"public","site_admin":false},"reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"timeline_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/timeline","performed_via_github_app":null,"state_reason":"completed","pinned_comment":null} \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/issue-card-callback.json b/docs/02-架构设计/专项设计/.research/issue-card-callback.json new file mode 100644 index 0000000..95f5c37 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/issue-card-callback.json @@ -0,0 +1 @@ +{"total_count":1,"incomplete_results":false,"items":[{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130","repository_url":"https://api.github.com/repos/larksuite/oapi-sdk-go","labels_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/labels{/name}","comments_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/comments","events_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/events","html_url":"https://github.com/larksuite/oapi-sdk-go/issues/130","id":2201794740,"node_id":"I_kwDOEiHOJs6DPLi0","number":130,"title":"使用新版卡片回调消息格式不匹配,反序列化错误","user":{"login":"lxitgto","id":11627541,"node_id":"MDQ6VXNlcjExNjI3NTQx","avatar_url":"https://avatars.githubusercontent.com/u/11627541?v=4","gravatar_id":"","url":"https://api.github.com/users/lxitgto","html_url":"https://github.com/lxitgto","followers_url":"https://api.github.com/users/lxitgto/followers","following_url":"https://api.github.com/users/lxitgto/following{/other_user}","gists_url":"https://api.github.com/users/lxitgto/gists{/gist_id}","starred_url":"https://api.github.com/users/lxitgto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lxitgto/subscriptions","organizations_url":"https://api.github.com/users/lxitgto/orgs","repos_url":"https://api.github.com/users/lxitgto/repos","events_url":"https://api.github.com/users/lxitgto/events{/privacy}","received_events_url":"https://api.github.com/users/lxitgto/received_events","type":"User","user_view_type":"public","site_admin":false},"labels":[],"state":"closed","locked":false,"assignees":[],"milestone":null,"comments":10,"created_at":"2024-03-22T06:30:32Z","updated_at":"2025-11-24T04:13:19Z","closed_at":"2025-03-14T10:12:28Z","assignee":null,"author_association":"NONE","issue_field_values":[],"type":null,"active_lock_reason":null,"sub_issues_summary":{"total":0,"completed":0,"percent_completed":0},"issue_dependencies_summary":{"blocked_by":0,"total_blocked_by":0,"blocking":0,"total_blocking":0},"body":"使用新版卡片回调消息格式不匹配,反序列化错误。\r\n\r\n`CardAction struct`格式为v1格式,请问什么时候支持新版消息格式?","reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"timeline_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/130/timeline","performed_via_github_app":null,"state_reason":"completed","pinned_comment":null,"score":1.0}],"search_type":"lexical"} \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/issue-card-ws.json b/docs/02-架构设计/专项设计/.research/issue-card-ws.json new file mode 100644 index 0000000..b749c43 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/issue-card-ws.json @@ -0,0 +1 @@ +{"total_count":1,"incomplete_results":false,"items":[{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/244","repository_url":"https://api.github.com/repos/larksuite/oapi-sdk-go","labels_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/244/labels{/name}","comments_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/244/comments","events_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/244/events","html_url":"https://github.com/larksuite/oapi-sdk-go/pull/244","id":5270680313,"node_id":"PR_kwDOEiHOJs8AAAABBPl61Q","number":244,"title":"fix(ws): make WebSocket client lifecycle cancellable","user":{"login":"SadMemories","id":44433408,"node_id":"MDQ6VXNlcjQ0NDMzNDA4","avatar_url":"https://avatars.githubusercontent.com/u/44433408?v=4","gravatar_id":"","url":"https://api.github.com/users/SadMemories","html_url":"https://github.com/SadMemories","followers_url":"https://api.github.com/users/SadMemories/followers","following_url":"https://api.github.com/users/SadMemories/following{/other_user}","gists_url":"https://api.github.com/users/SadMemories/gists{/gist_id}","starred_url":"https://api.github.com/users/SadMemories/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SadMemories/subscriptions","organizations_url":"https://api.github.com/users/SadMemories/orgs","repos_url":"https://api.github.com/users/SadMemories/repos","events_url":"https://api.github.com/users/SadMemories/events{/privacy}","received_events_url":"https://api.github.com/users/SadMemories/received_events","type":"User","user_view_type":"public","site_admin":false},"labels":[],"state":"closed","locked":false,"assignees":[],"milestone":null,"comments":0,"created_at":"2026-08-27T18:07:43Z","updated_at":"2026-08-28T09:41:33Z","closed_at":"2026-08-28T09:41:33Z","assignee":null,"author_association":"COLLABORATOR","issue_field_values":[],"type":null,"active_lock_reason":null,"draft":false,"pull_request":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/pulls/244","html_url":"https://github.com/larksuite/oapi-sdk-go/pull/244","diff_url":"https://github.com/larksuite/oapi-sdk-go/pull/244.diff","patch_url":"https://github.com/larksuite/oapi-sdk-go/pull/244.patch","merged_at":"2026-08-28T09:41:33Z"},"body":"## Summary\n- make `Client.Start` stop deterministically for caller cancellation, explicit `Close`, and terminal connection errors\n- preserve public bootstrap error types and synchronous lifecycle callback delivery\n- detect half-open connections with read deadlines, and bound WebSocket writes so read or write failures enter the reconnect flow\n- remove the bootstrap dependency on the global HTTP client: add `WithHttpClient` and `WithWebSocketDialer`, with a bounded default bootstrap HTTP client\n- keep `Close` callback-safe and non-blocking; add `CloseAndWait(ctx)` for callers that must wait for connection teardown, `OnDisconnected`, and SDK workers\n- restore legacy lifecycle log wording and per-connection `conn_id` output\n\n## Fixed issues\n\nFixes #189\nFixes #201\nFixes #204\nFixes #224\n\n## Notes\n- `CloseAndWait(ctx)` must be called outside lifecycle callbacks; callbacks should call `Close()` to avoid waiting for the coordinator that is currently executing them.\n- Ping may start before `OnReady` / `OnReconnected` returns, consistent with the accepted behavior for this PR.\n\n## Validation\n- `go test -count=1 -race ./ws`\n- `go test ./...`\n- IM sample: Code 0\n- event and card mocks: HTTP 200\n- WebSocket sample: connected, ping success, receive pong","reactions":{"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/244/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"timeline_url":"https://api.github.com/repos/larksuite/oapi-sdk-go/issues/244/timeline","performed_via_github_app":null,"state_reason":null,"score":1.0}],"search_type":"lexical"} \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/lark-sdk-tree.json b/docs/02-架构设计/专项设计/.research/lark-sdk-tree.json new file mode 100644 index 0000000..9474cda --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/lark-sdk-tree.json @@ -0,0 +1 @@ +{"sha":"9f904cf36162afdab0e3a38d5fae41e3391b7655","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/9f904cf36162afdab0e3a38d5fae41e3391b7655","tree":[{"path":".github","mode":"040000","type":"tree","sha":"c3017054bc6305da653e8a7409c995c4e69ff577","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/c3017054bc6305da653e8a7409c995c4e69ff577"},{"path":".github/workflows","mode":"040000","type":"tree","sha":"999043685446131a266c26c82a197805027ce99e","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/999043685446131a266c26c82a197805027ce99e"},{"path":".github/workflows/go.yml","mode":"100644","type":"blob","sha":"56700da4b1b09fac2b703b74f2dd8f374c847505","size":389,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/56700da4b1b09fac2b703b74f2dd8f374c847505"},{"path":".gitignore","mode":"100644","type":"blob","sha":"5f05c8a26fc971111b2a9206f36d75e87ff0268f","size":100,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/5f05c8a26fc971111b2a9206f36d75e87ff0268f"},{"path":"LICENSE","mode":"100644","type":"blob","sha":"9d01995f1e1817ccbf3bf1fe135ed5bfc2c7b528","size":1084,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/9d01995f1e1817ccbf3bf1fe135ed5bfc2c7b528"},{"path":"README.md","mode":"100644","type":"blob","sha":"7f9e4da9d0f8ef16f7f3f03aad4ddba7cec40eda","size":18914,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7f9e4da9d0f8ef16f7f3f03aad4ddba7cec40eda"},{"path":"README.zh.md","mode":"100644","type":"blob","sha":"b0d2dca80722b67039b6894de0d8b4429d2c84f0","size":23759,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/b0d2dca80722b67039b6894de0d8b4429d2c84f0"},{"path":"api","mode":"040000","type":"tree","sha":"a6e752a2db6ab9f4fd0515ca6303d4da94eae99b","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/a6e752a2db6ab9f4fd0515ca6303d4da94eae99b"},{"path":"api/api.go","mode":"100644","type":"blob","sha":"01ee17b25433f252d6891c247f15658437b915ba","size":513,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/01ee17b25433f252d6891c247f15658437b915ba"},{"path":"api/core","mode":"040000","type":"tree","sha":"40db0b6f359c6fc1f12b66f981a1b246f547a868","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/40db0b6f359c6fc1f12b66f981a1b246f547a868"},{"path":"api/core/constants","mode":"040000","type":"tree","sha":"16b4acadaeb62af88e1460e9aa7ee27f9775c405","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/16b4acadaeb62af88e1460e9aa7ee27f9775c405"},{"path":"api/core/constants/constants.go","mode":"100644","type":"blob","sha":"cc052dc90675d9651057bdbbc450dd34961672df","size":591,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/cc052dc90675d9651057bdbbc450dd34961672df"},{"path":"api/core/errors","mode":"040000","type":"tree","sha":"48d17181297699e4ef60eb507f367b586d6d083d","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/48d17181297699e4ef60eb507f367b586d6d083d"},{"path":"api/core/errors/errors.go","mode":"100644","type":"blob","sha":"b26679404b8a21ea4e62569e8c665fed0bd82df9","size":321,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/b26679404b8a21ea4e62569e8c665fed0bd82df9"},{"path":"api/core/handlers","mode":"040000","type":"tree","sha":"d0c87eca28a1e38eb1e43cd6f5602f448a4d4c37","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/d0c87eca28a1e38eb1e43cd6f5602f448a4d4c37"},{"path":"api/core/handlers/access_token.go","mode":"100644","type":"blob","sha":"38fdb624d0c7ed50e7b74dcdc85e22da6e121f77","size":6791,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/38fdb624d0c7ed50e7b74dcdc85e22da6e121f77"},{"path":"api/core/handlers/handlers.go","mode":"100644","type":"blob","sha":"7dfb81052d6563aefb0ae5765983cfe1052a8290","size":11185,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7dfb81052d6563aefb0ae5765983cfe1052a8290"},{"path":"api/core/request","mode":"040000","type":"tree","sha":"0f7b8a4fe664d63ea808971ad333128ba4fca800","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/0f7b8a4fe664d63ea808971ad333128ba4fca800"},{"path":"api/core/request/formdata.go","mode":"100644","type":"blob","sha":"667688b6ce22f37c597daba3044ca962f1b7d743","size":2390,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/667688b6ce22f37c597daba3044ca962f1b7d743"},{"path":"api/core/request/request.go","mode":"100644","type":"blob","sha":"b29acefe0e2157bfe6149117a302a2ecdcd83c3f","size":7031,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/b29acefe0e2157bfe6149117a302a2ecdcd83c3f"},{"path":"api/core/response","mode":"040000","type":"tree","sha":"b2273ab8a23176e05f31e42905a996865f37a8bd","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/b2273ab8a23176e05f31e42905a996865f37a8bd"},{"path":"api/core/response/error.go","mode":"100644","type":"blob","sha":"87e4330310af37931aeb287a8a3748856baef044","size":2023,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/87e4330310af37931aeb287a8a3748856baef044"},{"path":"api/core/response/no_data.go","mode":"100644","type":"blob","sha":"0c70afd8add753fefaf62c542576dd6bb3efb3ae","size":41,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0c70afd8add753fefaf62c542576dd6bb3efb3ae"},{"path":"api/core/response/response.go","mode":"100644","type":"blob","sha":"acc06bcf2e43e350b1b87756017a4bb6bb5d79bc","size":82,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/acc06bcf2e43e350b1b87756017a4bb6bb5d79bc"},{"path":"api/core/token","mode":"040000","type":"tree","sha":"9dfa7f0f789f8e8195c9f4bd78c7f8fc0cf662ef","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/9dfa7f0f789f8e8195c9f4bd78c7f8fc0cf662ef"},{"path":"api/core/token/token.go","mode":"100644","type":"blob","sha":"bb737acb50cac71a096ffbd7bf8887a46b520918","size":766,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/bb737acb50cac71a096ffbd7bf8887a46b520918"},{"path":"api/core/tools","mode":"040000","type":"tree","sha":"98e9e662fb8a4a17a4e72349aaf872d4646e590a","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/98e9e662fb8a4a17a4e72349aaf872d4646e590a"},{"path":"api/core/tools/file.go","mode":"100644","type":"blob","sha":"8740e6f81848c375af3b28fcb27c99041b182c04","size":745,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8740e6f81848c375af3b28fcb27c99041b182c04"},{"path":"api/core/tools/json.go","mode":"100644","type":"blob","sha":"7eb0d4c293f33f7f7fa339d513d7bb1e5dd08929","size":2877,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7eb0d4c293f33f7f7fa339d513d7bb1e5dd08929"},{"path":"api/core/transport","mode":"040000","type":"tree","sha":"d07a52d9f80ec326bbe775977fe874b6230ffc52","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/d07a52d9f80ec326bbe775977fe874b6230ffc52"},{"path":"api/core/transport/transport.go","mode":"100644","type":"blob","sha":"ef45986a91f53fcfc996ee574f334d527c89a91d","size":463,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/ef45986a91f53fcfc996ee574f334d527c89a91d"},{"path":"api_process.jpg","mode":"100644","type":"blob","sha":"0c2a8fa903784d805bdc1bef7641d2dd44316da6","size":92661,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0c2a8fa903784d805bdc1bef7641d2dd44316da6"},{"path":"api_process_en.jpg","mode":"100644","type":"blob","sha":"0707b40f4cc4772527b8703deb81c1b046d0057f","size":91379,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0707b40f4cc4772527b8703deb81c1b046d0057f"},{"path":"card","mode":"040000","type":"tree","sha":"d5071e8247ce62b4bf7cd41fbe473ae98616ec26","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/d5071e8247ce62b4bf7cd41fbe473ae98616ec26"},{"path":"card/card.go","mode":"100644","type":"blob","sha":"aece985098793c66bfbbe4319f7f140892907631","size":658,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/aece985098793c66bfbbe4319f7f140892907631"},{"path":"card/handlers","mode":"040000","type":"tree","sha":"f44e9fff54bc47720011db7581d725473d718ec8","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f44e9fff54bc47720011db7581d725473d718ec8"},{"path":"card/handlers/err.go","mode":"100644","type":"blob","sha":"a87e624d1f16af3313e009839eef75e0d8f67bae","size":384,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/a87e624d1f16af3313e009839eef75e0d8f67bae"},{"path":"card/handlers/handler.go","mode":"100644","type":"blob","sha":"0084c987fbf5ccd9da99bbcc46ced810c8c517c4","size":404,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0084c987fbf5ccd9da99bbcc46ced810c8c517c4"},{"path":"card/handlers/handlers.go","mode":"100644","type":"blob","sha":"3f1ff7d3b6c4f4164c441a2e6deb1a4dbe0b3e5a","size":5079,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/3f1ff7d3b6c4f4164c441a2e6deb1a4dbe0b3e5a"},{"path":"card/http","mode":"040000","type":"tree","sha":"9a8c195c63dd6c64ac454cdb056579127c3af002","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/9a8c195c63dd6c64ac454cdb056579127c3af002"},{"path":"card/http/handler.go","mode":"100644","type":"blob","sha":"4b26f1a6b9e02f4577cd93ee4089f7847fb89d88","size":554,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/4b26f1a6b9e02f4577cd93ee4089f7847fb89d88"},{"path":"card/http/native","mode":"040000","type":"tree","sha":"0c256ceb8250dc03d39c615ecf9399cf60c50d02","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/0c256ceb8250dc03d39c615ecf9399cf60c50d02"},{"path":"card/http/native/handler.go","mode":"100644","type":"blob","sha":"7f7d4c80b3997bdae5605e36f29947ae96f03d4f","size":305,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7f7d4c80b3997bdae5605e36f29947ae96f03d4f"},{"path":"card/model","mode":"040000","type":"tree","sha":"c8b4f38f761097d98db506cfb9ce1d97616f082a","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/c8b4f38f761097d98db506cfb9ce1d97616f082a"},{"path":"card/model/card.go","mode":"100644","type":"blob","sha":"d181129ff07d6187c5a5613de0622955850ca7cf","size":1383,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/d181129ff07d6187c5a5613de0622955850ca7cf"},{"path":"core","mode":"040000","type":"tree","sha":"920c2d80c5c8b70f36355c0ada5bdb167cb3d01f","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/920c2d80c5c8b70f36355c0ada5bdb167cb3d01f"},{"path":"core/config","mode":"040000","type":"tree","sha":"3a95196dcd62617954a5611a133ff7a052ba7166","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/3a95196dcd62617954a5611a133ff7a052ba7166"},{"path":"core/config/app_settings.go","mode":"100644","type":"blob","sha":"f0867eac171ae1d8174be8a5efc044e0470e2049","size":2952,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/f0867eac171ae1d8174be8a5efc044e0470e2049"},{"path":"core/config/config.go","mode":"100644","type":"blob","sha":"549f2791a3d617df1961625149946d54352d394b","size":1951,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/549f2791a3d617df1961625149946d54352d394b"},{"path":"core/constants","mode":"040000","type":"tree","sha":"18458f5966f8bfdfa4549f4b888f7b5fbbaab689","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/18458f5966f8bfdfa4549f4b888f7b5fbbaab689"},{"path":"core/constants/constants.go","mode":"100644","type":"blob","sha":"a6ac70cdf890d3ff8948a9f8e60bb2439021d76c","size":757,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/a6ac70cdf890d3ff8948a9f8e60bb2439021d76c"},{"path":"core/context.go","mode":"100644","type":"blob","sha":"7d777dcd38d211905f2f3a504b40b6364b288b69","size":1752,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7d777dcd38d211905f2f3a504b40b6364b288b69"},{"path":"core/core.go","mode":"100644","type":"blob","sha":"887e6fc616f0fd441372d539f89daa3058b00362","size":2058,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/887e6fc616f0fd441372d539f89daa3058b00362"},{"path":"core/errors","mode":"040000","type":"tree","sha":"6f42dac1ba1e599895a252f85c10e623084185c4","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/6f42dac1ba1e599895a252f85c10e623084185c4"},{"path":"core/errors/errors.go","mode":"100644","type":"blob","sha":"dfe75e152a4a9423b1957d6acfaf8e0e7a34c8f7","size":302,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/dfe75e152a4a9423b1957d6acfaf8e0e7a34c8f7"},{"path":"core/log","mode":"040000","type":"tree","sha":"ae351ce4e239c305fe60fc690696db82c354fc4c","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/ae351ce4e239c305fe60fc690696db82c354fc4c"},{"path":"core/log/log.go","mode":"100644","type":"blob","sha":"15083426e7189fe01d37e110f20c1a7d69845443","size":1711,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/15083426e7189fe01d37e110f20c1a7d69845443"},{"path":"core/oapi_header.go","mode":"100644","type":"blob","sha":"8794c10c0f934aee112a2409d418cd3df5aa01f4","size":771,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8794c10c0f934aee112a2409d418cd3df5aa01f4"},{"path":"core/oapi_request.go","mode":"100644","type":"blob","sha":"0d3400e71552f130195a0daa23cad2ac64fe89a1","size":468,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0d3400e71552f130195a0daa23cad2ac64fe89a1"},{"path":"core/oapi_response.go","mode":"100644","type":"blob","sha":"337f8eb14aa95f8a132bf063d18f0a9d1e006fd4","size":1030,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/337f8eb14aa95f8a132bf063d18f0a9d1e006fd4"},{"path":"core/store","mode":"040000","type":"tree","sha":"ad08d0574aa1d4ae3d151d3baffb4668371be8e5","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/ad08d0574aa1d4ae3d151d3baffb4668371be8e5"},{"path":"core/store/keys.go","mode":"100644","type":"blob","sha":"8f9bc560950763b306ae684605d38ed8ec0335b0","size":534,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8f9bc560950763b306ae684605d38ed8ec0335b0"},{"path":"core/store/store.go","mode":"100644","type":"blob","sha":"49736b88f7af21bed708696b1f4f30caaab4302c","size":1199,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/49736b88f7af21bed708696b1f4f30caaab4302c"},{"path":"core/test","mode":"040000","type":"tree","sha":"81eecd6fb349a2ada59f22b951ceaf0b52cebf60","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/81eecd6fb349a2ada59f22b951ceaf0b52cebf60"},{"path":"core/test/config.go","mode":"100644","type":"blob","sha":"5561fe8266f56932c11d7bc5b9f936bb582979d2","size":1476,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/5561fe8266f56932c11d7bc5b9f936bb582979d2"},{"path":"core/tools","mode":"040000","type":"tree","sha":"f35385547308a4af2bb711bb874418d3ebc8c8ca","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f35385547308a4af2bb711bb874418d3ebc8c8ca"},{"path":"core/tools/decrypt.go","mode":"100644","type":"blob","sha":"babe44050a74ad1d43f9acb0659ab4e497cc3adb","size":1484,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/babe44050a74ad1d43f9acb0659ab4e497cc3adb"},{"path":"core/tools/file.go","mode":"100644","type":"blob","sha":"8740e6f81848c375af3b28fcb27c99041b182c04","size":745,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8740e6f81848c375af3b28fcb27c99041b182c04"},{"path":"core/tools/prettify.go","mode":"100644","type":"blob","sha":"ee7cd548587f3b092416cac99faed38e817e9a52","size":2507,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/ee7cd548587f3b092416cac99faed38e817e9a52"},{"path":"core/tools/time.go","mode":"100644","type":"blob","sha":"77163d1d3ed8f10b95830a73d4791f531619f5bc","size":251,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/77163d1d3ed8f10b95830a73d4791f531619f5bc"},{"path":"core/version.go","mode":"100644","type":"blob","sha":"d62a02c8c26e63694625550dbb0814074a815004","size":43,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/d62a02c8c26e63694625550dbb0814074a815004"},{"path":"doc","mode":"040000","type":"tree","sha":"74682146f003e6750982eb90cd060d8599ad7fb2","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/74682146f003e6750982eb90cd060d8599ad7fb2"},{"path":"doc/ISV.APP.README.zh.md","mode":"100644","type":"blob","sha":"8aceb732d6f6a58d01f1f004f1e9782e49b43d1d","size":8057,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8aceb732d6f6a58d01f1f004f1e9782e49b43d1d"},{"path":"doc/app_type.en.png","mode":"100644","type":"blob","sha":"33473ab411e4011296813ced67a58eac7a23c763","size":77181,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/33473ab411e4011296813ced67a58eac7a23c763"},{"path":"doc/app_type.zh.png","mode":"100644","type":"blob","sha":"91e25ba63a8e75d44c069e4ec271d43d88438b7c","size":221422,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/91e25ba63a8e75d44c069e4ec271d43d88438b7c"},{"path":"event","mode":"040000","type":"tree","sha":"f6ec2c3cfaf3a30da1233f70cc5c5c1af08d28a9","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f6ec2c3cfaf3a30da1233f70cc5c5c1af08d28a9"},{"path":"event/app","mode":"040000","type":"tree","sha":"ea05241954dca29080ac97f2eb6e9b46ec27d2f7","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/ea05241954dca29080ac97f2eb6e9b46ec27d2f7"},{"path":"event/app/v1","mode":"040000","type":"tree","sha":"b8236e5d1cf709329502079b82b33a9991a583a4","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/b8236e5d1cf709329502079b82b33a9991a583a4"},{"path":"event/app/v1/app_ticket.go","mode":"100644","type":"blob","sha":"84f30a304aed4fb5d78733b275b918bb87fad49f","size":1172,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/84f30a304aed4fb5d78733b275b918bb87fad49f"},{"path":"event/core","mode":"040000","type":"tree","sha":"1de3eeea8ec34600061dfe9e02f6d4dc6b7fdde2","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/1de3eeea8ec34600061dfe9e02f6d4dc6b7fdde2"},{"path":"event/core/handlers","mode":"040000","type":"tree","sha":"f0ee90de4070d07740f9037c1c982ee3b20e89a9","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f0ee90de4070d07740f9037c1c982ee3b20e89a9"},{"path":"event/core/handlers/err.go","mode":"100644","type":"blob","sha":"576d2ebeb26ae3c531f41b5226b89f22b7b9bd3b","size":317,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/576d2ebeb26ae3c531f41b5226b89f22b7b9bd3b"},{"path":"event/core/handlers/handlers.go","mode":"100644","type":"blob","sha":"2bb705fbfc1b40dc1818d6a8ff335cd2264fc9aa","size":3633,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/2bb705fbfc1b40dc1818d6a8ff335cd2264fc9aa"},{"path":"event/core/handlers/header.go","mode":"100644","type":"blob","sha":"0a2c952fa5725e8f9d116f06973447de3831e9f5","size":788,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0a2c952fa5725e8f9d116f06973447de3831e9f5"},{"path":"event/core/model","mode":"040000","type":"tree","sha":"ab0c1901e200be326bf25d79767beff471c95532","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/ab0c1901e200be326bf25d79767beff471c95532"},{"path":"event/core/model/event.go","mode":"100644","type":"blob","sha":"4954d54c62c45f99d2d8cd8136f1550926b74d8f","size":1186,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/4954d54c62c45f99d2d8cd8136f1550926b74d8f"},{"path":"event/event.go","mode":"100644","type":"blob","sha":"2bc42506cd80849e93cda99b5f9f33dd61030b48","size":1628,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/2bc42506cd80849e93cda99b5f9f33dd61030b48"},{"path":"event/http","mode":"040000","type":"tree","sha":"c638208b46adee4c11db02b271062f8066deb7a4","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/c638208b46adee4c11db02b271062f8066deb7a4"},{"path":"event/http/handler.go","mode":"100644","type":"blob","sha":"757f3437e83530e75b7be8cef8e639e316d3556b","size":556,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/757f3437e83530e75b7be8cef8e639e316d3556b"},{"path":"event/http/native","mode":"040000","type":"tree","sha":"307196daa5d0b87263147b156cf4ca46b6fd65ac","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/307196daa5d0b87263147b156cf4ca46b6fd65ac"},{"path":"event/http/native/handler.go","mode":"100644","type":"blob","sha":"e75d7bd5021aa58b61ff3b8735cbf84ef047051e","size":306,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/e75d7bd5021aa58b61ff3b8735cbf84ef047051e"},{"path":"go.mod","mode":"100644","type":"blob","sha":"2ee3a1018c33277c4b948c2b30ee5ecdbb4a7975","size":167,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/2ee3a1018c33277c4b948c2b30ee5ecdbb4a7975"},{"path":"go.sum","mode":"100644","type":"blob","sha":"f8b018f70234a9e4b61b8c881d1573ca578b6776","size":11046,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/f8b018f70234a9e4b61b8c881d1573ca578b6776"},{"path":"sample","mode":"040000","type":"tree","sha":"147eb052115318603be58d1768402349dc40c2d2","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/147eb052115318603be58d1768402349dc40c2d2"},{"path":"sample/api","mode":"040000","type":"tree","sha":"e56ccfa793ec68b6a5286531f54b65676ab544d7","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/e56ccfa793ec68b6a5286531f54b65676ab544d7"},{"path":"sample/api/api.go","mode":"100644","type":"blob","sha":"e99785780fd3f8a962bc7cae176ceadbdf017f5f","size":4909,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/e99785780fd3f8a962bc7cae176ceadbdf017f5f"},{"path":"sample/api/authen.go","mode":"100644","type":"blob","sha":"ae008ff3932ccaab3edff63b35b9ebd1ab895d67","size":2244,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/ae008ff3932ccaab3edff63b35b9ebd1ab895d67"},{"path":"sample/api/bitable.go","mode":"100644","type":"blob","sha":"bcd62590fd78ed02225386bb16495af3b5ff60ac","size":2483,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/bcd62590fd78ed02225386bb16495af3b5ff60ac"},{"path":"sample/api/bot.go","mode":"100644","type":"blob","sha":"78ba59e973864a6e790455e1c8c44180caf730fc","size":936,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/78ba59e973864a6e790455e1c8c44180caf730fc"},{"path":"sample/api/calendar.go","mode":"100644","type":"blob","sha":"cd0e416d9a56384afab4e519ad66751fe822492c","size":1510,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/cd0e416d9a56384afab4e519ad66751fe822492c"},{"path":"sample/api/contact.go","mode":"100644","type":"blob","sha":"f48c08684f271a2a28a10a7c7c9978525a4ed2a2","size":1700,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/f48c08684f271a2a28a10a7c7c9978525a4ed2a2"},{"path":"sample/api/docx.go","mode":"100644","type":"blob","sha":"7953dd1abf44b78eac3f51b22ee89c2cc5c6478e","size":4513,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7953dd1abf44b78eac3f51b22ee89c2cc5c6478e"},{"path":"sample/api/drive.go","mode":"100644","type":"blob","sha":"9e40a45510f6b2d44f80412bd415f984627b204c","size":2969,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/9e40a45510f6b2d44f80412bd415f984627b204c"},{"path":"sample/api/helpdesk.go","mode":"100644","type":"blob","sha":"e63d3b9f2fb5b50ee1f5d2655f86de1df271ce92","size":2184,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/e63d3b9f2fb5b50ee1f5d2655f86de1df271ce92"},{"path":"sample/api/im.go","mode":"100644","type":"blob","sha":"61e79f10ed7f406c8504b1ef934b8c786d1cbc93","size":2640,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/61e79f10ed7f406c8504b1ef934b8c786d1cbc93"},{"path":"sample/api/image.go","mode":"100644","type":"blob","sha":"7ed9607fd6edab7449be22386e25e6ce2c7376c6","size":1828,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7ed9607fd6edab7449be22386e25e6ce2c7376c6"},{"path":"sample/api/optical_char_recognition.go","mode":"100644","type":"blob","sha":"f1899daee6ec00f5a8a3015761a2045bef8673a3","size":1161,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/f1899daee6ec00f5a8a3015761a2045bef8673a3"},{"path":"sample/api/speech_to_text.go","mode":"100644","type":"blob","sha":"7a6ad0ade6fd8ec66be29f2e9f9b704905f47c1e","size":1330,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7a6ad0ade6fd8ec66be29f2e9f9b704905f47c1e"},{"path":"sample/api/translation.go","mode":"100644","type":"blob","sha":"eb637bb5d2cd4658b1aa5112fe7a9aeec9e9cd4e","size":1192,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/eb637bb5d2cd4658b1aa5112fe7a9aeec9e9cd4e"},{"path":"sample/api/vc.go","mode":"100644","type":"blob","sha":"e0bcbac9d82130a8f371830eeea22490ae58f931","size":1466,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/e0bcbac9d82130a8f371830eeea22490ae58f931"},{"path":"sample/card","mode":"040000","type":"tree","sha":"e6e6bc18543aa40a724caa211270180543223c0a","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/e6e6bc18543aa40a724caa211270180543223c0a"},{"path":"sample/card/custom_resp.go","mode":"100644","type":"blob","sha":"2f72b6d8df8f59a6f9347eb4e8bac0eabc891b5c","size":1894,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/2f72b6d8df8f59a6f9347eb4e8bac0eabc891b5c"},{"path":"sample/card/gin.go","mode":"100644","type":"blob","sha":"f3ff7a403ecf371601312a22fdde86ed1f14fd8d","size":965,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/f3ff7a403ecf371601312a22fdde86ed1f14fd8d"},{"path":"sample/card/go.go","mode":"100644","type":"blob","sha":"471ffc3e62c144433ae66943b47790f013927edd","size":1485,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/471ffc3e62c144433ae66943b47790f013927edd"},{"path":"sample/card/http_server.go","mode":"100644","type":"blob","sha":"cb8e080f7788947bef7e860dbacb960af8be987f","size":1201,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/cb8e080f7788947bef7e860dbacb960af8be987f"},{"path":"sample/configs","mode":"040000","type":"tree","sha":"0dec5103a75262465d17c78dab95d73ae39f522d","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/0dec5103a75262465d17c78dab95d73ae39f522d"},{"path":"sample/configs/config.go","mode":"100644","type":"blob","sha":"ddcdb5c3778978c6399e8425463cf04c77e1fd6e","size":616,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/ddcdb5c3778978c6399e8425463cf04c77e1fd6e"},{"path":"sample/configs/logrus.go","mode":"100644","type":"blob","sha":"cedfa1d4d3fe4fb011d2036952867e742e133ecf","size":664,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/cedfa1d4d3fe4fb011d2036952867e742e133ecf"},{"path":"sample/configs/redis_store.go","mode":"100644","type":"blob","sha":"3ddf9bfb815197acdccce6f8af8e1ae056e862ff","size":1039,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/3ddf9bfb815197acdccce6f8af8e1ae056e862ff"},{"path":"sample/event","mode":"040000","type":"tree","sha":"ba51de1889cd268d3669458223459665b7d8a9c2","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/ba51de1889cd268d3669458223459665b7d8a9c2"},{"path":"sample/event/application.go","mode":"100644","type":"blob","sha":"68147b4700b049b81ce5ea2aa8626b0355213599","size":1279,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/68147b4700b049b81ce5ea2aa8626b0355213599"},{"path":"sample/event/contact.go","mode":"100644","type":"blob","sha":"80652f3da7344ea481bc28e3e3fc8d8b5311f12d","size":1355,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/80652f3da7344ea481bc28e3e3fc8d8b5311f12d"},{"path":"sample/event/gin.go","mode":"100644","type":"blob","sha":"8caad683b8e188707845f3658379e86e94d8a177","size":1911,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8caad683b8e188707845f3658379e86e94d8a177"},{"path":"sample/event/go.go","mode":"100644","type":"blob","sha":"0947c9f0b01ffd9b7fd285f37789444bc7f74c8f","size":1397,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0947c9f0b01ffd9b7fd285f37789444bc7f74c8f"},{"path":"sample/event/http_server.go","mode":"100644","type":"blob","sha":"3d9eb098afe4b2c20a24d53ba5bd8de7ae2b9d3c","size":1527,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/3d9eb098afe4b2c20a24d53ba5bd8de7ae2b9d3c"},{"path":"sample/event/im.go","mode":"100644","type":"blob","sha":"f9dcdc255db4a6c7cf7c9a830aec44f8007ac284","size":937,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/f9dcdc255db4a6c7cf7c9a830aec44f8007ac284"},{"path":"sample/test_download.png","mode":"100755","type":"blob","sha":"bea74ce5da54127bcc2f5060b0dd9741960a91df","size":10799,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/bea74ce5da54127bcc2f5060b0dd9741960a91df"},{"path":"sample/tools","mode":"040000","type":"tree","sha":"7442dc314cb199a030f8e57c4538381a535455b5","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/7442dc314cb199a030f8e57c4538381a535455b5"},{"path":"sample/tools/file_download.go","mode":"100644","type":"blob","sha":"6e051803ef3b3cf3537aff86e4cbd4c11cf2bd34","size":548,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/6e051803ef3b3cf3537aff86e4cbd4c11cf2bd34"},{"path":"service","mode":"040000","type":"tree","sha":"caa0d3340991686a8e071dc4ef429c3c2825e1bd","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/caa0d3340991686a8e071dc4ef429c3c2825e1bd"},{"path":"service/acs","mode":"040000","type":"tree","sha":"5b6f219955374b5a591d88ff71654d5a58b7e367","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/5b6f219955374b5a591d88ff71654d5a58b7e367"},{"path":"service/acs/v1","mode":"040000","type":"tree","sha":"5e69d8a26d2eec7bc2cae001e378e59608bfe9d5","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/5e69d8a26d2eec7bc2cae001e378e59608bfe9d5"},{"path":"service/acs/v1/api.go","mode":"100644","type":"blob","sha":"42bf01e78464b38c8df787ec856d5e01335404ad","size":11660,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/42bf01e78464b38c8df787ec856d5e01335404ad"},{"path":"service/acs/v1/event.go","mode":"100644","type":"blob","sha":"45266fd9738d185eba2159cb4c77c36fbca1e6a6","size":1314,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/45266fd9738d185eba2159cb4c77c36fbca1e6a6"},{"path":"service/acs/v1/model.go","mode":"100644","type":"blob","sha":"d47c7b56c227fe566dc76793bac39b124f060ae1","size":3478,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/d47c7b56c227fe566dc76793bac39b124f060ae1"},{"path":"service/admin","mode":"040000","type":"tree","sha":"1321dedcb250ffce5556e69ba547c94ac90e3660","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/1321dedcb250ffce5556e69ba547c94ac90e3660"},{"path":"service/admin/v1","mode":"040000","type":"tree","sha":"217acee42c96275730cf86402dfbcb0b361c38d9","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/217acee42c96275730cf86402dfbcb0b361c38d9"},{"path":"service/admin/v1/api.go","mode":"100644","type":"blob","sha":"74ba6a3fa930b30b4ef75d42d9a7ff8a640297f1","size":4521,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/74ba6a3fa930b30b4ef75d42d9a7ff8a640297f1"},{"path":"service/admin/v1/model.go","mode":"100644","type":"blob","sha":"1cd7e8dcf377b9d5983e7b8a12e813533e327753","size":13070,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/1cd7e8dcf377b9d5983e7b8a12e813533e327753"},{"path":"service/application","mode":"040000","type":"tree","sha":"3c4c969e327e30cc1e2161f7c1ae6b293fe2bf0b","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/3c4c969e327e30cc1e2161f7c1ae6b293fe2bf0b"},{"path":"service/application/v1","mode":"040000","type":"tree","sha":"46395e49369e38511888521243b6ad3fd867acdd","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/46395e49369e38511888521243b6ad3fd867acdd"},{"path":"service/application/v1/event.go","mode":"100644","type":"blob","sha":"23c562b5a76d5d9859a0f9d290f2eb9b9f883b04","size":2250,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/23c562b5a76d5d9859a0f9d290f2eb9b9f883b04"},{"path":"service/application/v1/model.go","mode":"100644","type":"blob","sha":"be447171610d3981ac68ac6fc9e5fd65a24f449d","size":1999,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/be447171610d3981ac68ac6fc9e5fd65a24f449d"},{"path":"service/application/v6","mode":"040000","type":"tree","sha":"d061b3dc1df3a3dee7b9a0417f0cf46f2c63b407","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/d061b3dc1df3a3dee7b9a0417f0cf46f2c63b407"},{"path":"service/application/v6/api.go","mode":"100644","type":"blob","sha":"bd44febd603b68eb84549318028f57d4a415f4ff","size":2694,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/bd44febd603b68eb84549318028f57d4a415f4ff"},{"path":"service/application/v6/event.go","mode":"100644","type":"blob","sha":"752a29f0280dc1424bf0ca5ef6c090e8a8425f16","size":875,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/752a29f0280dc1424bf0ca5ef6c090e8a8425f16"},{"path":"service/application/v6/model.go","mode":"100644","type":"blob","sha":"a13f623a609bd9e3ea45c1d441e4b1d01ac8ee78","size":20259,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/a13f623a609bd9e3ea45c1d441e4b1d01ac8ee78"},{"path":"service/authen","mode":"040000","type":"tree","sha":"c887873a49eed018dd9d45b0f11d5500a26bb1f5","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/c887873a49eed018dd9d45b0f11d5500a26bb1f5"},{"path":"service/authen/v1","mode":"040000","type":"tree","sha":"3bd65c856f7892e6887042166cac145d7f9d6d32","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/3bd65c856f7892e6887042166cac145d7f9d6d32"},{"path":"service/authen/v1/api.go","mode":"100644","type":"blob","sha":"b3711ff9852ed3178582e6f4db2f0afb7b536d5a","size":2803,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/b3711ff9852ed3178582e6f4db2f0afb7b536d5a"},{"path":"service/authen/v1/model.go","mode":"100644","type":"blob","sha":"950c0638a6ce2901b36ef8e1e99694730e183a2e","size":2903,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/950c0638a6ce2901b36ef8e1e99694730e183a2e"},{"path":"service/aweme_ecosystem","mode":"040000","type":"tree","sha":"7fada976fa451c294f7e21ab229776337c1dd889","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/7fada976fa451c294f7e21ab229776337c1dd889"},{"path":"service/aweme_ecosystem/v1","mode":"040000","type":"tree","sha":"23c2782073c3d4e32b02bdc9f64e633ea1e6938c","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/23c2782073c3d4e32b02bdc9f64e633ea1e6938c"},{"path":"service/aweme_ecosystem/v1/api.go","mode":"100644","type":"blob","sha":"5f2bb5e25ba7f0aa8f3492dd414753a62ce2a121","size":1765,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/5f2bb5e25ba7f0aa8f3492dd414753a62ce2a121"},{"path":"service/aweme_ecosystem/v1/event.go","mode":"100644","type":"blob","sha":"fba16e6121e17a3c17ae898fcb610512708854d9","size":840,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/fba16e6121e17a3c17ae898fcb610512708854d9"},{"path":"service/aweme_ecosystem/v1/model.go","mode":"100644","type":"blob","sha":"e1c185ed46b91b7003ce3290932f723d4a5efb0d","size":1621,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/e1c185ed46b91b7003ce3290932f723d4a5efb0d"},{"path":"service/bitable","mode":"040000","type":"tree","sha":"d0d0bd49a9fd30e365c36ba0ac07a8f5d978f0f8","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/d0d0bd49a9fd30e365c36ba0ac07a8f5d978f0f8"},{"path":"service/bitable/v1","mode":"040000","type":"tree","sha":"690d64a14f89fd61c8344967effec4e7ec198ec5","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/690d64a14f89fd61c8344967effec4e7ec198ec5"},{"path":"service/bitable/v1/api.go","mode":"100644","type":"blob","sha":"fe0f58a07a09ff58962daa4f2665e51d7651c9f4","size":42919,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/fe0f58a07a09ff58962daa4f2665e51d7651c9f4"},{"path":"service/bitable/v1/model.go","mode":"100644","type":"blob","sha":"68f7789d3102755f73ba1163b07b2b60a25f9913","size":15072,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/68f7789d3102755f73ba1163b07b2b60a25f9913"},{"path":"service/bot","mode":"040000","type":"tree","sha":"6fa9d366ce1ab6e085f629c0682cc0d7e9382c05","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/6fa9d366ce1ab6e085f629c0682cc0d7e9382c05"},{"path":"service/bot/v3","mode":"040000","type":"tree","sha":"f7190f641c334c11f1d897d1ef552eb666becc43","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f7190f641c334c11f1d897d1ef552eb666becc43"},{"path":"service/bot/v3/api.go","mode":"100644","type":"blob","sha":"0ec3016075e3b5ad4d8a73ce17a9feac47d89bea","size":1198,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0ec3016075e3b5ad4d8a73ce17a9feac47d89bea"},{"path":"service/bot/v3/model.go","mode":"100644","type":"blob","sha":"0ef162d50f14d6403bc4b9f4aeda23884084c1a4","size":687,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0ef162d50f14d6403bc4b9f4aeda23884084c1a4"},{"path":"service/calendar","mode":"040000","type":"tree","sha":"9fad26e7dd2eddd5b156b1cd8348ee6a47285547","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/9fad26e7dd2eddd5b156b1cd8348ee6a47285547"},{"path":"service/calendar/v4","mode":"040000","type":"tree","sha":"1c9567bfb87a12613682e97dd9fc505641cd20e4","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/1c9567bfb87a12613682e97dd9fc505641cd20e4"},{"path":"service/calendar/v4/api.go","mode":"100644","type":"blob","sha":"4c321d70067dafa9559271ea88986c5927067aa0","size":37743,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/4c321d70067dafa9559271ea88986c5927067aa0"},{"path":"service/calendar/v4/event.go","mode":"100644","type":"blob","sha":"f3a8f408f88ab945047feb057ede90cc480e34b6","size":2560,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/f3a8f408f88ab945047feb057ede90cc480e34b6"},{"path":"service/calendar/v4/model.go","mode":"100644","type":"blob","sha":"a1181396099ae9be791c2d9de3ef5cb23ccf77ad","size":17440,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/a1181396099ae9be791c2d9de3ef5cb23ccf77ad"},{"path":"service/contact","mode":"040000","type":"tree","sha":"aed8c5e6823f1ef1974f6b0ac0fcdc0b5fc90aa9","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/aed8c5e6823f1ef1974f6b0ac0fcdc0b5fc90aa9"},{"path":"service/contact/v3","mode":"040000","type":"tree","sha":"17d4a982c03dd463f31878b4f33896f5250460e6","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/17d4a982c03dd463f31878b4f33896f5250460e6"},{"path":"service/contact/v3/api.go","mode":"100644","type":"blob","sha":"dc50e6ee1456947b932ee7485ae39297d028b4bb","size":35170,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/dc50e6ee1456947b932ee7485ae39297d028b4bb"},{"path":"service/contact/v3/event.go","mode":"100644","type":"blob","sha":"d62dd59c8b21adff4d7ef8883a337f799b67c3b8","size":7291,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/d62dd59c8b21adff4d7ef8883a337f799b67c3b8"},{"path":"service/contact/v3/model.go","mode":"100644","type":"blob","sha":"68601e91bbcb81d0175f4afbc7731dec5fd7eecb","size":24252,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/68601e91bbcb81d0175f4afbc7731dec5fd7eecb"},{"path":"service/doc","mode":"040000","type":"tree","sha":"66bda5b0dda78d66e0bb7bc6b9a7c389ecb15e70","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/66bda5b0dda78d66e0bb7bc6b9a7c389ecb15e70"},{"path":"service/doc/v2","mode":"040000","type":"tree","sha":"8146acf88f57021b6566e665723ee18bc283ce1f","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/8146acf88f57021b6566e665723ee18bc283ce1f"},{"path":"service/doc/v2/api.go","mode":"100644","type":"blob","sha":"46ce19041c5d29ef8c3480a0f071edd3c81e34ac","size":5089,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/46ce19041c5d29ef8c3480a0f071edd3c81e34ac"},{"path":"service/doc/v2/model.go","mode":"100644","type":"blob","sha":"03593f11f202d4cc8db95875fa6c4f5a9d752be7","size":2093,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/03593f11f202d4cc8db95875fa6c4f5a9d752be7"},{"path":"service/docx","mode":"040000","type":"tree","sha":"3a985ee2c4d4ad7aab605837eaf5995b1ac08292","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/3a985ee2c4d4ad7aab605837eaf5995b1ac08292"},{"path":"service/docx/v1","mode":"040000","type":"tree","sha":"1fd5662235c71142d6c4277b1e82deee2aca813f","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/1fd5662235c71142d6c4277b1e82deee2aca813f"},{"path":"service/docx/v1/api.go","mode":"100644","type":"blob","sha":"3995a3981147cc94b29e3cc6efd6fd78521c8800","size":17347,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/3995a3981147cc94b29e3cc6efd6fd78521c8800"},{"path":"service/docx/v1/model.go","mode":"100644","type":"blob","sha":"55054206a22ea676c3a0d38e41c359a447b8a3a9","size":23320,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/55054206a22ea676c3a0d38e41c359a447b8a3a9"},{"path":"service/drive","mode":"040000","type":"tree","sha":"f326ef306e0b02eb6f149f487dc59901f892ea10","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f326ef306e0b02eb6f149f487dc59901f892ea10"},{"path":"service/drive/v1","mode":"040000","type":"tree","sha":"584b7160cf7bd16c35053af260b72ef5d0cf59ba","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/584b7160cf7bd16c35053af260b72ef5d0cf59ba"},{"path":"service/drive/v1/api.go","mode":"100644","type":"blob","sha":"76e99bd8ce3086d533e812480737649aa5cb91a6","size":34865,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/76e99bd8ce3086d533e812480737649aa5cb91a6"},{"path":"service/drive/v1/event.go","mode":"100644","type":"blob","sha":"3f53baa28655a497f1a4835909276ce4a4426ae0","size":4127,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/3f53baa28655a497f1a4835909276ce4a4426ae0"},{"path":"service/drive/v1/model.go","mode":"100644","type":"blob","sha":"da2a6e40801380cb80e581204f4beba8347ed688","size":15681,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/da2a6e40801380cb80e581204f4beba8347ed688"},{"path":"service/drive_explorer","mode":"040000","type":"tree","sha":"f66355a466965157f19d91d1e7e7d0f0e2b0c4fe","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f66355a466965157f19d91d1e7e7d0f0e2b0c4fe"},{"path":"service/drive_explorer/v2","mode":"040000","type":"tree","sha":"4af0287d71e78d62616f6be8dbfe9386d2efdb97","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/4af0287d71e78d62616f6be8dbfe9386d2efdb97"},{"path":"service/drive_explorer/v2/api.go","mode":"100644","type":"blob","sha":"b20ea6087b4f6c218bab85d24b8e21778f316913","size":8803,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/b20ea6087b4f6c218bab85d24b8e21778f316913"},{"path":"service/drive_explorer/v2/model.go","mode":"100644","type":"blob","sha":"bb8fa6e8f875d0d5fd1a72450b48096303c43e4f","size":3366,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/bb8fa6e8f875d0d5fd1a72450b48096303c43e4f"},{"path":"service/drive_permission","mode":"040000","type":"tree","sha":"64524adeca7965f0058ed305c2a9b71301a6b663","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/64524adeca7965f0058ed305c2a9b71301a6b663"},{"path":"service/drive_permission/v1","mode":"040000","type":"tree","sha":"f8111b7a2cb364bd68861be784625975105edb32","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f8111b7a2cb364bd68861be784625975105edb32"},{"path":"service/drive_permission/v1/api.go","mode":"100644","type":"blob","sha":"7844799c6424fe5c3a6c1382a107a68f7b3a5727","size":6162,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7844799c6424fe5c3a6c1382a107a68f7b3a5727"},{"path":"service/drive_permission/v1/model.go","mode":"100644","type":"blob","sha":"d4d36328521281c8eae5e34a819610a3d80dd8cb","size":5441,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/d4d36328521281c8eae5e34a819610a3d80dd8cb"},{"path":"service/drive_permission/v2","mode":"040000","type":"tree","sha":"7adb977ac86dc98607d9376666cf5c57f5bad7c7","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/7adb977ac86dc98607d9376666cf5c57f5bad7c7"},{"path":"service/drive_permission/v2/api.go","mode":"100644","type":"blob","sha":"e04e93b478396aa4b05c96031d98ed71a42e29ee","size":2144,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/e04e93b478396aa4b05c96031d98ed71a42e29ee"},{"path":"service/drive_permission/v2/model.go","mode":"100644","type":"blob","sha":"4ac989fa548fda39b0747ac15b87c3526eb4d49c","size":1611,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/4ac989fa548fda39b0747ac15b87c3526eb4d49c"},{"path":"service/ehr","mode":"040000","type":"tree","sha":"4ed1fd32b35a01f7b5ac454460222f46b33673d6","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/4ed1fd32b35a01f7b5ac454460222f46b33673d6"},{"path":"service/ehr/v1","mode":"040000","type":"tree","sha":"1476cc4f9655115f6fdc4dfe9253e685906d4cbe","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/1476cc4f9655115f6fdc4dfe9253e685906d4cbe"},{"path":"service/ehr/v1/api.go","mode":"100644","type":"blob","sha":"8ff7caad8c1b3a1055c7aa510111daa0f8814cfe","size":3663,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8ff7caad8c1b3a1055c7aa510111daa0f8814cfe"},{"path":"service/ehr/v1/model.go","mode":"100644","type":"blob","sha":"55be54a15eea9a10475cdb6a8d621dcb6c36e976","size":9848,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/55be54a15eea9a10475cdb6a8d621dcb6c36e976"},{"path":"service/face_verify","mode":"040000","type":"tree","sha":"2addbda31a278e0448a594aa03b9a7ad0aa96b43","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/2addbda31a278e0448a594aa03b9a7ad0aa96b43"},{"path":"service/face_verify/v1","mode":"040000","type":"tree","sha":"d1acf616d284a80d0a8298258b9ffdd90105407f","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/d1acf616d284a80d0a8298258b9ffdd90105407f"},{"path":"service/face_verify/v1/api.go","mode":"100644","type":"blob","sha":"ea1b63c01c93db6b08e61f7fecf2bee37a367f6c","size":4252,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/ea1b63c01c93db6b08e61f7fecf2bee37a367f6c"},{"path":"service/face_verify/v1/model.go","mode":"100644","type":"blob","sha":"1dd30fe3a8c18c2f39e0e46c6ab6dd2be61f9e98","size":395,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/1dd30fe3a8c18c2f39e0e46c6ab6dd2be61f9e98"},{"path":"service/human_authentication","mode":"040000","type":"tree","sha":"349cba18100caeb7e94c4f00d2c7fa95c9858f06","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/349cba18100caeb7e94c4f00d2c7fa95c9858f06"},{"path":"service/human_authentication/v1","mode":"040000","type":"tree","sha":"d2c76460e5621ecd468c6f4ef3931c07a1a8a13d","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/d2c76460e5621ecd468c6f4ef3931c07a1a8a13d"},{"path":"service/human_authentication/v1/api.go","mode":"100644","type":"blob","sha":"a4bfcab4d0d84d375fa6cfbdc8b85d2859a4b45c","size":1778,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/a4bfcab4d0d84d375fa6cfbdc8b85d2859a4b45c"},{"path":"service/human_authentication/v1/model.go","mode":"100644","type":"blob","sha":"d0a0ce1c3dd3e5ade493b9fddb0096057858a375","size":984,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/d0a0ce1c3dd3e5ade493b9fddb0096057858a375"},{"path":"service/im","mode":"040000","type":"tree","sha":"94e689e37da7fbf50d565d40097c03d12c318a8f","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/94e689e37da7fbf50d565d40097c03d12c318a8f"},{"path":"service/im/v1","mode":"040000","type":"tree","sha":"34b5998da168b39c5cf5eae07b2308449ae89509","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/34b5998da168b39c5cf5eae07b2308449ae89509"},{"path":"service/im/v1/api.go","mode":"100644","type":"blob","sha":"97794b22c82f27b02b763562490d72c04b4303c2","size":57896,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/97794b22c82f27b02b763562490d72c04b4303c2"},{"path":"service/im/v1/event.go","mode":"100644","type":"blob","sha":"74e775485de0252bf0ae1e1971481462c02d6354","size":6688,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/74e775485de0252bf0ae1e1971481462c02d6354"},{"path":"service/im/v1/model.go","mode":"100644","type":"blob","sha":"1be9a505d1ffb77e39d6dd0c53e79ff319944d40","size":43810,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/1be9a505d1ffb77e39d6dd0c53e79ff319944d40"},{"path":"service/image","mode":"040000","type":"tree","sha":"05bdab4bf410730a13b439e186435a186fbac11d","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/05bdab4bf410730a13b439e186435a186fbac11d"},{"path":"service/image/v4","mode":"040000","type":"tree","sha":"2a5b7c702c89d7bc2f8c74a7e980c5237fffb2dd","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/2a5b7c702c89d7bc2f8c74a7e980c5237fffb2dd"},{"path":"service/image/v4/api.go","mode":"100644","type":"blob","sha":"8fb98eb1a02583a8302c21bb7069e6f0c978951a","size":2456,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8fb98eb1a02583a8302c21bb7069e6f0c978951a"},{"path":"service/image/v4/model.go","mode":"100644","type":"blob","sha":"47219b71ab9beeadf761befb3598b7cba854242e","size":365,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/47219b71ab9beeadf761befb3598b7cba854242e"},{"path":"service/mail","mode":"040000","type":"tree","sha":"221c0518fea5630eeecb096d1e435228a9121cf1","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/221c0518fea5630eeecb096d1e435228a9121cf1"},{"path":"service/mail/v1","mode":"040000","type":"tree","sha":"5f2d17ecf0f4f2864707b40355398d64d34e8729","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/5f2d17ecf0f4f2864707b40355398d64d34e8729"},{"path":"service/mail/v1/api.go","mode":"100644","type":"blob","sha":"ee131e8e46117d5f7514cd209e03f1457464e5a3","size":33544,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/ee131e8e46117d5f7514cd209e03f1457464e5a3"},{"path":"service/mail/v1/model.go","mode":"100644","type":"blob","sha":"5a9a225ca4f8506c6b6883ce593d1fd0ef0755cb","size":3737,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/5a9a225ca4f8506c6b6883ce593d1fd0ef0755cb"},{"path":"service/meeting_room","mode":"040000","type":"tree","sha":"772fb3cce66c2902accda6fe9813b1d840c3b9ec","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/772fb3cce66c2902accda6fe9813b1d840c3b9ec"},{"path":"service/meeting_room/v1","mode":"040000","type":"tree","sha":"5eb0e9c138edbb04756431d3408a98ff2e4397ec","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/5eb0e9c138edbb04756431d3408a98ff2e4397ec"},{"path":"service/meeting_room/v1/api.go","mode":"100644","type":"blob","sha":"8315735d808a645a7c10e117fbbf219a5b75d0d0","size":17512,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8315735d808a645a7c10e117fbbf219a5b75d0d0"},{"path":"service/meeting_room/v1/event.go","mode":"100644","type":"blob","sha":"e1e0eca0300ce76f7cf034d746c51ad9806e3907","size":4813,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/e1e0eca0300ce76f7cf034d746c51ad9806e3907"},{"path":"service/meeting_room/v1/model.go","mode":"100644","type":"blob","sha":"7940220ecc3da118ea4c1182090e60a90de6a620","size":11548,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7940220ecc3da118ea4c1182090e60a90de6a620"},{"path":"service/optical_char_recognition","mode":"040000","type":"tree","sha":"c9e43db7e7fba3e291d7a753983612377452c48d","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/c9e43db7e7fba3e291d7a753983612377452c48d"},{"path":"service/optical_char_recognition/v1","mode":"040000","type":"tree","sha":"c7f7e74a2e677bc0c56d12452931eb4f5cc2c4dc","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/c7f7e74a2e677bc0c56d12452931eb4f5cc2c4dc"},{"path":"service/optical_char_recognition/v1/api.go","mode":"100644","type":"blob","sha":"4a872b4cb355426b34586f9cfb2722fee76daace","size":1397,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/4a872b4cb355426b34586f9cfb2722fee76daace"},{"path":"service/optical_char_recognition/v1/model.go","mode":"100644","type":"blob","sha":"b3b697fd9baa96278d96496f0a5bab225a430d12","size":515,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/b3b697fd9baa96278d96496f0a5bab225a430d12"},{"path":"service/search","mode":"040000","type":"tree","sha":"bda8ea982427ae49654fc1405f2edc6f32c5b758","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/bda8ea982427ae49654fc1405f2edc6f32c5b758"},{"path":"service/search/v2","mode":"040000","type":"tree","sha":"6ab14d7256b783021f9dffbe91d8e613f6a3cdf9","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/6ab14d7256b783021f9dffbe91d8e613f6a3cdf9"},{"path":"service/search/v2/api.go","mode":"100644","type":"blob","sha":"55336ab4bb1eeed4480e32fe07c867c548922080","size":9424,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/55336ab4bb1eeed4480e32fe07c867c548922080"},{"path":"service/search/v2/model.go","mode":"100644","type":"blob","sha":"b2a4ef3fd96057372ea7b01ef104e219adc85570","size":5710,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/b2a4ef3fd96057372ea7b01ef104e219adc85570"},{"path":"service/sheets","mode":"040000","type":"tree","sha":"1547b28ae507219de98339ac0e12d247f520b68a","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/1547b28ae507219de98339ac0e12d247f520b68a"},{"path":"service/sheets/v2","mode":"040000","type":"tree","sha":"651297773febaf0454ca22d3222c68bf01364410","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/651297773febaf0454ca22d3222c68bf01364410"},{"path":"service/sheets/v2/api.go","mode":"100644","type":"blob","sha":"82b6c989c9ed646fed0e0cf4052b7ee36313f5a0","size":44494,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/82b6c989c9ed646fed0e0cf4052b7ee36313f5a0"},{"path":"service/sheets/v2/model.go","mode":"100644","type":"blob","sha":"a214917ef88bb4f60478dca1a0cef1f248f412c5","size":34000,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/a214917ef88bb4f60478dca1a0cef1f248f412c5"},{"path":"service/speech_to_text","mode":"040000","type":"tree","sha":"df451403f3a97234a27f37e93bee95269f5f5f71","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/df451403f3a97234a27f37e93bee95269f5f5f71"},{"path":"service/speech_to_text/v1","mode":"040000","type":"tree","sha":"27c19e06e701d65da479ebe1bf1435743ca86e07","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/27c19e06e701d65da479ebe1bf1435743ca86e07"},{"path":"service/speech_to_text/v1/api.go","mode":"100644","type":"blob","sha":"c0c7d24df5fd187299e3abfed51127503300d47e","size":2230,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/c0c7d24df5fd187299e3abfed51127503300d47e"},{"path":"service/speech_to_text/v1/model.go","mode":"100644","type":"blob","sha":"cf175e6289444f2a206ce9c92325c1e1a30a9952","size":2336,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/cf175e6289444f2a206ce9c92325c1e1a30a9952"},{"path":"service/suite","mode":"040000","type":"tree","sha":"8ee4a0dec2e8fc705e8d3bd278d4788b4d7bbd3a","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/8ee4a0dec2e8fc705e8d3bd278d4788b4d7bbd3a"},{"path":"service/suite/v1","mode":"040000","type":"tree","sha":"2a57eedbc49742a0fec134a55937e4b06a6bd32b","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/2a57eedbc49742a0fec134a55937e4b06a6bd32b"},{"path":"service/suite/v1/api.go","mode":"100644","type":"blob","sha":"5524b5c82e5f8fe02b563fea3d7c5f863b563bf7","size":2082,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/5524b5c82e5f8fe02b563fea3d7c5f863b563bf7"},{"path":"service/suite/v1/model.go","mode":"100644","type":"blob","sha":"f7feed4d798744fb4b3a057c8b014333dc968378","size":2622,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/f7feed4d798744fb4b3a057c8b014333dc968378"},{"path":"service/task","mode":"040000","type":"tree","sha":"43bbb4c075808167d60f28149d8f52c8876ad16a","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/43bbb4c075808167d60f28149d8f52c8876ad16a"},{"path":"service/task/v1","mode":"040000","type":"tree","sha":"f57ef0310794b6bd80473d3d997b28effc570f9e","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/f57ef0310794b6bd80473d3d997b28effc570f9e"},{"path":"service/task/v1/api.go","mode":"100644","type":"blob","sha":"6114526b0985634f859ee988c01c5213bf99e245","size":23922,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/6114526b0985634f859ee988c01c5213bf99e245"},{"path":"service/task/v1/event.go","mode":"100644","type":"blob","sha":"d4c0d715c36beb0b722fc2d7fba7b2f32c807e82","size":1306,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/d4c0d715c36beb0b722fc2d7fba7b2f32c807e82"},{"path":"service/task/v1/model.go","mode":"100644","type":"blob","sha":"8368df1329a53fc3b2eea83fd9bcf1265dafcc6a","size":5721,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/8368df1329a53fc3b2eea83fd9bcf1265dafcc6a"},{"path":"service/tenant","mode":"040000","type":"tree","sha":"b8f66444972aea61df7b1b9c27d5085f7d123400","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/b8f66444972aea61df7b1b9c27d5085f7d123400"},{"path":"service/tenant/v2","mode":"040000","type":"tree","sha":"0ebbae41f5a4ce15ca439ce99619b1438eaadd3b","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/0ebbae41f5a4ce15ca439ce99619b1438eaadd3b"},{"path":"service/tenant/v2/api.go","mode":"100644","type":"blob","sha":"bc65a8c6fa56ccc625a8c174e4145a4e4b18503f","size":1242,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/bc65a8c6fa56ccc625a8c174e4145a4e4b18503f"},{"path":"service/tenant/v2/model.go","mode":"100644","type":"blob","sha":"0ca6273547c04441e8b0d89e03c7296bc97c4fd1","size":1080,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/0ca6273547c04441e8b0d89e03c7296bc97c4fd1"},{"path":"service/translation","mode":"040000","type":"tree","sha":"fe4841a955ab8e716b344f4442d8bc7ef51e721f","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/fe4841a955ab8e716b344f4442d8bc7ef51e721f"},{"path":"service/translation/v1","mode":"040000","type":"tree","sha":"6a05dbafc22a31104a17967d0d77c205a4b03a05","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/6a05dbafc22a31104a17967d0d77c205a4b03a05"},{"path":"service/translation/v1/api.go","mode":"100644","type":"blob","sha":"ecb7bfe0b3533fd32008c07f2a9d73df15a268da","size":2002,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/ecb7bfe0b3533fd32008c07f2a9d73df15a268da"},{"path":"service/translation/v1/model.go","mode":"100644","type":"blob","sha":"16ffaca35d89531fc0a92c4ae21e857ee3795a08","size":1297,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/16ffaca35d89531fc0a92c4ae21e857ee3795a08"},{"path":"service/vc","mode":"040000","type":"tree","sha":"be03e9b58dfef1dc11dd0c225c99871608ab27ca","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/be03e9b58dfef1dc11dd0c225c99871608ab27ca"},{"path":"service/vc/v1","mode":"040000","type":"tree","sha":"c97e1f43d68900f935bd07e9ca239da918238903","url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/trees/c97e1f43d68900f935bd07e9ca239da918238903"},{"path":"service/vc/v1/api.go","mode":"100644","type":"blob","sha":"c520dcf2fc2e2eedf67f3031191703706cfdd7a5","size":24801,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/c520dcf2fc2e2eedf67f3031191703706cfdd7a5"},{"path":"service/vc/v1/event.go","mode":"100644","type":"blob","sha":"ce9e35bffb3b3b1d69f0f888e875415f6f761b62","size":5642,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/ce9e35bffb3b3b1d69f0f888e875415f6f761b62"},{"path":"service/vc/v1/model.go","mode":"100644","type":"blob","sha":"7d01fd69deed58b0fda78bc63f610ae77fc78624","size":21628,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/7d01fd69deed58b0fda78bc63f610ae77fc78624"},{"path":"test.pdf","mode":"100644","type":"blob","sha":"1d2cfc637c19a01d166f7b35d9a3295b99a1d7ab","size":36023,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/1d2cfc637c19a01d166f7b35d9a3295b99a1d7ab"},{"path":"test.png","mode":"100644","type":"blob","sha":"bea74ce5da54127bcc2f5060b0dd9741960a91df","size":10799,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/bea74ce5da54127bcc2f5060b0dd9741960a91df"},{"path":"test_download.pdf","mode":"100755","type":"blob","sha":"1d2cfc637c19a01d166f7b35d9a3295b99a1d7ab","size":36023,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/1d2cfc637c19a01d166f7b35d9a3295b99a1d7ab"},{"path":"test_download.png","mode":"100644","type":"blob","sha":"bea74ce5da54127bcc2f5060b0dd9741960a91df","size":10799,"url":"https://api.github.com/repos/larksuite/oapi-sdk-go/git/blobs/bea74ce5da54127bcc2f5060b0dd9741960a91df"}],"truncated":false} \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/lark-sdk-v3-tree.json b/docs/02-架构设计/专项设计/.research/lark-sdk-v3-tree.json new file mode 100644 index 0000000..905b6e6 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/lark-sdk-v3-tree.json @@ -0,0 +1 @@ +{"message":"Not Found","documentation_url":"https://docs.github.com/rest/git/trees#get-a-tree","status":"404"} \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/larkrs-card-action-handler-example.rs b/docs/02-架构设计/专项设计/.research/larkrs-card-action-handler-example.rs new file mode 100644 index 0000000..65ba865 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/larkrs-card-action-handler-example.rs @@ -0,0 +1,46 @@ +use std::collections::HashMap; + +use larksuite_oapi_sdk_rs::{CardAction, CardActionHandler, EventReq}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let handler = + CardActionHandler::new("verification_token", "", |action: CardAction| async move { + println!("card clicked by open_id={}", action.open_id); + println!("action value: {:?}", action.action.value); + + Ok(serde_json::json!({ + "toast": { + "type": "info", + "content": "Handled by Rust" + } + })) + }) + .skip_sign_verify(); + + let payload = serde_json::json!({ + "open_id": "ou_xxx", + "open_message_id": "om_xxx", + "open_chat_id": "oc_xxx", + "tenant_key": "tenant_xxx", + "token": "verification_token", + "action": { + "tag": "button", + "value": { + "action": "acknowledge", + "ticket_id": "INC-1001" + } + } + }); + + let req = EventReq { + headers: HashMap::new(), + body: serde_json::to_vec(&payload)?, + request_uri: "/webhook/card".to_string(), + }; + + let resp = handler.handle(req).await; + println!("response status: {}", resp.status_code); + println!("response body: {}", String::from_utf8_lossy(&resp.body)); + Ok(()) +} diff --git a/docs/02-架构设计/专项设计/.research/larkrs-card-protocol.md b/docs/02-架构设计/专项设计/.research/larkrs-card-protocol.md new file mode 100644 index 0000000..1f8897a --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/larkrs-card-protocol.md @@ -0,0 +1,132 @@ +# Card protocol alignment + +Message cards are an embedded JSON protocol: IM and CardKit APIs transport a +card as opaque content, so REST route parity cannot establish card-schema +coverage. This guide defines the required reference and verification workflow +for every change to the SDK's card support. + +## Source roles + +Use each reference for the thing it can prove: + +1. **Official Lark card documentation** is the wire-contract authority. It + defines Card JSON 1.0 and 2.0 roots, component tags, field names, value + domains, constraints, client-version requirements, and deprecations. +2. **The Lark CLI card references and fixtures** are an operational + cross-check. They supply representative valid payloads and callback/update + lifecycle details, including `card.action.trigger`. +3. **The official Go SDK** is an implementation cross-check and compatibility + reference. Its card model may deliberately represent a different layer or + expose a different surface from a particular documentation page; an apparent + gap is a discrepancy to investigate, not evidence that the Go SDK is + obsolete or incomplete. +4. **The existing Rust SDK** is a regression baseline only. An existing + serializer or a test that repeats its output is not evidence of protocol + correctness. + +When sources disagree, record the discrepancy in the protocol inventory and +investigate its version, schema, and transport context. The official +documentation defines the wire contract, while current Go SDK and CLI behavior +are required implementation evidence; do not assume that either implementation +is stale, or silently preserve a conflicting spelling. + +## Required workflow + +Before implementing or accepting an embedded-protocol change: + +1. Classify it. REST resources and typed events use the Go compatibility + workflow; message cards use this workflow. A change touching both must pass + both sets of checks. +2. Refresh the pinned Go SDK and Lark CLI references, and record the source + revision plus official-documentation URLs and access date in the protocol + inventory. +3. Update the version-specific inventory before implementation. Card JSON 1.0 + and 2.0 have separate roots and must have separate component, field, + enumeration, validation, and client-version entries. +4. Add or update exact JSON fixtures for every changed wire shape. Fixtures + must cover the tag, required fields, optional fields, enum spelling, and at + least one invalid cross-field combination where the protocol defines one. +5. Cross-check each changed card component against the CLI and Go references. + Record an absence from either reference as an absence, not as permission to + omit a documented capability. +6. Run the aggregate reference-alignment check and the focused Rust tests. A + new card feature cannot merge with only a builder unit test that asserts the + implementation's own JSON. + +## Callback and update coverage + +For callback-bearing Card JSON 1.0 components, the inventory and fixtures must +also cover the corresponding `card.action.trigger` fields, callback value or +form-value shape, and the three-second acknowledgement/update response. The +SDK may expose outbound card construction and inbound event types independently, +but their protocol relationship must be tested together. + +Card JSON 2.0 and CardKit are separate tracks. CardKit's typed Card JSON 2.0 +document transport and ordered text-content streaming have their own manifest +and transport fixtures; they are not optional extensions of a Card JSON 1.0 +fixture. CardKit settings, element mutation, and batch-action helpers remain +separate protocol surfaces with their own exact transport fixtures. + +## Gate status + +The Card JSON 1.0 inventory and fixture corpus extend the deterministic +`reference-alignment-check` command with card fixtures and validation. The +aggregate command receives pinned Go and Lark CLI checkout paths, verifies +their revisions, and checks recorded source artifacts before it runs the Rust +fixture tests. Card checks remain one part of this aggregate gate, alongside +REST, event, and future protocol alignment; they do not get a separate command +or CI job. + +The Card JSON 2.0 inventory is separate from the 1.0 corpus. It records the +incompatible root, every documented handwritten component, the root and +cross-field constraints that the typed AST must enforce, and exact valid and +invalid wire fixtures. `partial` means that a component has a typed wire shape +but is not yet eligible to be marked complete: its optional fields, validation, +and exact fixtures must still be covered. The documented Card-Builder-only +recycling container is recorded as a known divergence because it has no +handwritten Card JSON tag. + +Card Builder is not a Card JSON AST. The SDK does not replicate the visual +editor or represent builder-only components. It supports delivery of a published +template through `card::template::TemplateMessage`. `T` is a caller-defined +`Serialize` variable object, validated before delivery; `TemplateMessage` uses +`JsonValue` by default as the dynamic JSON escape hatch. The template-delivery +manifest keeps that envelope, its IM transport, and the builder-only +recycling-container example in the same aggregate alignment check. + +`implemented` is reserved for a component whose field-level inventory, valid +fixture, invalid constraint fixture where applicable, and direct serde +round-trip coverage are all present. A complete component group does not imply +that the incompatible Card JSON 2.0 root or unrelated interaction and update +surfaces are complete. + +## CardKit document, mutation, and instance coverage + +`card::cardkit::CardDocument` accepts only a locally validated Card JSON 2.0 +document and emits CardKit's escaped `type: "card_json"` payload. The typed +`LarkClient::cardkit_cards()` helper covers entity creation and creates an +ordered update session for full replacement and full-content updates on one +text or Markdown element. + +Creation returns a CardKit entity ID, not an IM message. Send it through an IM +`interactive` request with `card::cardkit::CardEntityMessage`, which emits the +separate `{"type":"card","data":{"card_id":"..."}}` content envelope. +Do not reuse the CardKit `card_json` REST envelope for this step. + +Each CardKit update carries a caller-provided idempotency key and a positive +sequence. One `CardKitUpdateSession` owns the sequence across document and +content, settings, element, and batch updates, advancing it only after a +successful request; a failed update can therefore be retried with the same +sequence and key. Content updates intentionally send the complete current +value: Lark renders a prefix extension as a typewriter update and otherwise +replaces the element content. + +The typed mutation surface retains Card JSON 2.0 settings and full elements, +validates partial-element and insertion constraints, and encodes the nested +objects required by CardKit batch actions exactly once. It also covers template +instance creation and variable updates (a name-keyed object with open JSON +values). The pinned Go CardKit source does not expose Card Instance resources, +so those endpoints are normatively tracked against the official documentation +rather than unrelated Go template models. The raw generated resource methods +remain available for any future CardKit endpoint not yet given a typed +composition layer. diff --git a/docs/02-架构设计/专项设计/.research/larkrs-cards.md b/docs/02-架构设计/专项设计/.research/larkrs-cards.md new file mode 100644 index 0000000..0165bab --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/larkrs-cards.md @@ -0,0 +1,105 @@ +# Cards + +The SDK keeps Card JSON 1.0, Card JSON 2.0, CardKit, and published Card Builder +templates as separate protocols. Choose the smallest surface that matches the +message lifecycle you need. + +## Send a Card JSON document + +Build a versioned Card JSON value, then create a `CardDocument` before passing +it to IM or channel message helpers. `CardDocument::new` validates the document; +outbound interactive-card helpers deliberately reject arbitrary serializable +values. + +[`examples/card_send.rs`](../examples/card_send.rs) sends a validated Card JSON +1.0 document through the IM create-message API: + +```sh +APP_ID=... APP_SECRET=... CHAT_ID=oc_... cargo run --example card_send +``` + +Use `card::v1` for Card JSON 1.0. Use `card::v2` for Card JSON 2.0; its root, +shared-card requirements, and element-identifier rules are incompatible with +1.0. + +## Author Card JSON 2.0 layouts safely + +`CardDocument::new` remains the final strict protocol gate. For dynamic +authoring, `CardDocument::new_with_diagnostic` preserves the original +`ValidationError` and also returns a stable code, a JSON Pointer, the violated +constraint, and relevant legal values. Do not parse its display string. + +`ColumnSet` has constructors for the three legal width branches. They emit the +wire shape required by Card JSON 2.0 and leave document validation enabled: + +```rust +use larksuite_oapi_sdk_rs::card::v2::{ + AutoColumn, Body, Card, CardDocument, ColumnSet, ColumnWeight, Config, + Element, FixedColumn, FixedColumnWidth, Markdown, WeightedColumn, +}; + +let automatic = Element::ColumnSet(ColumnSet::automatic([ + AutoColumn::new().element(Element::Markdown(Markdown::new("auto"))), +])); + +let fixed = Element::ColumnSet(ColumnSet::fixed([ + FixedColumn::new(FixedColumnWidth::pixels(160)?) + .element(Element::Markdown(Markdown::new("fixed"))), +])); + +let weighted = Element::ColumnSet(ColumnSet::weighted([ + WeightedColumn::new(ColumnWeight::new(3)?) + .element(Element::Markdown(Markdown::new("weighted"))), +])); + +let card = Card::new() + .config(Config::new().update_multi()) + .body(Body::new().element(automatic).element(fixed).element(weighted)); +let _document = CardDocument::new(card)?; +# Ok::<(), larksuite_oapi_sdk_rs::card::v2::ValidationError>(()) +``` + +The `Padding`, `Margin`, `Spacing::pixels`, `FixedColumnWidth`, and +`ColumnWeight` constructors encode the documented finite pixel/range grammar. +Existing string-oriented builders remain supported for compatibility and are +still validated by `CardDocument`. + +## Stream a CardKit document + +CardKit accepts a validated Card JSON 2.0 `card::cardkit::CardDocument`. +`LarkClient::cardkit_cards()` creates the entity; send that entity once with a +`card::cardkit::CardEntityMessage` through IM before beginning ordered updates. +Keep one update session for full-document, settings, element, and content +updates; each successful mutation advances its sequence. CardKit entities have +the documented 14-day lifetime and may be sent only once. + +[`examples/cardkit_stream.rs`](../examples/cardkit_stream.rs) creates a +streaming CardKit card and replaces a Markdown element's complete content: + +```sh +APP_ID=... APP_SECRET=... CHAT_ID=oc_... cargo run --example cardkit_stream +``` + +## Send a published Card Builder template + +Card Builder's editor and builder-only components are not a Card JSON AST. +Send a published template with `TemplateMessage`, where `T: Serialize` is +your application's template-variable object. The SDK requires that it serialize +to a JSON object; `TemplateMessage` remains available for dynamic +bindings. + +[`examples/card_template_send.rs`](../examples/card_template_send.rs) sends a +typed published template: + +```sh +APP_ID=... APP_SECRET=... CHAT_ID=oc_... CARD_TEMPLATE_ID=... \ + cargo run --example card_template_send +``` + +## Handle callbacks + +Card callbacks are inbound events. Use +[`examples/card_action_handler.rs`](../examples/card_action_handler.rs) for a +typed `CardActionHandler` that returns a callback response. Application-level +authorization, routing, deduplication, and state management remain the +application's responsibility. diff --git a/docs/02-架构设计/专项设计/.research/larkrs-channel-send.rs b/docs/02-架构设计/专项设计/.research/larkrs-channel-send.rs new file mode 100644 index 0000000..186696a --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/larkrs-channel-send.rs @@ -0,0 +1,30 @@ +use larksuite_oapi_sdk_rs::channel::{Channel, ChannelPolicy}; +use larksuite_oapi_sdk_rs::{EventDispatcher, LarkClient, LarkError}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let app_id = std::env::var("APP_ID").expect("APP_ID env var required"); + let app_secret = std::env::var("APP_SECRET").expect("APP_SECRET env var required"); + let verification_token = std::env::var("VERIFICATION_TOKEN").unwrap_or_default(); + let encrypt_key = std::env::var("ENCRYPT_KEY").unwrap_or_default(); + + let client = LarkClient::builder(app_id, app_secret).build()?; + let dispatcher = EventDispatcher::new(verification_token, encrypt_key); + + Channel::builder(&client, dispatcher) + .policy(ChannelPolicy::default().require_mention(false)) + .on_message(|message| async move { + println!("message_id: {}", message.message_id); + println!("raw content: {}", message.content); + println!("normalized text: {}", message.text.unwrap_or_default()); + for resource in message.resources { + println!("resource: {} {}", resource.resource_type, resource.file_key); + } + Ok::<(), LarkError>(()) + }) + .build() + .start() + .await?; + + Ok(()) +} diff --git a/docs/02-架构设计/专项设计/.research/larkrs-events-and-channels.md b/docs/02-架构设计/专项设计/.research/larkrs-events-and-channels.md new file mode 100644 index 0000000..7625f75 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/larkrs-events-and-channels.md @@ -0,0 +1,158 @@ +# Events, WebSockets, and channels + +Webhook handlers use `EventDispatcher` for typed event callbacks, signature +verification, and encrypted payload processing. The optional `ws` feature adds +long connections for event delivery; see +[`examples/ws_client.rs`](../examples/ws_client.rs). + +Trusted user channels match the upstream Go SDK behavior. Configure the channel +before obtaining its control handle, start the client, then bind a user token +after the connection becomes ready. See +[`examples/ws_client.rs`](../examples/ws_client.rs) for the connection lifecycle +and [`examples/channel_send.rs`](../examples/channel_send.rs) for higher-level +messages. + +```rust,no_run +use std::collections::HashMap; +use std::time::Duration; + +# use larksuite_oapi_sdk_rs::{EventDispatcher, LarkClient}; +# #[tokio::main] +# async fn main() -> Result<(), Box> { +# let client = LarkClient::builder("APP_ID", "APP_SECRET").build()?; +# let dispatcher = EventDispatcher::new("VERIFICATION_TOKEN", "ENCRYPT_KEY"); +let ws_client = client + .ws_client(dispatcher) + .channel_tag("trusted_channel") + .websocket_query_params(HashMap::from([("env".into(), "staging".into())])); +let control = ws_client.control(); +let task = tokio::spawn(ws_client.start()); + +while control.connection_id().is_none() { + tokio::time::sleep(Duration::from_millis(50)).await; +} +control.attach_user("USER_ACCESS_TOKEN").await?; +// Later, before dropping the user session: +// control.detach_user("USER_ACCESS_TOKEN").await?; +control.close_and_wait().await; +task.await??; +# Ok(()) +# } +``` + +`WsClientControl::close` requests shutdown without waiting, which is safe from +lifecycle callbacks. `close_and_wait` is for external teardown and waits until +the running client has stopped. The client honors a finite reconnect count from +the gateway bootstrap configuration, treats an inbound-silent connection as +failed after two ping intervals plus five seconds, and reconnects after either +that liveness timeout or a timed-out connection write. It bounds each +connection write to ten seconds by default and accepts `write_timeout` for a +different bound. Event callbacks run independently of the receive loop, so a +slow callback does not mask a silent gateway. Use +`websocket_connector` when a custom proxy, TLS, or test dial transport is +needed; it affects only the WebSocket gateway dial, not the bootstrap HTTP +client. + +## Calendar share-token joins + +Calendar invitations provide an opaque share token through their link, QR +code, or RSVP card. Join with that token; the endpoint deliberately has no +event-ID alternative. + +```rust,no_run +use larksuite_oapi_sdk_rs::service::calendar::v4::JoinCalendarEventReqBody; +use larksuite_oapi_sdk_rs::{LarkClient, RequestOption}; + +# async fn example() -> Result<(), larksuite_oapi_sdk_rs::LarkError> { +let client = LarkClient::builder("APP_ID", "APP_SECRET").build()?; +client + .calendar() + .calendar + .join_event( + &JoinCalendarEventReqBody::new("CALENDAR_SHARE_TOKEN"), + &RequestOption::default(), + ) + .await?; +# Ok(()) +# } +``` + +Use the optional `axum` feature for Axum HTTP adapters. The optional `messaging` +feature provides outbound send, reply, update, upload, and download operations +without WebSocket dependencies. The optional `channel` feature adds inbound +message normalization and runtime policy controls and enables both `messaging` +and `ws`. + +## Replies + +`ChannelMessaging::send` preserves convenience behavior: a reply whose target has +disappeared may retry as a top-level send. For workflows that must never escape +a thread, use `ChannelMessaging::reply` or `ChannelMessaging::reply_in_thread`. +Both methods only call the reply endpoint and return its error unchanged. Set +`uuid` to opt into Lark's one-hour idempotency window; automatic split messages +derive distinct UUIDs, so the supplied UUID must leave room for a `-N` suffix. + +```rust,no_run +use larksuite_oapi_sdk_rs::channel::SendInput; +use larksuite_oapi_sdk_rs::{LarkClient, RequestOption}; + +# async fn run() -> Result<(), Box> { +let client = LarkClient::builder("APP_ID", "APP_SECRET").build()?; +let messaging = client.channel_messaging(); +messaging + .reply_in_thread( + "om_parent_message", + &SendInput { + markdown: Some("A reply that stays in the topic".into()), + uuid: Some("order-42-status".into()), + ..Default::default() + }, + &RequestOption::default(), + ) + .await?; +# Ok(()) +# } +``` + +## Message updates + +Use `ChannelMessaging::edit_text` to update a text message. Use +`ChannelMessaging::edit_card` only for validated interactive-card documents; it +uses the separate card patch operation. See +[`examples/card_send.rs`](../examples/card_send.rs) for Card JSON delivery and +[`examples/card_action_handler.rs`](../examples/card_action_handler.rs) for +interactive callback responses. + +```rust,no_run +use larksuite_oapi_sdk_rs::card::v1::{Card, CardDocument, Div, Element, Header, Text}; +use larksuite_oapi_sdk_rs::{LarkClient, RequestOption}; + +# async fn run() -> Result<(), Box> { +let client = LarkClient::builder("APP_ID", "APP_SECRET").build()?; +let messaging = client.channel_messaging(); +let card = CardDocument::new( + Card::new() + .header(Header::new("Done")) + .element(Element::Div(Div::new(Text::lark_md("Complete")))), +)?; + +messaging + .edit_text("om_text_message", "Updated status", &RequestOption::default()) + .await?; +messaging + .edit_card( + "om_card_message", + &card, + &RequestOption::default(), + ) + .await?; +# Ok(()) +# } +``` + +## Cards + +Use `card::v1` or `card::v2` to build versioned Card JSON, and validate it with +their `CardDocument` types before sending. `CardActionHandler` handles callback +payloads. See the [Cards guide](cards.md) for CardKit and published Card Builder +templates. diff --git a/docs/02-架构设计/专项设计/.research/larkrs-readme.md b/docs/02-架构设计/专项设计/.research/larkrs-readme.md new file mode 100644 index 0000000..77bc1a9 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/larkrs-readme.md @@ -0,0 +1,219 @@ +# larksuite-oapi-sdk-rs + +[![Crates.io](https://img.shields.io/crates/v/larksuite-oapi-sdk-rs)](https://crates.io/crates/larksuite-oapi-sdk-rs) +[![Docs](https://docs.rs/larksuite-oapi-sdk-rs/badge.svg)](https://docs.rs/larksuite-oapi-sdk-rs) +[![CI](https://github.com/adamcavendish/larksuite-oapi-sdk-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/adamcavendish/larksuite-oapi-sdk-rs/actions/workflows/ci.yml) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) + +Non-official, Tokio-native Rust SDK for Lark / Feishu (飞书) Open Platform +APIs, bots, events, WebSockets, and interactive cards. It follows the official +[Go SDK](https://github.com/larksuite/oapi-sdk-go) closely while providing +idiomatic Rust configuration, typed REST resources, event handling, and optional +WebSocket, Axum, and channel integration. + +## Build with Lark / Feishu + +- **Build a bot:** receive typed message events and reply through the IM API. +- **Receive events:** use WebSocket long connections without a public callback + URL, or verify and dispatch HTTP webhooks. +- **Automate Open Platform workflows:** use typed REST resources for messages, + calendars, documents, Base, approvals, and more. + +New to the platform? Start with the +[build a Lark / Feishu bot tutorial](docs/tutorial-build-a-feishu-bot.md). It +creates a bot that replies to received messages over a WebSocket long +connection. + +## Highlights + +- Tenant, user, app, and marketplace token acquisition with configurable + caching and retry behavior. +- Typed REST resources with query inputs, upload/download helpers, and lazy + iterators for supported paginated endpoints. +- Raw requests and a Go-compatibility bridge for endpoints not yet promoted to + dedicated Rust resources. +- Webhook event dispatch with signature verification and encrypted payload + decryption. +- Optional WebSocket long connections, Axum handlers, and higher-level channel + message helpers. + +## Install + +```toml +[dependencies] +larksuite-oapi-sdk-rs = "0.3.12" +``` + +The minimum supported Rust version is 1.95.0. + +## Quick Start + +Build a client once, then call a typed resource. The SDK handles tenant-token +acquisition and request authentication for this endpoint. + +```rust,no_run +use larksuite_oapi_sdk_rs::service::im::v1::CreateMessageReqBody; +use larksuite_oapi_sdk_rs::{LarkClient, RequestOption}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = LarkClient::builder("APP_ID", "APP_SECRET").build()?; + let body = CreateMessageReqBody { + receive_id: Some("CHAT_ID".to_string()), + msg_type: Some("text".to_string()), + content: Some(r#"{"text":"Hello from Rust"}"#.to_string()), + uuid: None, + }; + + let response = client + .im() + .message + .create("chat_id", &body, &RequestOption::default()) + .await?; + + println!("success: {}", response.success()); + Ok(()) +} +``` + +Use `LarkClient::builder` to set a base URL, timeout, retry limit, token cache, +custom headers, marketplace mode, helpdesk credentials, or a JWT client +assertion provider. See [`examples/client_config.rs`](examples/client_config.rs) +for the complete setup. + +## Common Flows + +| Need | Start here | +| --- | --- | +| Build a first Lark / Feishu bot | [Tutorial: build a Lark / Feishu bot](docs/tutorial-build-a-feishu-bot.md) | +| Call a typed REST endpoint | [`examples/send_message.rs`](examples/send_message.rs) or [generated examples](examples/README.md) | +| Call an uncovered endpoint | [`examples/raw_api.rs`](examples/raw_api.rs) | +| Control VC meeting bots | [VC meeting bots](docs/vc-bots.md) | +| Export Spark app source | [Spark app export](docs/spark-app-export.md) | +| Preview or apply Spark database schema changes | [Spark database migration](docs/spark-db-migration.md) | +| Read or modify Mail conversations | [Mail user-mailbox threads](docs/mail-v1-threads.md) | +| Resolve Drive object metadata by token | [Drive token lookup](docs/drive-token-lookup.md) | +| Manage Unified KMS autonomous keys | [Unified KMS](docs/unified-kms.md) | +| Discover or manage BaseApps | [`examples/base_v3_app_read.rs`](examples/base_v3_app_read.rs) and [Base v3 application mode](docs/base-v3-apps.md) | +| Manage Base dashboards or dashboard blocks | [Base v3 dashboards](docs/base-v3-dashboards.md) | +| Read or update Base dashboard or form sharing | [Base v3 sharing](docs/base-v3-sharing.md) | +| Manage Base v3 forms and questions | [Base v3 forms](docs/base-v3-forms.md) | +| Manage Base workflows or visible fields | [Base workflows](docs/base-v3-workflows.md) and [Base visible fields](docs/base-visible-fields.md) | +| Configure a Base field extension or share records | [Base v3 field extensions and record share links](docs/base-v3-field-extensions.md) | +| Read or update modern Docx content | [`examples/docs_ai_fetch.rs`](examples/docs_ai_fetch.rs) and [Docs AI document content](docs/docs-ai.md) | +| Read, edit, or render Slides AI presentations | [`examples/slides_ai_read.rs`](examples/slides_ai_read.rs), [`examples/slides_ai_render.rs`](examples/slides_ai_render.rs), and [Slides AI presentation content](docs/slides-ai.md) | +| Exchange or refresh OAuth tokens | [`examples/authen_oauth.rs`](examples/authen_oauth.rs) and [OAuth SSO guide](docs/oauth-sso.md) | +| Join a shared calendar event | [Events, WebSockets, and channels](docs/events-and-channels.md#calendar-share-token-joins) | +| Receive webhook events | [`examples/event_handler.rs`](examples/event_handler.rs) | +| Send a validated interactive card | [`examples/card_send.rs`](examples/card_send.rs) and [Cards guide](docs/cards.md) | +| Stream a CardKit document | [`examples/cardkit_stream.rs`](examples/cardkit_stream.rs) and [Cards guide](docs/cards.md) | +| Send a published Card Builder template | [`examples/card_template_send.rs`](examples/card_template_send.rs) and [Cards guide](docs/cards.md) | +| Handle interactive card callbacks | [`examples/card_action_handler.rs`](examples/card_action_handler.rs) | +| Run a WebSocket event client | [`examples/ws_client.rs`](examples/ws_client.rs) with `ws` enabled | +| Send channel messages | [`examples/channel_send.rs`](examples/channel_send.rs) with `messaging` enabled | +| Normalize channel messages | [`examples/channel_normalize.rs`](examples/channel_normalize.rs) with `channel` enabled | +| Search bots or work with OKRs | [`examples/bot_search.rs`](examples/bot_search.rs) and [`examples/okr_v2.rs`](examples/okr_v2.rs) | + +## Guides + +- [Tutorial: build a Lark / Feishu bot](docs/tutorial-build-a-feishu-bot.md): + create a WebSocket-connected bot that receives and replies to messages. +- [Go service and event compatibility](docs/go-compatibility.md): raw + requests, the compatibility bridge, and deterministic Go-to-Rust checks. +- [Cards](docs/cards.md): versioned Card JSON, CardKit streaming, published + Card Builder templates, and callbacks. +- [Card protocol alignment](docs/card-protocol.md): source hierarchy and + verification workflow for Card JSON and card callbacks. +- [Events, WebSockets, and channels](docs/events-and-channels.md): webhook + dispatch, long connections, trusted user channels, replies, and updates. +- [OAuth SSO](docs/oauth-sso.md): Feishu and Lark browser login, PKCE, token + refresh, and application-owned session handling. +- [VC meeting bots](docs/vc-bots.md): event queries and outbound meeting join, + leave, message, countdown, and active-meeting operations. +- [Spark app export](docs/spark-app-export.md): streaming source archives, + response validation, and app-type-dependent source snapshots. +- [Spark database migration](docs/spark-db-migration.md): explicit preview, + irreversible apply, and caller-owned asynchronous status polling. +- [Mail user-mailbox threads](docs/mail-v1-threads.md): conversation reads + and batch label, folder, and Trash operations. +- [Drive token lookup](docs/drive-token-lookup.md): underlying object metadata + resolution without implicit download or export. +- [Unified KMS](docs/unified-kms.md): tenant-scoped autonomous-key lifecycle, + deletion plans, recovery, and import material. +- [Base v3 application mode](docs/base-v3-apps.md): user-token workspace, + BaseApp, page, and block operations. +- [Base v3 dashboards](docs/base-v3-dashboards.md): dashboard and dashboard + block lifecycle, chart data, and layout operations. +- [Base v3 sharing](docs/base-v3-sharing.md): dashboard and form sharing + reads and partial updates with user or tenant credentials. +- [Base v3 forms](docs/base-v3-forms.md): form lifecycle and form-question + operations, including explicit destructive-delete semantics. +- [Base v3 workflows](docs/base-v3-workflows.md): workflow lifecycle with + caller-owned definition bodies. +- [Base visible fields](docs/base-visible-fields.md): full-list view and form + field visibility and ordering. +- [Base v3 field extensions and record share links](docs/base-v3-field-extensions.md): + typed extension configuration, cell updates, and batch record links. +- [Docs AI document content](docs/docs-ai.md): modern Docx content fetches, + updates, asynchronous-task reads, and version-history operations. +- [Slides AI presentation content](docs/slides-ai.md): XML presentation and + slide operations with version-history support. +- [Documentation index](docs/README.md): detailed guides and runnable examples. + +## Migration from 0.2 + +Version 0.3 makes API-breaking corrections so the public Rust surface matches +the upstream Lark contracts more closely. + +- Replace `Client` with `LarkClient` and `ClientBuilder` with + `LarkClientBuilder`. +- Replace `client.wiki()` with `client.wiki_v1()` or `client.wiki_v2()`. +- Replace `do_req`, `do_req_typed`, and verb helpers with `raw_request*` and an + `ApiReq` when a dedicated typed resource is unavailable. +- Treat Hire v1 `IdNameObject.name` as `Option` with `zh_cn` and `en_us` + fields. +- Handle JSON construction failures as `LarkError::Json`; legacy card element + helpers are fallible for the same reason. +- Use `JsonValue` for intentionally dynamic API data instead of public + `serde_json::Value` model fields. + +Typed REST responses and incoming event payloads are `#[non_exhaustive]`. +Deserialize them and read their public fields as before, but do not use struct +literals to create server-owned values. + +## Cargo Features + +| Feature | Description | +| --- | --- | +| `ws` | WebSocket long-connection event client with protobuf framing. | +| `messaging` | High-level outbound messaging and resource operations without WebSocket dependencies. | +| `channel` | Inbound normalization and runtime policy helpers; enables `messaging` and `ws`. | +| `axum` | Axum adapters for webhook and card-action handlers. | + +Cargo unifies enabled features across the dependency graph. Enabling `channel` +therefore enables both `messaging` and `ws`; enabling `messaging` and `ws` +separately does not enable the `channel` runtime API. + +```toml +[dependencies] +larksuite-oapi-sdk-rs = { version = "0.3.12", features = ["ws", "axum"] } +``` + +## API Coverage + +The SDK provides dedicated resources across IM, Calendar, Drive, Docs, Sheets, +Bitable, Contacts, Approval, VC, Wiki, Hire, and the broader Lark/Feishu +OpenAPI surface. Generated smoke tests verify request paths, query inputs, +bodies, uploads, downloads, and response handling. See the +[changelog](CHANGELOG.md) for release detail and the +[examples index](examples/README.md) for runnable calls. + +## Requirements + +- Rust 1.95.0 or newer +- Rust edition 2024 + +## License + +Licensed under [MIT](LICENSE). + diff --git a/docs/02-架构设计/专项设计/.research/larkrs-tree.json b/docs/02-架构设计/专项设计/.research/larkrs-tree.json new file mode 100644 index 0000000..6ae9f2a --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/larkrs-tree.json @@ -0,0 +1 @@ +{"sha":"72ef975fb0deb58770e918b25611406da481fe73","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/72ef975fb0deb58770e918b25611406da481fe73","tree":[{"path":".github","mode":"040000","type":"tree","sha":"50bd23a577d78e5d8ab1c5dc5dcbaecb28fb644e","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/50bd23a577d78e5d8ab1c5dc5dcbaecb28fb644e"},{"path":".github/ISSUE_TEMPLATE","mode":"040000","type":"tree","sha":"560a79907873c4d29f4f0466e7b6887731d50e21","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/560a79907873c4d29f4f0466e7b6887731d50e21"},{"path":".github/ISSUE_TEMPLATE/bug_report.yml","mode":"100644","type":"blob","sha":"f8182cc8896234c2b95aa1bf28484d95709f5346","size":1206,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f8182cc8896234c2b95aa1bf28484d95709f5346"},{"path":".github/ISSUE_TEMPLATE/feature_request.yml","mode":"100644","type":"blob","sha":"d59d47599798cc13e5a1b992641cead974e30225","size":915,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d59d47599798cc13e5a1b992641cead974e30225"},{"path":".github/pull_request_template.md","mode":"100644","type":"blob","sha":"686847d1a5424c330359ba523070ebc12ffb98b7","size":536,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/686847d1a5424c330359ba523070ebc12ffb98b7"},{"path":".github/workflows","mode":"040000","type":"tree","sha":"caf967357d412fa994b665f83a3384ab98d56473","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/caf967357d412fa994b665f83a3384ab98d56473"},{"path":".github/workflows/ci.yml","mode":"100644","type":"blob","sha":"eaef141acf38ac60405bf65664f309fa443d251d","size":5275,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/eaef141acf38ac60405bf65664f309fa443d251d"},{"path":".github/workflows/release.yml","mode":"100644","type":"blob","sha":"4dc0428960966773471c32719ad7d4ca5d080646","size":1185,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4dc0428960966773471c32719ad7d4ca5d080646"},{"path":".gitignore","mode":"100644","type":"blob","sha":"e14541ff209cedf2ff9085434212ab9f3caf423a","size":18,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e14541ff209cedf2ff9085434212ab9f3caf423a"},{"path":"CHANGELOG.md","mode":"100644","type":"blob","sha":"396cfcc59d80a05bc90e03819b9fd1dc5dd2422e","size":30950,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/396cfcc59d80a05bc90e03819b9fd1dc5dd2422e"},{"path":"CODE_OF_CONDUCT.md","mode":"100644","type":"blob","sha":"fed2c7746bb3fabd1148a00e2dcf643e3e08b2fb","size":883,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fed2c7746bb3fabd1148a00e2dcf643e3e08b2fb"},{"path":"CONTRIBUTING.md","mode":"100644","type":"blob","sha":"fa8c0b6915273dad582a5600b15c868a4b3191d0","size":1317,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fa8c0b6915273dad582a5600b15c868a4b3191d0"},{"path":"Cargo.lock","mode":"100644","type":"blob","sha":"e408de81f1cec5a9883562feb221f56afa704eae","size":68495,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e408de81f1cec5a9883562feb221f56afa704eae"},{"path":"Cargo.toml","mode":"100644","type":"blob","sha":"e27b6c0b27267c50e738735180e012d7e6c5c8e3","size":1909,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e27b6c0b27267c50e738735180e012d7e6c5c8e3"},{"path":"Justfile","mode":"100644","type":"blob","sha":"3fb402d0768da4da6ce1419637358661b9ee6dec","size":4736,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3fb402d0768da4da6ce1419637358661b9ee6dec"},{"path":"LICENSE","mode":"100644","type":"blob","sha":"84570567728d88db2c51bf9404523ff06e345b42","size":1078,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/84570567728d88db2c51bf9404523ff06e345b42"},{"path":"README.md","mode":"100644","type":"blob","sha":"829af4fa905b498b76f2db14771188c902fbc6d6","size":11542,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/829af4fa905b498b76f2db14771188c902fbc6d6"},{"path":"SECURITY.md","mode":"100644","type":"blob","sha":"e8586ffab37531909dafd006ed3caf42c521c446","size":529,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e8586ffab37531909dafd006ed3caf42c521c446"},{"path":"build.rs","mode":"100644","type":"blob","sha":"40bec07e44461b97bd4a6fa68557376dd709ba84","size":117,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/40bec07e44461b97bd4a6fa68557376dd709ba84"},{"path":"docs","mode":"040000","type":"tree","sha":"cf87b4566d9aa8ddc7aaa38925b569c488b1615b","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/cf87b4566d9aa8ddc7aaa38925b569c488b1615b"},{"path":"docs/README.md","mode":"100644","type":"blob","sha":"5cbea147b6488f598ed8bfd11dc03789a16db5df","size":3899,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5cbea147b6488f598ed8bfd11dc03789a16db5df"},{"path":"docs/api-errors.md","mode":"100644","type":"blob","sha":"263ef86822eee360a09dfc05df2f49c9558fc585","size":1866,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/263ef86822eee360a09dfc05df2f49c9558fc585"},{"path":"docs/base-v3-apps.md","mode":"100644","type":"blob","sha":"b74bf3f08f6bb1d49d8431f11f506a4f302a9325","size":2552,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b74bf3f08f6bb1d49d8431f11f506a4f302a9325"},{"path":"docs/base-v3-dashboards.md","mode":"100644","type":"blob","sha":"bf0bb0409b18f57253994e01f82a8a3807f2319a","size":2230,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bf0bb0409b18f57253994e01f82a8a3807f2319a"},{"path":"docs/base-v3-field-extensions.md","mode":"100644","type":"blob","sha":"4102b00b1c88ce77e183cb286f7612ad4fa4824d","size":2263,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4102b00b1c88ce77e183cb286f7612ad4fa4824d"},{"path":"docs/base-v3-forms.md","mode":"100644","type":"blob","sha":"7c45b6c128bf98b6fbb04f6f9026c454678118f5","size":2090,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7c45b6c128bf98b6fbb04f6f9026c454678118f5"},{"path":"docs/base-v3-records.md","mode":"100644","type":"blob","sha":"49c437de1b23a2f0012ba05dcf991d8eb0a8cc5c","size":2107,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/49c437de1b23a2f0012ba05dcf991d8eb0a8cc5c"},{"path":"docs/base-v3-sharing.md","mode":"100644","type":"blob","sha":"ec57b4c63649ab3c537d2ec9766b4e2fc5c81082","size":2436,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ec57b4c63649ab3c537d2ec9766b4e2fc5c81082"},{"path":"docs/base-v3-workflows.md","mode":"100644","type":"blob","sha":"de62fbc21c6476abf3091ce81e77b6980631169c","size":2197,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/de62fbc21c6476abf3091ce81e77b6980631169c"},{"path":"docs/base-visible-fields.md","mode":"100644","type":"blob","sha":"bcc046f10a107ac9fc8cf258aaf02bccdd617c88","size":1553,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bcc046f10a107ac9fc8cf258aaf02bccdd617c88"},{"path":"docs/card-protocol.md","mode":"100644","type":"blob","sha":"1f8897a3405d7a1966d58dbab52cb5198e471dc9","size":7366,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1f8897a3405d7a1966d58dbab52cb5198e471dc9"},{"path":"docs/cards.md","mode":"100644","type":"blob","sha":"0165babbbf235cc1225b877d466b0aa8b027a54d","size":4132,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0165babbbf235cc1225b877d466b0aa8b027a54d"},{"path":"docs/docs-ai.md","mode":"100644","type":"blob","sha":"f39fcaa8682d1f7a605fb6c44c6784eb5a12b160","size":3221,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f39fcaa8682d1f7a605fb6c44c6784eb5a12b160"},{"path":"docs/drive-media-preview.md","mode":"100644","type":"blob","sha":"5c08cc0ab61def6ef12bc021ae7dd191c2b993b7","size":1375,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5c08cc0ab61def6ef12bc021ae7dd191c2b993b7"},{"path":"docs/drive-token-lookup.md","mode":"100644","type":"blob","sha":"b6b9c6b19f21b7df02393c4ddcb32c924e063672","size":722,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b6b9c6b19f21b7df02393c4ddcb32c924e063672"},{"path":"docs/events-and-channels.md","mode":"100644","type":"blob","sha":"7625f750742229db1915bfcc8d746f37e54cb839","size":5888,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7625f750742229db1915bfcc8d746f37e54cb839"},{"path":"docs/go-compatibility.md","mode":"100644","type":"blob","sha":"c17f2d3050b7c36e71666a9e37080d07fb7650f1","size":4023,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c17f2d3050b7c36e71666a9e37080d07fb7650f1"},{"path":"docs/mail-v1-threads.md","mode":"100644","type":"blob","sha":"f076b157a06ced5703ea2e17f18dbd04f5800b96","size":1182,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f076b157a06ced5703ea2e17f18dbd04f5800b96"},{"path":"docs/oauth-sso.md","mode":"100644","type":"blob","sha":"9e612d8015c14f1ac641695049fcfa512ecfcfe4","size":6074,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9e612d8015c14f1ac641695049fcfa512ecfcfe4"},{"path":"docs/slides-ai.md","mode":"100644","type":"blob","sha":"4535d2de61f76cde3d2b7a4a4aacde99607a0cdc","size":6251,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4535d2de61f76cde3d2b7a4a4aacde99607a0cdc"},{"path":"docs/spark-app-export.md","mode":"100644","type":"blob","sha":"beaaa8bf3b289bdf0cf4cd52c4ac8cf7b3688678","size":6228,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/beaaa8bf3b289bdf0cf4cd52c4ac8cf7b3688678"},{"path":"docs/spark-db-migration.md","mode":"100644","type":"blob","sha":"2c8f7f2e1b4f85af701eaaeb49c01f3ac076f6cb","size":2068,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2c8f7f2e1b4f85af701eaaeb49c01f3ac076f6cb"},{"path":"docs/spark-db-sync.md","mode":"100644","type":"blob","sha":"dafafb9f3f4b6f9e0dbdc3fac1eadc38afee62a8","size":1777,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/dafafb9f3f4b6f9e0dbdc3fac1eadc38afee62a8"},{"path":"docs/tutorial-build-a-feishu-bot.md","mode":"100644","type":"blob","sha":"782958ea1327ecdd5d794c88d6b4fba993e39e4d","size":7265,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/782958ea1327ecdd5d794c88d6b4fba993e39e4d"},{"path":"docs/unified-kms.md","mode":"100644","type":"blob","sha":"8a828ab06cedd67f3100633770b1a58c5472f32e","size":1169,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8a828ab06cedd67f3100633770b1a58c5472f32e"},{"path":"docs/vc-bots.md","mode":"100644","type":"blob","sha":"aa5f848e53efe20990b95bb3b312f518f74384bc","size":3759,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/aa5f848e53efe20990b95bb3b312f518f74384bc"},{"path":"examples","mode":"040000","type":"tree","sha":"82a07230c3f509d3a4fde6b26cc675293680d9c5","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/82a07230c3f509d3a4fde6b26cc675293680d9c5"},{"path":"examples/README.md","mode":"100644","type":"blob","sha":"81143d1a0fdae44af7948333ee7c4f750686bd3d","size":9740,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/81143d1a0fdae44af7948333ee7c4f750686bd3d"},{"path":"examples/app_registration.rs","mode":"100644","type":"blob","sha":"65b7e054d29524628822613724402955f8bbaa14","size":2887,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/65b7e054d29524628822613724402955f8bbaa14"},{"path":"examples/app_slash_commands.rs","mode":"100644","type":"blob","sha":"2d3f25790afc818af5061e505bd27a40badb9aff","size":1789,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2d3f25790afc818af5061e505bd27a40badb9aff"},{"path":"examples/application_v6.rs","mode":"100644","type":"blob","sha":"0beea82f55911422c460054ac0c897cc4e7b923d","size":2119,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0beea82f55911422c460054ac0c897cc4e7b923d"},{"path":"examples/authen_oauth.rs","mode":"100644","type":"blob","sha":"3967c3aeb2aa5e128e8d45b7f7e694f25f562275","size":2927,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3967c3aeb2aa5e128e8d45b7f7e694f25f562275"},{"path":"examples/base_v3_app_read.rs","mode":"100644","type":"blob","sha":"0b25cecbc6f58e9f5051f421e4230bfc504e5a4b","size":1908,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0b25cecbc6f58e9f5051f421e4230bfc504e5a4b"},{"path":"examples/base_v3_record_read.rs","mode":"100644","type":"blob","sha":"a61f800ba55db3bb384dc3a2b903c7d64f10f9c4","size":884,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a61f800ba55db3bb384dc3a2b903c7d64f10f9c4"},{"path":"examples/bitable_records.rs","mode":"100644","type":"blob","sha":"1bb728e856315136c96242bca84d10c7ae8d1114","size":2528,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1bb728e856315136c96242bca84d10c7ae8d1114"},{"path":"examples/bot_search.rs","mode":"100644","type":"blob","sha":"4aabab8c5e48031c4e9c67b60352e5bf5b28b21f","size":949,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4aabab8c5e48031c4e9c67b60352e5bf5b28b21f"},{"path":"examples/calendar_events.rs","mode":"100644","type":"blob","sha":"c25cc31605ae823b2dae5239069b7da54cf34a23","size":1842,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c25cc31605ae823b2dae5239069b7da54cf34a23"},{"path":"examples/card_action_handler.rs","mode":"100644","type":"blob","sha":"65ba86589205dbd61802cd0f82db7429ce269431","size":1385,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/65ba86589205dbd61802cd0f82db7429ce269431"},{"path":"examples/card_send.rs","mode":"100644","type":"blob","sha":"9a985f8bcf5c0a4493ce88f66e1cc14d511e89bd","size":1184,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9a985f8bcf5c0a4493ce88f66e1cc14d511e89bd"},{"path":"examples/card_template_send.rs","mode":"100644","type":"blob","sha":"960d21febcc699f72f7326395ec75ecaae0b0e12","size":1402,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/960d21febcc699f72f7326395ec75ecaae0b0e12"},{"path":"examples/cardkit_stream.rs","mode":"100644","type":"blob","sha":"8839db1b61ee0d8821bf4bc1df43298ea43eb2fe","size":1834,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8839db1b61ee0d8821bf4bc1df43298ea43eb2fe"},{"path":"examples/channel_normalize.rs","mode":"100644","type":"blob","sha":"186696a5253fd7407525c67373714f1d7a38a1cb","size":1283,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/186696a5253fd7407525c67373714f1d7a38a1cb"},{"path":"examples/channel_send.rs","mode":"100644","type":"blob","sha":"8826bea41c5598241719bebf3b4ab2f796dd3676","size":969,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8826bea41c5598241719bebf3b4ab2f796dd3676"},{"path":"examples/client_config.rs","mode":"100644","type":"blob","sha":"0cfdd5fc28ac82d1530fc63003fd3d6fc7143f0e","size":1535,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0cfdd5fc28ac82d1530fc63003fd3d6fc7143f0e"},{"path":"examples/docs_ai_fetch.rs","mode":"100644","type":"blob","sha":"9e400e32660d64436e15058ace1b4c817fda63c1","size":917,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9e400e32660d64436e15058ace1b4c817fda63c1"},{"path":"examples/dpop_file_key_store.rs","mode":"100644","type":"blob","sha":"3a76ff0f337384ab687a5d14bcaf0a354132ee47","size":2946,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3a76ff0f337384ab687a5d14bcaf0a354132ee47"},{"path":"examples/drive_files.rs","mode":"100644","type":"blob","sha":"37de748f294b25588632d6286091451b04b1e1f2","size":2820,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/37de748f294b25588632d6286091451b04b1e1f2"},{"path":"examples/event_handler.rs","mode":"100644","type":"blob","sha":"892ecd9507a86ced0452385e50a503848af7a415","size":1691,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/892ecd9507a86ced0452385e50a503848af7a415"},{"path":"examples/go_compatibility_endpoint.rs","mode":"100644","type":"blob","sha":"bae907d2ddd4cd9fd6aecb3b9ae7390ef92c4b60","size":1266,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bae907d2ddd4cd9fd6aecb3b9ae7390ef92c4b60"},{"path":"examples/im_message_query.rs","mode":"100644","type":"blob","sha":"c14cf2d50fd8e80e4c926b3e499b3e7b76c84896","size":1518,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c14cf2d50fd8e80e4c926b3e499b3e7b76c84896"},{"path":"examples/im_stream_download.rs","mode":"100644","type":"blob","sha":"b13d6849161b8fca28fe8c8c8bf31b851fcee0a2","size":2938,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b13d6849161b8fca28fe8c8c8bf31b851fcee0a2"},{"path":"examples/im_upload_download.rs","mode":"100644","type":"blob","sha":"897a6cc4f237999708e7ac910137a2ccc3ba2a77","size":2327,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/897a6cc4f237999708e7ac910137a2ccc3ba2a77"},{"path":"examples/okr_v2.rs","mode":"100644","type":"blob","sha":"fa5ce653e18fee33ab10d264cf46db0623304429","size":711,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fa5ce653e18fee33ab10d264cf46db0623304429"},{"path":"examples/raw_api.rs","mode":"100644","type":"blob","sha":"255cb15ab4115b7bc0e07ce8b419654485079f1f","size":2284,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/255cb15ab4115b7bc0e07ce8b419654485079f1f"},{"path":"examples/send_message.rs","mode":"100644","type":"blob","sha":"1b5025889bb9ec8ffd0b19e8023a7044fd3e70ae","size":1206,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1b5025889bb9ec8ffd0b19e8023a7044fd3e70ae"},{"path":"examples/sheets_read.rs","mode":"100644","type":"blob","sha":"29f2c4bfcb97c6ed770a02ad0a37f0b40a59ae13","size":1854,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/29f2c4bfcb97c6ed770a02ad0a37f0b40a59ae13"},{"path":"examples/slides_ai_read.rs","mode":"100644","type":"blob","sha":"9ad60ce76b88c28e5bc441917f3dc0bdcd646b6f","size":801,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9ad60ce76b88c28e5bc441917f3dc0bdcd646b6f"},{"path":"examples/slides_ai_render.rs","mode":"100644","type":"blob","sha":"e168ffc8339913769185abcc58d184ed388d0b53","size":983,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e168ffc8339913769185abcc58d184ed388d0b53"},{"path":"examples/spark_db_sync.rs","mode":"100644","type":"blob","sha":"2b36c17e6aa69e6f3f4d374222c080a6815cbfe9","size":1004,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2b36c17e6aa69e6f3f4d374222c080a6815cbfe9"},{"path":"examples/ws_client.rs","mode":"100644","type":"blob","sha":"9e5b39a2912c7bb777ae3e5acbe487cf7b0205bd","size":3714,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9e5b39a2912c7bb777ae3e5acbe487cf7b0205bd"},{"path":"proto","mode":"040000","type":"tree","sha":"35c387edef92e409f3b3d6fb81efa8b85d9eacc9","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/35c387edef92e409f3b3d6fb81efa8b85d9eacc9"},{"path":"proto/ws.proto","mode":"100644","type":"blob","sha":"5ca1528c05b5078517f6ac6e1786d9cf4e7c45ba","size":579,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5ca1528c05b5078517f6ac6e1786d9cf4e7c45ba"},{"path":"src","mode":"040000","type":"tree","sha":"56e805de270abd2d46dccb3140cb76fd43dbf9a6","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/56e805de270abd2d46dccb3140cb76fd43dbf9a6"},{"path":"src/axum_handler.rs","mode":"100644","type":"blob","sha":"89c91fb2f7ec70f68692f0a9099653b8f3a9e035","size":6152,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/89c91fb2f7ec70f68692f0a9099653b8f3a9e035"},{"path":"src/cache.rs","mode":"100644","type":"blob","sha":"d84589ff6ac04cfc98a3893afc261fb3f987bb2f","size":3166,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d84589ff6ac04cfc98a3893afc261fb3f987bb2f"},{"path":"src/card.rs","mode":"100644","type":"blob","sha":"6def3418d11fbf4e5d05d1c75de75eb6acf153cd","size":36102,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6def3418d11fbf4e5d05d1c75de75eb6acf153cd"},{"path":"src/card","mode":"040000","type":"tree","sha":"1d6b38f0c4968234c6abcf98b272dd163ea92fd8","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/1d6b38f0c4968234c6abcf98b272dd163ea92fd8"},{"path":"src/card/cardkit.rs","mode":"100644","type":"blob","sha":"56f3c0e30e19cd46de786cf603c403a2e7d2f7fe","size":33136,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/56f3c0e30e19cd46de786cf603c403a2e7d2f7fe"},{"path":"src/card/template.rs","mode":"100644","type":"blob","sha":"6fbc3ac4ee4b6ca891c1bc210fb9f226166735b0","size":5795,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6fbc3ac4ee4b6ca891c1bc210fb9f226166735b0"},{"path":"src/card/v1.rs","mode":"100644","type":"blob","sha":"762cd0e5dbb935cfc99a5f63eed9435d0d886b1c","size":68579,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/762cd0e5dbb935cfc99a5f63eed9435d0d886b1c"},{"path":"src/card/v2.rs","mode":"100644","type":"blob","sha":"fd6e7c2736491aa9cfd63af6648a802d7c7cb05e","size":172759,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fd6e7c2736491aa9cfd63af6648a802d7c7cb05e"},{"path":"src/card/v2_diagnostic_path_tests.rs","mode":"100644","type":"blob","sha":"d8b513bb1384c90adde56e736ab852aca93a5d1a","size":13700,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d8b513bb1384c90adde56e736ab852aca93a5d1a"},{"path":"src/channel.rs","mode":"100644","type":"blob","sha":"811c758a9044f25959ef96bce9615e4f0d0f72f5","size":1242,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/811c758a9044f25959ef96bce9615e4f0d0f72f5"},{"path":"src/channel","mode":"040000","type":"tree","sha":"e93d3b599c5a3a297c28ce50555b2fdd8bf81557","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e93d3b599c5a3a297c28ce50555b2fdd8bf81557"},{"path":"src/channel/builder.rs","mode":"100644","type":"blob","sha":"f6d305e11232676317eaf80ed56ad993b9aed777","size":7215,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f6d305e11232676317eaf80ed56ad993b9aed777"},{"path":"src/channel/composition.rs","mode":"100644","type":"blob","sha":"3a8f406365bb6b1d381f9745dabf232d6d87ed2c","size":1964,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3a8f406365bb6b1d381f9745dabf232d6d87ed2c"},{"path":"src/channel/duration.rs","mode":"100644","type":"blob","sha":"cbed1ea0772b678372331903c764f2d8eb533b43","size":5555,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cbed1ea0772b678372331903c764f2d8eb533b43"},{"path":"src/channel/handler.rs","mode":"100644","type":"blob","sha":"3ba60d41f2e40d9a05ecfaf02af6c84393c424be","size":7448,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3ba60d41f2e40d9a05ecfaf02af6c84393c424be"},{"path":"src/channel/identity.rs","mode":"100644","type":"blob","sha":"6427a42875a08b663f59cd6419bb88990d2d672b","size":5005,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6427a42875a08b663f59cd6419bb88990d2d672b"},{"path":"src/channel/messaging.rs","mode":"100644","type":"blob","sha":"64fd8279ccc460ba644d92a48c3e0b88856ddf83","size":28602,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/64fd8279ccc460ba644d92a48c3e0b88856ddf83"},{"path":"src/channel/messaging_tests.rs","mode":"100644","type":"blob","sha":"775b19d83005ebd38cf136a00827f30399063b74","size":9763,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/775b19d83005ebd38cf136a00827f30399063b74"},{"path":"src/channel/normalize.rs","mode":"100644","type":"blob","sha":"1ec542ed4590eca4c7ee6005a161cf32d15a1c81","size":21650,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1ec542ed4590eca4c7ee6005a161cf32d15a1c81"},{"path":"src/channel/policy.rs","mode":"100644","type":"blob","sha":"ce14f7ae9a2499d363c0544111d42fc65b1bb82c","size":3099,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ce14f7ae9a2499d363c0544111d42fc65b1bb82c"},{"path":"src/channel/runtime.rs","mode":"100644","type":"blob","sha":"4be2ea739aa20be4ba76c6da32716210d18b3e1a","size":7524,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4be2ea739aa20be4ba76c6da32716210d18b3e1a"},{"path":"src/channel/safety.rs","mode":"100644","type":"blob","sha":"428f8b0d14725986789f694fa6fdadc045944c97","size":9056,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/428f8b0d14725986789f694fa6fdadc045944c97"},{"path":"src/channel/state.rs","mode":"100644","type":"blob","sha":"ddf9600c6f65c5504ab7ce650c99e10936b46bf6","size":9486,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ddf9600c6f65c5504ab7ce650c99e10936b46bf6"},{"path":"src/channel/stream.rs","mode":"100644","type":"blob","sha":"16b108bc0158755f77ad9fdd030edf9ebc2d2184","size":2302,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/16b108bc0158755f77ad9fdd030edf9ebc2d2184"},{"path":"src/channel/tests.rs","mode":"100644","type":"blob","sha":"a1ca266a8474c30e056b712f9e0206284a0d2b81","size":53490,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a1ca266a8474c30e056b712f9e0206284a0d2b81"},{"path":"src/channel/types.rs","mode":"100644","type":"blob","sha":"2312cd4aea933e101c5a91a5a9875027d4c662a5","size":12527,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2312cd4aea933e101c5a91a5a9875027d4c662a5"},{"path":"src/channel/util.rs","mode":"100644","type":"blob","sha":"a1f282f48713fc3536d3cfa11bfa96b833ced084","size":158,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a1f282f48713fc3536d3cfa11bfa96b833ced084"},{"path":"src/client.rs","mode":"100644","type":"blob","sha":"467d705291294d956a1e893af1fd4713289cf412","size":762,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/467d705291294d956a1e893af1fd4713289cf412"},{"path":"src/client","mode":"040000","type":"tree","sha":"8e9c2798f80ff059bc326cb75cfd74dfa4af1b18","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/8e9c2798f80ff059bc326cb75cfd74dfa4af1b18"},{"path":"src/client/builder.rs","mode":"100644","type":"blob","sha":"e85d535139dfd47b5e711f0e3044e9e793e77629","size":3643,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e85d535139dfd47b5e711f0e3044e9e793e77629"},{"path":"src/client/raw.rs","mode":"100644","type":"blob","sha":"ad0789b06dc9accb181dc5b2e77e366e5fc03309","size":2860,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ad0789b06dc9accb181dc5b2e77e366e5fc03309"},{"path":"src/client/services.rs","mode":"100644","type":"blob","sha":"88453ed983183dc9ece8cd63674b1dd4c27247d0","size":4941,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/88453ed983183dc9ece8cd63674b1dd4c27247d0"},{"path":"src/client/tokens.rs","mode":"100644","type":"blob","sha":"68bd436506907c4a470fcf0d98f9ce01bfc88734","size":2072,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/68bd436506907c4a470fcf0d98f9ce01bfc88734"},{"path":"src/config.rs","mode":"100644","type":"blob","sha":"9840cea732a6789743159ae91b6e59721e105974","size":7417,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9840cea732a6789743159ae91b6e59721e105974"},{"path":"src/constants.rs","mode":"100644","type":"blob","sha":"730efc373d102601a0223bd1236cafab730f508e","size":4026,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/730efc373d102601a0223bd1236cafab730f508e"},{"path":"src/crypto.rs","mode":"100644","type":"blob","sha":"ad3ac3678de9660a54be15bc82c805503a2ab897","size":3960,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ad3ac3678de9660a54be15bc82c805503a2ab897"},{"path":"src/dpop.rs","mode":"100644","type":"blob","sha":"31123b8bd3a8869d638c7b86da0c79cc77d68145","size":14542,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/31123b8bd3a8869d638c7b86da0c79cc77d68145"},{"path":"src/error.rs","mode":"100644","type":"blob","sha":"9ba3b0e5b638d1024d44126f18715729830f4991","size":2539,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9ba3b0e5b638d1024d44126f18715729830f4991"},{"path":"src/event.rs","mode":"100644","type":"blob","sha":"5ed6adacbf3ffc01472f245bf18f93afcc2009ab","size":22283,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5ed6adacbf3ffc01472f245bf18f93afcc2009ab"},{"path":"src/event","mode":"040000","type":"tree","sha":"236e7f24b441be45b76d12e0b91c2afb78057c35","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/236e7f24b441be45b76d12e0b91c2afb78057c35"},{"path":"src/event/callback.rs","mode":"100644","type":"blob","sha":"a9200d007b5771b51e39cacbfda2d14f6e9bbd82","size":9962,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a9200d007b5771b51e39cacbfda2d14f6e9bbd82"},{"path":"src/event/card_action.rs","mode":"100644","type":"blob","sha":"895a50d35cdf2de650242fd5ea44dc4e6759c40e","size":5919,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/895a50d35cdf2de650242fd5ea44dc4e6759c40e"},{"path":"src/events","mode":"040000","type":"tree","sha":"06cc9bffce4ddce7e1b21a2480f87ca4b11640dc","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/06cc9bffce4ddce7e1b21a2480f87ca4b11640dc"},{"path":"src/events/acs.rs","mode":"100644","type":"blob","sha":"25a0a9954d22bc6d7b5d689edb8ba3e1b6266eba","size":1540,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/25a0a9954d22bc6d7b5d689edb8ba3e1b6266eba"},{"path":"src/events/apaas.rs","mode":"100644","type":"blob","sha":"365c3808350b135870579e01c435e1d39a7ec5fc","size":1037,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/365c3808350b135870579e01c435e1d39a7ec5fc"},{"path":"src/events/application.rs","mode":"100644","type":"blob","sha":"48afe2d00d84e841572d1c43aa067888383c1599","size":16811,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/48afe2d00d84e841572d1c43aa067888383c1599"},{"path":"src/events/approval.rs","mode":"100644","type":"blob","sha":"a2b52902b6a6512668edaa6d044f2ab8f6dbaee0","size":3673,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a2b52902b6a6512668edaa6d044f2ab8f6dbaee0"},{"path":"src/events/auth.rs","mode":"100644","type":"blob","sha":"7667b054c2eea49963d81389ca897f7da824d7cd","size":1063,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7667b054c2eea49963d81389ca897f7da824d7cd"},{"path":"src/events/calendar.rs","mode":"100644","type":"blob","sha":"41e0705c56b90452d75e010e7703e5b366bef012","size":3260,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/41e0705c56b90452d75e010e7703e5b366bef012"},{"path":"src/events/common.rs","mode":"100644","type":"blob","sha":"cad7d9884dcf1e8c8c43ae250cd2f3d8313b65d2","size":2437,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cad7d9884dcf1e8c8c43ae250cd2f3d8313b65d2"},{"path":"src/events/compensation.rs","mode":"100644","type":"blob","sha":"8eb9b1ff190e22f4b0ab81f2fc9451aa64faffaf","size":841,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8eb9b1ff190e22f4b0ab81f2fc9451aa64faffaf"},{"path":"src/events/contact.rs","mode":"100644","type":"blob","sha":"6c83e301903bd46f6b5c441389fdacd586535172","size":12962,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6c83e301903bd46f6b5c441389fdacd586535172"},{"path":"src/events/corehr.rs","mode":"100644","type":"blob","sha":"18be2a664bd005e9bd2eb22b145fc9c6420921e1","size":13179,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/18be2a664bd005e9bd2eb22b145fc9c6420921e1"},{"path":"src/events/corehr_v2.rs","mode":"100644","type":"blob","sha":"fcad65b5bec85184ad80f5443a1a8c0ae56944bc","size":24029,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fcad65b5bec85184ad80f5443a1a8c0ae56944bc"},{"path":"src/events/drive.rs","mode":"100644","type":"blob","sha":"191af10e77b9b0ac7c2d49bdf9ccfb1fbf2fc420","size":15011,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/191af10e77b9b0ac7c2d49bdf9ccfb1fbf2fc420"},{"path":"src/events/elearning.rs","mode":"100644","type":"blob","sha":"5e294afcf1abc91e854e4c878504d6ec5f94c037","size":2840,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5e294afcf1abc91e854e4c878504d6ec5f94c037"},{"path":"src/events/helpdesk.rs","mode":"100644","type":"blob","sha":"ec3c7b3efdf66498ed85f1bd54f4065f08dbe86a","size":12078,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ec3c7b3efdf66498ed85f1bd54f4065f08dbe86a"},{"path":"src/events/hire.rs","mode":"100644","type":"blob","sha":"08bdb689adc43535a0399017d263bd8c672eaea6","size":14117,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/08bdb689adc43535a0399017d263bd8c672eaea6"},{"path":"src/events/im.rs","mode":"100644","type":"blob","sha":"c9d6870d26b3bc2a0b07f6ed7994e2f82263b82f","size":12989,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c9d6870d26b3bc2a0b07f6ed7994e2f82263b82f"},{"path":"src/events/mail.rs","mode":"100644","type":"blob","sha":"e30078810b8461e6eefa02b201b345f6aeb1ff92","size":1050,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e30078810b8461e6eefa02b201b345f6aeb1ff92"},{"path":"src/events/meeting_room.rs","mode":"100644","type":"blob","sha":"a201d7599e7d0beeb406b121fd3cb19054c8d199","size":1883,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a201d7599e7d0beeb406b121fd3cb19054c8d199"},{"path":"src/events/minutes.rs","mode":"100644","type":"blob","sha":"73f381dca471a96fc7216008e4eaac6078af1179","size":1016,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/73f381dca471a96fc7216008e4eaac6078af1179"},{"path":"src/events/mod.rs","mode":"100644","type":"blob","sha":"7c05302e2aae39298b39964827697024dd47e2ad","size":566,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7c05302e2aae39298b39964827697024dd47e2ad"},{"path":"src/events/moments.rs","mode":"100644","type":"blob","sha":"cdade38bcf960f5e9dd1c731ac95329a0519cc02","size":5103,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cdade38bcf960f5e9dd1c731ac95329a0519cc02"},{"path":"src/events/p1.rs","mode":"100644","type":"blob","sha":"7fb862ef48836f5f34f50fb049c161273c8f7de5","size":14159,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7fb862ef48836f5f34f50fb049c161273c8f7de5"},{"path":"src/events/passport.rs","mode":"100644","type":"blob","sha":"f436edcbcbe437c2bd771f42c0a67443fa4474ab","size":1024,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f436edcbcbe437c2bd771f42c0a67443fa4474ab"},{"path":"src/events/payroll.rs","mode":"100644","type":"blob","sha":"067ccdef2dc086a290c6da030b391e9894dd4485","size":926,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/067ccdef2dc086a290c6da030b391e9894dd4485"},{"path":"src/events/performance.rs","mode":"100644","type":"blob","sha":"b933ee9ee82a26d9a3acef4a264e1e488ac21cad","size":2178,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b933ee9ee82a26d9a3acef4a264e1e488ac21cad"},{"path":"src/events/search.rs","mode":"100644","type":"blob","sha":"2fab41f7496f9921636a44940d0bb0172d8dcac4","size":1128,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2fab41f7496f9921636a44940d0bb0172d8dcac4"},{"path":"src/events/security_and_compliance.rs","mode":"100644","type":"blob","sha":"df368ef1a831f6d5b8f2c8e5c64b68a6ea7f607e","size":6824,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/df368ef1a831f6d5b8f2c8e5c64b68a6ea7f607e"},{"path":"src/events/task.rs","mode":"100644","type":"blob","sha":"8ab0464799bd5962166d130aec1b542e4d997264","size":2469,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8ab0464799bd5962166d130aec1b542e4d997264"},{"path":"src/events/vc.rs","mode":"100644","type":"blob","sha":"5844b1a52cd2e86802944a0d4076f820694a120e","size":30720,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5844b1a52cd2e86802944a0d4076f820694a120e"},{"path":"src/http_handler.rs","mode":"100644","type":"blob","sha":"b79030314d2b42d91be34c55665c280d10998a3f","size":7555,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b79030314d2b42d91be34c55665c280d10998a3f"},{"path":"src/json.rs","mode":"100644","type":"blob","sha":"25a196a624358dd65dd7f2c9edaca2010d554554","size":1704,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/25a196a624358dd65dd7f2c9edaca2010d554554"},{"path":"src/lib.rs","mode":"100644","type":"blob","sha":"97696c5bce527ac4b1795511adaf126417d15906","size":3422,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/97696c5bce527ac4b1795511adaf126417d15906"},{"path":"src/macros.rs","mode":"100644","type":"blob","sha":"23bec020a7400125cf30fe87eabb7fb03badc131","size":9749,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/23bec020a7400125cf30fe87eabb7fb03badc131"},{"path":"src/registration.rs","mode":"100644","type":"blob","sha":"849967cd78275ad7b46a394efa9c3d8d82e73cab","size":26441,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/849967cd78275ad7b46a394efa9c3d8d82e73cab"},{"path":"src/req.rs","mode":"100644","type":"blob","sha":"592b1c74cefc63b6d2e8d6939e91ebc1d631fb5b","size":6073,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/592b1c74cefc63b6d2e8d6939e91ebc1d631fb5b"},{"path":"src/resp.rs","mode":"100644","type":"blob","sha":"1efeba24f44a6a890e1b34d37ecbb67ebadc481f","size":5759,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1efeba24f44a6a890e1b34d37ecbb67ebadc481f"},{"path":"src/service","mode":"040000","type":"tree","sha":"98c025797bbe525f22a4c3941d6a63120bdd02a8","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/98c025797bbe525f22a4c3941d6a63120bdd02a8"},{"path":"src/service/acs","mode":"040000","type":"tree","sha":"319b9471d6e7f6ae85f36aa7be3671fe828dcb84","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/319b9471d6e7f6ae85f36aa7be3671fe828dcb84"},{"path":"src/service/acs/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/acs/v1.rs","mode":"100644","type":"blob","sha":"518855c1da6594e7f039205fc78943afdcfc4427","size":31610,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/518855c1da6594e7f039205fc78943afdcfc4427"},{"path":"src/service/admin","mode":"040000","type":"tree","sha":"ccfd798553cd9b220ae34e2842a1b02d0a87b383","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/ccfd798553cd9b220ae34e2842a1b02d0a87b383"},{"path":"src/service/admin/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/admin/v1.rs","mode":"100644","type":"blob","sha":"4ee8a089e45e974a1a4a0dcde3e31b220f2f4ded","size":61229,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4ee8a089e45e974a1a4a0dcde3e31b220f2f4ded"},{"path":"src/service/aily","mode":"040000","type":"tree","sha":"8ff9deb17773102526f251eec026456f80b46c25","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/8ff9deb17773102526f251eec026456f80b46c25"},{"path":"src/service/aily/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/aily/v1.rs","mode":"100644","type":"blob","sha":"69d9bb52a19973730ecde73c525e0677dd415919","size":31713,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/69d9bb52a19973730ecde73c525e0677dd415919"},{"path":"src/service/apaas","mode":"040000","type":"tree","sha":"d539ef2b55ff9cb9b36a9320f2b1a73dbeabe091","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/d539ef2b55ff9cb9b36a9320f2b1a73dbeabe091"},{"path":"src/service/apaas/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/apaas/v1.rs","mode":"100644","type":"blob","sha":"609e77878dd5323dc35a71cc74f8091b33716611","size":48113,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/609e77878dd5323dc35a71cc74f8091b33716611"},{"path":"src/service/application","mode":"040000","type":"tree","sha":"0d4b05a0a36b0395d9a2cfe2f5496bbe42101a2c","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/0d4b05a0a36b0395d9a2cfe2f5496bbe42101a2c"},{"path":"src/service/application/mod.rs","mode":"100644","type":"blob","sha":"ce6feba5f841a95df8444cd05bec3b65a5c49b87","size":63,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ce6feba5f841a95df8444cd05bec3b65a5c49b87"},{"path":"src/service/application/v1.rs","mode":"100644","type":"blob","sha":"f8a5954cc7892a5461b420ba14dfc52544a9dd60","size":20485,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f8a5954cc7892a5461b420ba14dfc52544a9dd60"},{"path":"src/service/application/v5.rs","mode":"100644","type":"blob","sha":"c253d54bf5c9f3e65c13715647b51b074d560b53","size":7041,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c253d54bf5c9f3e65c13715647b51b074d560b53"},{"path":"src/service/application/v6.rs","mode":"100644","type":"blob","sha":"e591dd029ab6d66fc9988c17fe15f3fd1a32d513","size":88001,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e591dd029ab6d66fc9988c17fe15f3fd1a32d513"},{"path":"src/service/application/v7.rs","mode":"100644","type":"blob","sha":"21229ff2289108072ad79c79ed9097abe0a2125b","size":33302,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/21229ff2289108072ad79c79ed9097abe0a2125b"},{"path":"src/service/approval","mode":"040000","type":"tree","sha":"0f040642c67f503571198d6cfb726aff424066fb","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/0f040642c67f503571198d6cfb726aff424066fb"},{"path":"src/service/approval/mod.rs","mode":"100644","type":"blob","sha":"c53da1730d87b52a25467a4e45db753c43cdffcd","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c53da1730d87b52a25467a4e45db753c43cdffcd"},{"path":"src/service/approval/v4.rs","mode":"100644","type":"blob","sha":"921aa5e1d6fc6212d553fb321091bee513a6bb1f","size":89097,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/921aa5e1d6fc6212d553fb321091bee513a6bb1f"},{"path":"src/service/attendance","mode":"040000","type":"tree","sha":"bcdd8c7d61d3f52bef41d78d6ae15dff617e47d7","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/bcdd8c7d61d3f52bef41d78d6ae15dff617e47d7"},{"path":"src/service/attendance/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/attendance/v1.rs","mode":"100644","type":"blob","sha":"72575d98edd2fbe94b6670ad25d14de948486701","size":112957,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/72575d98edd2fbe94b6670ad25d14de948486701"},{"path":"src/service/auth","mode":"040000","type":"tree","sha":"7d25e43dd4b660327afdf87f7fd583a74401e815","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/7d25e43dd4b660327afdf87f7fd583a74401e815"},{"path":"src/service/auth/mod.rs","mode":"100644","type":"blob","sha":"2a989da0ec1a9b663aa563342f2fb12fc143f8de","size":28,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2a989da0ec1a9b663aa563342f2fb12fc143f8de"},{"path":"src/service/auth/v3.rs","mode":"100644","type":"blob","sha":"0cf21fde995763520650e728b015ab65ec02fd41","size":8292,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0cf21fde995763520650e728b015ab65ec02fd41"},{"path":"src/service/authen","mode":"040000","type":"tree","sha":"71f7a367e4ca75a19469bed9e2db2ade1841789f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/71f7a367e4ca75a19469bed9e2db2ade1841789f"},{"path":"src/service/authen/mod.rs","mode":"100644","type":"blob","sha":"44c6ed465f6ab5b084d518cf414fffebb440b328","size":28,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/44c6ed465f6ab5b084d518cf414fffebb440b328"},{"path":"src/service/authen/v1.rs","mode":"100644","type":"blob","sha":"d1ac5bf7291531802e00e806ba449f9ae2bf902b","size":32503,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d1ac5bf7291531802e00e806ba449f9ae2bf902b"},{"path":"src/service/baike","mode":"040000","type":"tree","sha":"9a4e704ad2a0cc14b89bdfa1c1d7b68bc80c9d72","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/9a4e704ad2a0cc14b89bdfa1c1d7b68bc80c9d72"},{"path":"src/service/baike/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/baike/v1.rs","mode":"100644","type":"blob","sha":"282544e0414bcaff1b9b8e6d6b8ac5838fb9d9aa","size":31059,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/282544e0414bcaff1b9b8e6d6b8ac5838fb9d9aa"},{"path":"src/service/base","mode":"040000","type":"tree","sha":"8631dafc6659b5c39d88c440f9f5ce1910f0ea6b","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/8631dafc6659b5c39d88c440f9f5ce1910f0ea6b"},{"path":"src/service/base/mod.rs","mode":"100644","type":"blob","sha":"e55f7d1fdcc029db9f967a6bd36d3daa99fd6ab4","size":24,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e55f7d1fdcc029db9f967a6bd36d3daa99fd6ab4"},{"path":"src/service/base/v2.rs","mode":"100644","type":"blob","sha":"5c3c16d9bd4cb13d17a8abe94cca7e0ab09fdb90","size":9727,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5c3c16d9bd4cb13d17a8abe94cca7e0ab09fdb90"},{"path":"src/service/base/v3.rs","mode":"100644","type":"blob","sha":"eb8d5ad7eac60f02d06fc1110f978f6fe2ee34b7","size":68435,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/eb8d5ad7eac60f02d06fc1110f978f6fe2ee34b7"},{"path":"src/service/bitable","mode":"040000","type":"tree","sha":"0ee99dba7d50bf7739326d712ccfdde054553121","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/0ee99dba7d50bf7739326d712ccfdde054553121"},{"path":"src/service/bitable/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/bitable/v1.rs","mode":"100644","type":"blob","sha":"3f836d80a8d262a8ff84bf8f7397c7cc3979e12b","size":92604,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3f836d80a8d262a8ff84bf8f7397c7cc3979e12b"},{"path":"src/service/block","mode":"040000","type":"tree","sha":"b0b0a8ae18dc152116c5f83891342a3112051852","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/b0b0a8ae18dc152116c5f83891342a3112051852"},{"path":"src/service/block/mod.rs","mode":"100644","type":"blob","sha":"85307cc80d847d8e016e700ec5c823b9f0654e38","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/85307cc80d847d8e016e700ec5c823b9f0654e38"},{"path":"src/service/block/v1.rs","mode":"100644","type":"blob","sha":"6f88daa206c3c64be8386510a90076e2c5566c78","size":4294,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6f88daa206c3c64be8386510a90076e2c5566c78"},{"path":"src/service/block/v2.rs","mode":"100644","type":"blob","sha":"be66b53542e0d2c390fdf1ef60dcb8847a3ddd0b","size":6424,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/be66b53542e0d2c390fdf1ef60dcb8847a3ddd0b"},{"path":"src/service/board","mode":"040000","type":"tree","sha":"c099b7f9a90a17b2a68e9fed6f810c80750ce38f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/c099b7f9a90a17b2a68e9fed6f810c80750ce38f"},{"path":"src/service/board/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/board/v1.rs","mode":"100644","type":"blob","sha":"05f3b8b3aeecfaaa0cd19f35995cc6a839f3f4a8","size":40392,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/05f3b8b3aeecfaaa0cd19f35995cc6a839f3f4a8"},{"path":"src/service/bot","mode":"040000","type":"tree","sha":"cad1d72c069a1dc348aca9768e4154aed07469b3","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/cad1d72c069a1dc348aca9768e4154aed07469b3"},{"path":"src/service/bot/mod.rs","mode":"100644","type":"blob","sha":"c1052a5cf02fdf35aabd91ad699a2e92eb26baa8","size":12,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c1052a5cf02fdf35aabd91ad699a2e92eb26baa8"},{"path":"src/service/bot/v4.rs","mode":"100644","type":"blob","sha":"dba7831ac7f1dce84e2d2ad22e8f17c4962f72b3","size":6264,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/dba7831ac7f1dce84e2d2ad22e8f17c4962f72b3"},{"path":"src/service/calendar","mode":"040000","type":"tree","sha":"5a1228e412fae58563873336859ebb1ff5f0cbc5","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/5a1228e412fae58563873336859ebb1ff5f0cbc5"},{"path":"src/service/calendar/mod.rs","mode":"100644","type":"blob","sha":"c53da1730d87b52a25467a4e45db753c43cdffcd","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c53da1730d87b52a25467a4e45db753c43cdffcd"},{"path":"src/service/calendar/v4.rs","mode":"100644","type":"blob","sha":"e563d595dc90575ed2c08198915f8cff341af721","size":83915,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e563d595dc90575ed2c08198915f8cff341af721"},{"path":"src/service/cardkit","mode":"040000","type":"tree","sha":"e8d0fef3173dc4e7d3cc270541163035c2b05a59","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e8d0fef3173dc4e7d3cc270541163035c2b05a59"},{"path":"src/service/cardkit/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/cardkit/v1.rs","mode":"100644","type":"blob","sha":"daf69eaf3dfd31139dfa5d4d421c33feda7fbde0","size":21084,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/daf69eaf3dfd31139dfa5d4d421c33feda7fbde0"},{"path":"src/service/common.rs","mode":"100644","type":"blob","sha":"178a5c3664e9c3fdc2f96de9ecd62cbfb607a43f","size":19676,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/178a5c3664e9c3fdc2f96de9ecd62cbfb607a43f"},{"path":"src/service/compensation","mode":"040000","type":"tree","sha":"ade631bd70d0a2b2b2c44c4674eef019d6ab7d6e","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/ade631bd70d0a2b2b2c44c4674eef019d6ab7d6e"},{"path":"src/service/compensation/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/compensation/v1.rs","mode":"100644","type":"blob","sha":"339ee4046ddde336be3d374a1409ac958ef02e09","size":40761,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/339ee4046ddde336be3d374a1409ac958ef02e09"},{"path":"src/service/contact","mode":"040000","type":"tree","sha":"cbc5b2580a944dcb5f473df57ee70a4d9fd7460c","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/cbc5b2580a944dcb5f473df57ee70a4d9fd7460c"},{"path":"src/service/contact/mod.rs","mode":"100644","type":"blob","sha":"2a989da0ec1a9b663aa563342f2fb12fc143f8de","size":28,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2a989da0ec1a9b663aa563342f2fb12fc143f8de"},{"path":"src/service/contact/v3.rs","mode":"100644","type":"blob","sha":"0e685427f5f2e3288c3440d0ef60fa3e87fe7776","size":139623,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0e685427f5f2e3288c3440d0ef60fa3e87fe7776"},{"path":"src/service/corehr","mode":"040000","type":"tree","sha":"19e411c5b6e9746d94554a41bac3da63a047f1f3","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/19e411c5b6e9746d94554a41bac3da63a047f1f3"},{"path":"src/service/corehr/mod.rs","mode":"100644","type":"blob","sha":"85307cc80d847d8e016e700ec5c823b9f0654e38","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/85307cc80d847d8e016e700ec5c823b9f0654e38"},{"path":"src/service/corehr/v1.rs","mode":"100644","type":"blob","sha":"587399c33fcdf431e9c5ba9f88841eec14e34ebd","size":233406,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/587399c33fcdf431e9c5ba9f88841eec14e34ebd"},{"path":"src/service/corehr/v2.rs","mode":"100644","type":"blob","sha":"f933ce680208360a5bc537e18075e414024d7a7a","size":248706,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f933ce680208360a5bc537e18075e414024d7a7a"},{"path":"src/service/directory","mode":"040000","type":"tree","sha":"5bcf0f33a1ea6934b2bba76895adab05f1dd22f3","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/5bcf0f33a1ea6934b2bba76895adab05f1dd22f3"},{"path":"src/service/directory/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/directory/v1.rs","mode":"100644","type":"blob","sha":"0f84c7bd893b5fbe33caea8e30e7b798c450ac05","size":123331,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0f84c7bd893b5fbe33caea8e30e7b798c450ac05"},{"path":"src/service/docs","mode":"040000","type":"tree","sha":"b6193dee849c776cf4d498bc52864c2942dfcf00","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/b6193dee849c776cf4d498bc52864c2942dfcf00"},{"path":"src/service/docs/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/docs/v1.rs","mode":"100644","type":"blob","sha":"6eee94e203014348f94f3eef0227b8530f8b2900","size":5816,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6eee94e203014348f94f3eef0227b8530f8b2900"},{"path":"src/service/docs_ai","mode":"040000","type":"tree","sha":"24857691fe601c058f59d68c654f6c415dd1d69c","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/24857691fe601c058f59d68c654f6c415dd1d69c"},{"path":"src/service/docs_ai/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/docs_ai/v1.rs","mode":"100644","type":"blob","sha":"456d848290516d417e5011750eddb933a7f97b08","size":7504,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/456d848290516d417e5011750eddb933a7f97b08"},{"path":"src/service/document_ai","mode":"040000","type":"tree","sha":"4c83f7db9f0a3089ff5a42f4d8ec73d034ede3a9","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/4c83f7db9f0a3089ff5a42f4d8ec73d034ede3a9"},{"path":"src/service/document_ai/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/document_ai/v1.rs","mode":"100644","type":"blob","sha":"f7659f1772958e60505ae3ca701e32d2d8720a40","size":41325,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f7659f1772958e60505ae3ca701e32d2d8720a40"},{"path":"src/service/docx","mode":"040000","type":"tree","sha":"06b9f12486d5fc84b54bbb4f3a00d568c0f0d5b2","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/06b9f12486d5fc84b54bbb4f3a00d568c0f0d5b2"},{"path":"src/service/docx/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/docx/v1.rs","mode":"100644","type":"blob","sha":"9555da9e57a1746bb9344cf4b18907411b7cc89b","size":93032,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9555da9e57a1746bb9344cf4b18907411b7cc89b"},{"path":"src/service/drive","mode":"040000","type":"tree","sha":"8803da5853101add07d115e78e58ae7c946335c7","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/8803da5853101add07d115e78e58ae7c946335c7"},{"path":"src/service/drive/mod.rs","mode":"100644","type":"blob","sha":"03d4fd000045d0fc60f43b859e9732972da4d208","size":40,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/03d4fd000045d0fc60f43b859e9732972da4d208"},{"path":"src/service/drive/v1.rs","mode":"100644","type":"blob","sha":"21a8eb7589f794274921aa35e7867a2156978299","size":91070,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/21a8eb7589f794274921aa35e7867a2156978299"},{"path":"src/service/drive/v2.rs","mode":"100644","type":"blob","sha":"945ad917245ee93123ddf3526287eb3bbc7919ae","size":10016,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/945ad917245ee93123ddf3526287eb3bbc7919ae"},{"path":"src/service/ehr","mode":"040000","type":"tree","sha":"3b4a8e0399a9a383913724f70722385e3946d871","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/3b4a8e0399a9a383913724f70722385e3946d871"},{"path":"src/service/ehr/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/ehr/v1.rs","mode":"100644","type":"blob","sha":"9ad8575de21e4e0b3b53974d7cb46c24088b4eae","size":4466,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9ad8575de21e4e0b3b53974d7cb46c24088b4eae"},{"path":"src/service/event","mode":"040000","type":"tree","sha":"a894d04fb4b2755a22ab50d0bb47833a808971b0","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/a894d04fb4b2755a22ab50d0bb47833a808971b0"},{"path":"src/service/event/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/event/v1.rs","mode":"100644","type":"blob","sha":"a2e60fa937f15613e43d3d78a0ec51a0479398af","size":1680,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a2e60fa937f15613e43d3d78a0ec51a0479398af"},{"path":"src/service/ext","mode":"040000","type":"tree","sha":"36ca17535755a46aa928911ab80eebfd803a3f6f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/36ca17535755a46aa928911ab80eebfd803a3f6f"},{"path":"src/service/ext/api.rs","mode":"100644","type":"blob","sha":"a78b77b312019ec7c818f933a91a00f2a6450dfb","size":6092,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a78b77b312019ec7c818f933a91a00f2a6450dfb"},{"path":"src/service/ext/mod.rs","mode":"100644","type":"blob","sha":"f2d6eb5aa229859c5a41ffcd4e029668088b702c","size":30,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f2d6eb5aa229859c5a41ffcd4e029668088b702c"},{"path":"src/service/face_detection","mode":"040000","type":"tree","sha":"e56eb742fbf71881e684afbae50cf6c053515fe4","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e56eb742fbf71881e684afbae50cf6c053515fe4"},{"path":"src/service/face_detection/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/face_detection/v1.rs","mode":"100644","type":"blob","sha":"fa0d1c6e466e9ed1b796e1988b9eea3063c84138","size":2115,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fa0d1c6e466e9ed1b796e1988b9eea3063c84138"},{"path":"src/service/go_compatibility.rs","mode":"100644","type":"blob","sha":"b40573a8df7ba33e18100ce2b436f8e7c1a4020e","size":6818,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b40573a8df7ba33e18100ce2b436f8e7c1a4020e"},{"path":"src/service/go_compatibility_metadata.rs","mode":"100644","type":"blob","sha":"020056b84eb5da906c02140445d750c476c08a71","size":107422,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/020056b84eb5da906c02140445d750c476c08a71"},{"path":"src/service/helpdesk","mode":"040000","type":"tree","sha":"c39ddc1506f1401c0314e90a1f3d171c98e110d7","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/c39ddc1506f1401c0314e90a1f3d171c98e110d7"},{"path":"src/service/helpdesk/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/helpdesk/v1.rs","mode":"100644","type":"blob","sha":"7fd8e1fba60902e16cd3fc6c583e1af65dff203b","size":76654,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7fd8e1fba60902e16cd3fc6c583e1af65dff203b"},{"path":"src/service/hire","mode":"040000","type":"tree","sha":"2f318d465d341e6275286f39b082c6d9d7fbc31b","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/2f318d465d341e6275286f39b082c6d9d7fbc31b"},{"path":"src/service/hire/mod.rs","mode":"100644","type":"blob","sha":"3eb05d3b52a2cab9007ae11a6a02ec9a53abd60e","size":52,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3eb05d3b52a2cab9007ae11a6a02ec9a53abd60e"},{"path":"src/service/hire/shared.rs","mode":"100644","type":"blob","sha":"2fb1847233feef9548774f8b053004cc19f3e6be","size":1922,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2fb1847233feef9548774f8b053004cc19f3e6be"},{"path":"src/service/hire/v1.rs","mode":"100644","type":"blob","sha":"62b69b1194f322242e34b881c49a7b67698e2558","size":513851,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/62b69b1194f322242e34b881c49a7b67698e2558"},{"path":"src/service/hire/v2.rs","mode":"100644","type":"blob","sha":"c782430eacacfaeac039e8c1a62a2d1a27d28efd","size":28947,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c782430eacacfaeac039e8c1a62a2d1a27d28efd"},{"path":"src/service/human_authentication","mode":"040000","type":"tree","sha":"b1ce986b4e42e57141182468baa4336a2756c6c8","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/b1ce986b4e42e57141182468baa4336a2756c6c8"},{"path":"src/service/human_authentication/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/human_authentication/v1.rs","mode":"100644","type":"blob","sha":"fc08465a0ab15bb91fed191415ebb306c752504c","size":2772,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fc08465a0ab15bb91fed191415ebb306c752504c"},{"path":"src/service/im","mode":"040000","type":"tree","sha":"f6e59e8783013d1ea97f0f018f420ca819d32fa4","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/f6e59e8783013d1ea97f0f018f420ca819d32fa4"},{"path":"src/service/im/mod.rs","mode":"100644","type":"blob","sha":"85307cc80d847d8e016e700ec5c823b9f0654e38","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/85307cc80d847d8e016e700ec5c823b9f0654e38"},{"path":"src/service/im/v1.rs","mode":"100644","type":"blob","sha":"90505be36d2113b73240e47cc045cd5261d4e05a","size":107721,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/90505be36d2113b73240e47cc045cd5261d4e05a"},{"path":"src/service/im/v2.rs","mode":"100644","type":"blob","sha":"2a5898ae8dac893d56680e5718cd1f67cf6b6bbe","size":29377,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2a5898ae8dac893d56680e5718cd1f67cf6b6bbe"},{"path":"src/service/lingo","mode":"040000","type":"tree","sha":"2d3a3f40756720bc0669b171e6b16fc17466c765","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/2d3a3f40756720bc0669b171e6b16fc17466c765"},{"path":"src/service/lingo/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/lingo/v1.rs","mode":"100644","type":"blob","sha":"1125a2d5a5b16c240b1142dd5c458709b180bbc1","size":33741,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1125a2d5a5b16c240b1142dd5c458709b180bbc1"},{"path":"src/service/mail","mode":"040000","type":"tree","sha":"3228111c33e003485b7b5a0104f8eee75a43ae4e","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/3228111c33e003485b7b5a0104f8eee75a43ae4e"},{"path":"src/service/mail/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/mail/v1.rs","mode":"100644","type":"blob","sha":"d96e67931ad3321598d7648ae66bccc1d397c9c6","size":110624,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d96e67931ad3321598d7648ae66bccc1d397c9c6"},{"path":"src/service/mdm","mode":"040000","type":"tree","sha":"fe0d802da8810cf3cc46e02c73384659aa583d2f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/fe0d802da8810cf3cc46e02c73384659aa583d2f"},{"path":"src/service/mdm/mod.rs","mode":"100644","type":"blob","sha":"350461f8f1569ed6b12b438e9a12bebf0c165dd4","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/350461f8f1569ed6b12b438e9a12bebf0c165dd4"},{"path":"src/service/mdm/v1.rs","mode":"100644","type":"blob","sha":"c42084584f69f44057eec655e9f2ca7f858da065","size":8164,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c42084584f69f44057eec655e9f2ca7f858da065"},{"path":"src/service/mdm/v3.rs","mode":"100644","type":"blob","sha":"4d0397be65dd1530fe4eec96b93b55f585dd3ce8","size":9968,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4d0397be65dd1530fe4eec96b93b55f585dd3ce8"},{"path":"src/service/meeting_room","mode":"040000","type":"tree","sha":"a8ab53978f895ef4e309b91263d1452b24ed3222","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/a8ab53978f895ef4e309b91263d1452b24ed3222"},{"path":"src/service/meeting_room/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/meeting_room/v1.rs","mode":"100644","type":"blob","sha":"33f5e67fb1060b173a9da2c48e8b759108517114","size":6547,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/33f5e67fb1060b173a9da2c48e8b759108517114"},{"path":"src/service/minutes","mode":"040000","type":"tree","sha":"ba0a1e590ed5a09594ebb2c86276a76c61410933","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/ba0a1e590ed5a09594ebb2c86276a76c61410933"},{"path":"src/service/minutes/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/minutes/v1.rs","mode":"100644","type":"blob","sha":"57f832a9f9c4d6e347e8442ccc87530226377eb2","size":12146,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/57f832a9f9c4d6e347e8442ccc87530226377eb2"},{"path":"src/service/mod.rs","mode":"100644","type":"blob","sha":"37bc04a32ad20a09fcf6ded5a1096277fe9d83cd","size":1232,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/37bc04a32ad20a09fcf6ded5a1096277fe9d83cd"},{"path":"src/service/moments","mode":"040000","type":"tree","sha":"819fb0f4af7adbe6dbf2dca862f8140a4203e9d5","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/819fb0f4af7adbe6dbf2dca862f8140a4203e9d5"},{"path":"src/service/moments/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/moments/v1.rs","mode":"100644","type":"blob","sha":"60b8130f71da797bbe9c003a4f6999e301abbe4d","size":5153,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/60b8130f71da797bbe9c003a4f6999e301abbe4d"},{"path":"src/service/okr","mode":"040000","type":"tree","sha":"88dd359509961bb2eb9f90d7a1121b0a96073476","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/88dd359509961bb2eb9f90d7a1121b0a96073476"},{"path":"src/service/okr/mod.rs","mode":"100644","type":"blob","sha":"98fc2ab1b620e156881ddcd371824d8bcc0f02d1","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/98fc2ab1b620e156881ddcd371824d8bcc0f02d1"},{"path":"src/service/okr/v1.rs","mode":"100644","type":"blob","sha":"e36b3447907180757d8b38633d8b4b733333c54b","size":40282,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e36b3447907180757d8b38633d8b4b733333c54b"},{"path":"src/service/okr/v2.rs","mode":"100644","type":"blob","sha":"7284ed4e61bb6029788dbb66fc6103cd713d877f","size":35428,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7284ed4e61bb6029788dbb66fc6103cd713d877f"},{"path":"src/service/optical_char_recognition","mode":"040000","type":"tree","sha":"c23c9f2b9482aef02b1c4ff5c759305bd2a6a0fa","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/c23c9f2b9482aef02b1c4ff5c759305bd2a6a0fa"},{"path":"src/service/optical_char_recognition/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/optical_char_recognition/v1.rs","mode":"100644","type":"blob","sha":"63192459cc987ac8cf9ff8c4ccf8c67e7f280221","size":2120,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/63192459cc987ac8cf9ff8c4ccf8c67e7f280221"},{"path":"src/service/passport","mode":"040000","type":"tree","sha":"f8bf5466448f43d48554648494cef9da4ddfed49","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/f8bf5466448f43d48554648494cef9da4ddfed49"},{"path":"src/service/passport/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/passport/v1.rs","mode":"100644","type":"blob","sha":"af5e1a86621b3c3443e4ed1df50c77efa7ae5e01","size":5136,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/af5e1a86621b3c3443e4ed1df50c77efa7ae5e01"},{"path":"src/service/payroll","mode":"040000","type":"tree","sha":"8962f51522f517482e826ad475d0381fb1acfd83","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/8962f51522f517482e826ad475d0381fb1acfd83"},{"path":"src/service/payroll/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/payroll/v1.rs","mode":"100644","type":"blob","sha":"4c13acbcd7e78c200da417068c53c04ea8fde693","size":44304,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4c13acbcd7e78c200da417068c53c04ea8fde693"},{"path":"src/service/performance","mode":"040000","type":"tree","sha":"42e2c76a4994a8f25fbf16cef74fd03d347dc66e","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/42e2c76a4994a8f25fbf16cef74fd03d347dc66e"},{"path":"src/service/performance/mod.rs","mode":"100644","type":"blob","sha":"85307cc80d847d8e016e700ec5c823b9f0654e38","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/85307cc80d847d8e016e700ec5c823b9f0654e38"},{"path":"src/service/performance/v1.rs","mode":"100644","type":"blob","sha":"3532b40ebb46bc8922aa73e545b6b9a4e426977b","size":3298,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3532b40ebb46bc8922aa73e545b6b9a4e426977b"},{"path":"src/service/performance/v2.rs","mode":"100644","type":"blob","sha":"4c8e1c02e95a15845ff229134483be96b812b566","size":46843,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4c8e1c02e95a15845ff229134483be96b812b566"},{"path":"src/service/personal_settings","mode":"040000","type":"tree","sha":"de28c2c0049dd342ccc5b2b9a638eb3ad189d2e9","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/de28c2c0049dd342ccc5b2b9a638eb3ad189d2e9"},{"path":"src/service/personal_settings/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/personal_settings/v1.rs","mode":"100644","type":"blob","sha":"d01c9dd6bffbb4d42d942d60ac00bf57e8c457eb","size":11217,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d01c9dd6bffbb4d42d942d60ac00bf57e8c457eb"},{"path":"src/service/report","mode":"040000","type":"tree","sha":"ae2aa4f043fdd9f7429a35253dceda06baf5c5b7","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/ae2aa4f043fdd9f7429a35253dceda06baf5c5b7"},{"path":"src/service/report/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/report/v1.rs","mode":"100644","type":"blob","sha":"f49f75d1bde9c356508de76f58c21cc35bc4778a","size":9536,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f49f75d1bde9c356508de76f58c21cc35bc4778a"},{"path":"src/service/search","mode":"040000","type":"tree","sha":"03240949521f92efa734e1f991208bac200c7309","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/03240949521f92efa734e1f991208bac200c7309"},{"path":"src/service/search/mod.rs","mode":"100644","type":"blob","sha":"1579018c642e66209557479494d9a5308609dca7","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1579018c642e66209557479494d9a5308609dca7"},{"path":"src/service/search/v2.rs","mode":"100644","type":"blob","sha":"e1dcd037e27a746619ad480f5e6bf7bc152d46d9","size":42300,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e1dcd037e27a746619ad480f5e6bf7bc152d46d9"},{"path":"src/service/security_and_compliance","mode":"040000","type":"tree","sha":"95df11ead6ea4b552d280f299debd3ae4dcc31e7","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/95df11ead6ea4b552d280f299debd3ae4dcc31e7"},{"path":"src/service/security_and_compliance/mod.rs","mode":"100644","type":"blob","sha":"85307cc80d847d8e016e700ec5c823b9f0654e38","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/85307cc80d847d8e016e700ec5c823b9f0654e38"},{"path":"src/service/security_and_compliance/v1.rs","mode":"100644","type":"blob","sha":"42f4ea8c372dacfc64aa9d09e66eae54d49131e9","size":5344,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/42f4ea8c372dacfc64aa9d09e66eae54d49131e9"},{"path":"src/service/security_and_compliance/v2.rs","mode":"100644","type":"blob","sha":"51efdb85cdf5406b22d22e7b9d52abdbb1904906","size":13411,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/51efdb85cdf5406b22d22e7b9d52abdbb1904906"},{"path":"src/service/sheets","mode":"040000","type":"tree","sha":"73293dff32d3470c29734a40f8b2b7780ccdf5dc","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/73293dff32d3470c29734a40f8b2b7780ccdf5dc"},{"path":"src/service/sheets/mod.rs","mode":"100644","type":"blob","sha":"ed07658ba04e15faa5d514421e46ee56fb8adebf","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ed07658ba04e15faa5d514421e46ee56fb8adebf"},{"path":"src/service/sheets/v3.rs","mode":"100644","type":"blob","sha":"93913967f0415ab991d6ffc2afe87059672df112","size":58982,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/93913967f0415ab991d6ffc2afe87059672df112"},{"path":"src/service/slides_ai","mode":"040000","type":"tree","sha":"e5c94a5b1406d719b6c9b8c207cc9721b7d1b89e","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e5c94a5b1406d719b6c9b8c207cc9721b7d1b89e"},{"path":"src/service/slides_ai/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/slides_ai/v1.rs","mode":"100644","type":"blob","sha":"5570babcf4c8dca55af37957df2528fa19cc6697","size":20456,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5570babcf4c8dca55af37957df2528fa19cc6697"},{"path":"src/service/spark","mode":"040000","type":"tree","sha":"c3dcebe385c69e57fe038ad0be833c8a5d46749b","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/c3dcebe385c69e57fe038ad0be833c8a5d46749b"},{"path":"src/service/spark/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/spark/v1.rs","mode":"100644","type":"blob","sha":"b8c62c080c94f9e66053e9260b2b4dc4d02b6d5c","size":51491,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b8c62c080c94f9e66053e9260b2b4dc4d02b6d5c"},{"path":"src/service/speech_to_text","mode":"040000","type":"tree","sha":"c086b9638123e8abee34854f460284c573f65766","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/c086b9638123e8abee34854f460284c573f65766"},{"path":"src/service/speech_to_text/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/speech_to_text/v1.rs","mode":"100644","type":"blob","sha":"6cf4c3ce3d2acef84965bd820bfa24fd525c047a","size":4115,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6cf4c3ce3d2acef84965bd820bfa24fd525c047a"},{"path":"src/service/task","mode":"040000","type":"tree","sha":"63b1f5f0c217f7848d1505d0b57955feb7b70c14","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/63b1f5f0c217f7848d1505d0b57955feb7b70c14"},{"path":"src/service/task/mod.rs","mode":"100644","type":"blob","sha":"85307cc80d847d8e016e700ec5c823b9f0654e38","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/85307cc80d847d8e016e700ec5c823b9f0654e38"},{"path":"src/service/task/v1.rs","mode":"100644","type":"blob","sha":"1c21304c774317a1f951705622014bf4bd81c4b3","size":48809,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1c21304c774317a1f951705622014bf4bd81c4b3"},{"path":"src/service/task/v2.rs","mode":"100644","type":"blob","sha":"19e772a963994019035c0c0fc2a7a98e496616be","size":117720,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/19e772a963994019035c0c0fc2a7a98e496616be"},{"path":"src/service/tenant","mode":"040000","type":"tree","sha":"bca704020d2f8f3073426b7333338c4aa78d651f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/bca704020d2f8f3073426b7333338c4aa78d651f"},{"path":"src/service/tenant/mod.rs","mode":"100644","type":"blob","sha":"f266b3ec92615a551d1a4a017dfdda7548b4a9a8","size":28,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f266b3ec92615a551d1a4a017dfdda7548b4a9a8"},{"path":"src/service/tenant/v2.rs","mode":"100644","type":"blob","sha":"caedae9e02e63783f1618a9217f50f9c11e3a4e3","size":6816,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/caedae9e02e63783f1618a9217f50f9c11e3a4e3"},{"path":"src/service/translation","mode":"040000","type":"tree","sha":"9bc998c5a1c6010ab83221797dc31e10471c1177","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/9bc998c5a1c6010ab83221797dc31e10471c1177"},{"path":"src/service/translation/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/translation/v1.rs","mode":"100644","type":"blob","sha":"b71298c8a34182f56ba3e928cdd3d9c8f36aecda","size":4029,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b71298c8a34182f56ba3e928cdd3d9c8f36aecda"},{"path":"src/service/trust_party","mode":"040000","type":"tree","sha":"e9f159e77bcb1c96e1938d59b2726a27bd5090ac","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e9f159e77bcb1c96e1938d59b2726a27bd5090ac"},{"path":"src/service/trust_party/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/trust_party/v1.rs","mode":"100644","type":"blob","sha":"eb2ac3e70bf196d9ea8cd9a9838795ee46ff86f3","size":22210,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/eb2ac3e70bf196d9ea8cd9a9838795ee46ff86f3"},{"path":"src/service/unified_kms","mode":"040000","type":"tree","sha":"35859c357121c05f7e243dfe723a675545c03119","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/35859c357121c05f7e243dfe723a675545c03119"},{"path":"src/service/unified_kms/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/unified_kms/v1.rs","mode":"100644","type":"blob","sha":"2324a7ad53e144569644dd48253cecf0962b21a3","size":9187,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2324a7ad53e144569644dd48253cecf0962b21a3"},{"path":"src/service/vc","mode":"040000","type":"tree","sha":"b51f58d1b8b5c30a1ae309781fa30ab257c07d2d","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/b51f58d1b8b5c30a1ae309781fa30ab257c07d2d"},{"path":"src/service/vc/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/vc/v1.rs","mode":"100644","type":"blob","sha":"1edb96c054247fa1a681b5ad4236423b6ff20b8d","size":141796,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1edb96c054247fa1a681b5ad4236423b6ff20b8d"},{"path":"src/service/verification","mode":"040000","type":"tree","sha":"cea8926e95844d2a62d47330687e3d5af80463ca","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/cea8926e95844d2a62d47330687e3d5af80463ca"},{"path":"src/service/verification/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/verification/v1.rs","mode":"100644","type":"blob","sha":"e060f030ca9b3242f1b2f004c1c8775e78d35392","size":6584,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e060f030ca9b3242f1b2f004c1c8775e78d35392"},{"path":"src/service/wiki","mode":"040000","type":"tree","sha":"38846b1d7739bf4dea6f9285690b848af8f5719f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/38846b1d7739bf4dea6f9285690b848af8f5719f"},{"path":"src/service/wiki/mod.rs","mode":"100644","type":"blob","sha":"5d7ecd3671ec5e627c23cf6cd9b8ff26e1c47ece","size":39,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5d7ecd3671ec5e627c23cf6cd9b8ff26e1c47ece"},{"path":"src/service/wiki/v1.rs","mode":"100644","type":"blob","sha":"eb1ded0edf1fd8174984ed08638390aa8c0e7258","size":2515,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/eb1ded0edf1fd8174984ed08638390aa8c0e7258"},{"path":"src/service/wiki/v2.rs","mode":"100644","type":"blob","sha":"b99ff120a22fb95995719472c5e01a6845bfe997","size":19782,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b99ff120a22fb95995719472c5e01a6845bfe997"},{"path":"src/service/workplace","mode":"040000","type":"tree","sha":"e9d4786b6be0d130a4200bd33923a35e52f0cac6","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e9d4786b6be0d130a4200bd33923a35e52f0cac6"},{"path":"src/service/workplace/mod.rs","mode":"100644","type":"blob","sha":"3789969113e5433473e5137f01dd8d3726eee23a","size":27,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3789969113e5433473e5137f01dd8d3726eee23a"},{"path":"src/service/workplace/v1.rs","mode":"100644","type":"blob","sha":"d8f88bb7800168d53d145a137a2aa97d7e4c45c0","size":11207,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d8f88bb7800168d53d145a137a2aa97d7e4c45c0"},{"path":"src/token.rs","mode":"100644","type":"blob","sha":"f6618e558f31a12886a4a5ab7480664851b5017c","size":28841,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f6618e558f31a12886a4a5ab7480664851b5017c"},{"path":"src/transport.rs","mode":"100644","type":"blob","sha":"074c0f2cd110156671a8021ede7c480edd6d08b4","size":42144,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/074c0f2cd110156671a8021ede7c480edd6d08b4"},{"path":"src/ws.rs","mode":"100644","type":"blob","sha":"f623e132a41d6c518e20591fd75c3eb540a421b1","size":91761,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f623e132a41d6c518e20591fd75c3eb540a421b1"},{"path":"tests","mode":"040000","type":"tree","sha":"6591fc9c1cdd28098e5fe84952c927ad9958724f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/6591fc9c1cdd28098e5fe84952c927ad9958724f"},{"path":"tests/acs_event.rs","mode":"100644","type":"blob","sha":"21aab385d52e6e54db4266399f44f03f3fcb8bff","size":1716,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/21aab385d52e6e54db4266399f44f03f3fcb8bff"},{"path":"tests/apaas_event.rs","mode":"100644","type":"blob","sha":"8d5ad99742e1b75c0faf03445e6f1d12d9dbff9b","size":1435,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8d5ad99742e1b75c0faf03445e6f1d12d9dbff9b"},{"path":"tests/application_event.rs","mode":"100644","type":"blob","sha":"891daf3d7e180dff169323af2ec1b32d5c7983c1","size":8008,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/891daf3d7e180dff169323af2ec1b32d5c7983c1"},{"path":"tests/approval_event.rs","mode":"100644","type":"blob","sha":"807df09c12a725a96110182272d6a6aa2bbca211","size":3329,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/807df09c12a725a96110182272d6a6aa2bbca211"},{"path":"tests/auth_event.rs","mode":"100644","type":"blob","sha":"b92be7b37ba63930fee5b5f8829b0d06457da262","size":1206,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b92be7b37ba63930fee5b5f8829b0d06457da262"},{"path":"tests/base_v3_record_live.rs","mode":"100644","type":"blob","sha":"7c88a69d9753581ef587177d1611012a9b3d46c0","size":1813,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7c88a69d9753581ef587177d1611012a9b3d46c0"},{"path":"tests/bitable_iterator.rs","mode":"100644","type":"blob","sha":"0a86809ecebb8c98a64c88737fb0cb70bdedad38","size":5688,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0a86809ecebb8c98a64c88737fb0cb70bdedad38"},{"path":"tests/bot_v4.rs","mode":"100644","type":"blob","sha":"842c9ec7c25615fdfa5e0336d968212a6065e979","size":2019,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/842c9ec7c25615fdfa5e0336d968212a6065e979"},{"path":"tests/cache.rs","mode":"100644","type":"blob","sha":"975e38cdfe4f38284a47a7594efeed512999358b","size":1710,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/975e38cdfe4f38284a47a7594efeed512999358b"},{"path":"tests/calendar_event.rs","mode":"100644","type":"blob","sha":"31cc54cf2a932c806791bc2153b46aa65ee34e3f","size":3384,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/31cc54cf2a932c806791bc2153b46aa65ee34e3f"},{"path":"tests/card.rs","mode":"100644","type":"blob","sha":"6ce22ff050fcf876547975e235f0a98675c31fe1","size":18521,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6ce22ff050fcf876547975e235f0a98675c31fe1"},{"path":"tests/card_builder_template.rs","mode":"100644","type":"blob","sha":"6c5282421edce88aa05274ec21cd3d88ed96c507","size":4180,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6c5282421edce88aa05274ec21cd3d88ed96c507"},{"path":"tests/card_protocol.rs","mode":"100644","type":"blob","sha":"6f07eeada2c027ecae5563c5726b0222bc1b579d","size":126015,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6f07eeada2c027ecae5563c5726b0222bc1b579d"},{"path":"tests/card_protocol_support.rs","mode":"100644","type":"blob","sha":"7e04548d1144dddeb827b62a8123a3b56ff80305","size":881,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7e04548d1144dddeb827b62a8123a3b56ff80305"},{"path":"tests/cardkit_live.rs","mode":"100644","type":"blob","sha":"f7a538aac984a4f1b9dd0f82833ba74b1b421af6","size":2220,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f7a538aac984a4f1b9dd0f82833ba74b1b421af6"},{"path":"tests/cardkit_mutations.rs","mode":"100644","type":"blob","sha":"d36d325eebe8c73150671377c3b2f1fbffb8c42d","size":10360,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d36d325eebe8c73150671377c3b2f1fbffb8c42d"},{"path":"tests/cardkit_streaming.rs","mode":"100644","type":"blob","sha":"d4d9e20c364a98302cfee71060c0993f57791cd9","size":7555,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d4d9e20c364a98302cfee71060c0993f57791cd9"},{"path":"tests/client_builder.rs","mode":"100644","type":"blob","sha":"5cf40bd5c824c4935423c8d961920a3245370ddd","size":8593,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5cf40bd5c824c4935423c8d961920a3245370ddd"},{"path":"tests/common","mode":"040000","type":"tree","sha":"057f142325040f82ad9c2c2d8d7da04d6212756a","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/057f142325040f82ad9c2c2d8d7da04d6212756a"},{"path":"tests/common/mod.rs","mode":"100644","type":"blob","sha":"84c014b8ad65a1f5397e8414b29d18a2b8c1b175","size":7967,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/84c014b8ad65a1f5397e8414b29d18a2b8c1b175"},{"path":"tests/compensation_event.rs","mode":"100644","type":"blob","sha":"33dc5fb8ba32834972dc61bfbdc00dc60694bbe8","size":1369,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/33dc5fb8ba32834972dc61bfbdc00dc60694bbe8"},{"path":"tests/config.rs","mode":"100644","type":"blob","sha":"eb8294cb9dc82a9f4a53a9f6e810fe8473e7899f","size":1924,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/eb8294cb9dc82a9f4a53a9f6e810fe8473e7899f"},{"path":"tests/constants.rs","mode":"100644","type":"blob","sha":"ea45bb10ff07be1b714480ecc3dbbe7b914e688b","size":1658,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ea45bb10ff07be1b714480ecc3dbbe7b914e688b"},{"path":"tests/contact_event.rs","mode":"100644","type":"blob","sha":"3a7277db71954206a308f03bf7df391298aefaa4","size":6063,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3a7277db71954206a308f03bf7df391298aefaa4"},{"path":"tests/contact_iterator.rs","mode":"100644","type":"blob","sha":"86bbd16a2b07a7cc69c4f0854d6568a599f7c36d","size":6202,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/86bbd16a2b07a7cc69c4f0854d6568a599f7c36d"},{"path":"tests/corehr_event.rs","mode":"100644","type":"blob","sha":"0080d320a98f0ee5ca22017eb126454b2dda2d35","size":4989,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0080d320a98f0ee5ca22017eb126454b2dda2d35"},{"path":"tests/corehr_offboarding_iterator.rs","mode":"100644","type":"blob","sha":"ab7188a0bb3c3b87e48f80202d1173c8a492d387","size":3917,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ab7188a0bb3c3b87e48f80202d1173c8a492d387"},{"path":"tests/corehr_v2_event.rs","mode":"100644","type":"blob","sha":"e78afd70f0fadad73f6cf6e01058d4580b941e39","size":7178,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e78afd70f0fadad73f6cf6e01058d4580b941e39"},{"path":"tests/crypto.rs","mode":"100644","type":"blob","sha":"91000a65f42bbeb73365286d3fcbb7479e1024a2","size":7649,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/91000a65f42bbeb73365286d3fcbb7479e1024a2"},{"path":"tests/drive_event.rs","mode":"100644","type":"blob","sha":"76c53e1bca90fe53426e218725fa6920fd26e761","size":9827,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/76c53e1bca90fe53426e218725fa6920fd26e761"},{"path":"tests/drive_iterator.rs","mode":"100644","type":"blob","sha":"c24a665d22874b7cbd93c092bfc57f7b72c7a12b","size":5551,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c24a665d22874b7cbd93c092bfc57f7b72c7a12b"},{"path":"tests/elearning_event.rs","mode":"100644","type":"blob","sha":"37d3fb7f83bc5f73ca790b5b2f8a3bcd1785b223","size":2500,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/37d3fb7f83bc5f73ca790b5b2f8a3bcd1785b223"},{"path":"tests/error.rs","mode":"100644","type":"blob","sha":"f2357d61c42dbf4d72190ea84067abf38f535ddb","size":2420,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f2357d61c42dbf4d72190ea84067abf38f535ddb"},{"path":"tests/event_dispatch.rs","mode":"100644","type":"blob","sha":"a3351d16b9c86efe8c367a18b11e1d7962df9948","size":11509,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a3351d16b9c86efe8c367a18b11e1d7962df9948"},{"path":"tests/event_v2.rs","mode":"100644","type":"blob","sha":"cc040378088514af91dc6e13c3bd3c9997763238","size":48195,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cc040378088514af91dc6e13c3bd3c9997763238"},{"path":"tests/fixtures","mode":"040000","type":"tree","sha":"5ae963d24483ff49c2888101c1e3f456a80b46fd","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/5ae963d24483ff49c2888101c1e3f456a80b46fd"},{"path":"tests/fixtures/card_protocol","mode":"040000","type":"tree","sha":"bb42e2c4904aa8ed3168a74c8644b74ca079612e","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/bb42e2c4904aa8ed3168a74c8644b74ca079612e"},{"path":"tests/fixtures/card_protocol/card_builder.json","mode":"100644","type":"blob","sha":"3da58ce8a8b162a0e45b3091c5d3048b6d0b55f7","size":2308,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3da58ce8a8b162a0e45b3091c5d3048b6d0b55f7"},{"path":"tests/fixtures/card_protocol/card_builder","mode":"040000","type":"tree","sha":"7dbdc0e6ded5809a62855d17b5c16e99ba7a2a07","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/7dbdc0e6ded5809a62855d17b5c16e99ba7a2a07"},{"path":"tests/fixtures/card_protocol/card_builder/published_template_message.json","mode":"100644","type":"blob","sha":"6caa3a1a04ea78856076a13d719a5be26724c1c0","size":344,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6caa3a1a04ea78856076a13d719a5be26724c1c0"},{"path":"tests/fixtures/card_protocol/card_json_v1.json","mode":"100644","type":"blob","sha":"b1f72d19f77e3fa57461c97d68ad41371c2e5485","size":8296,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b1f72d19f77e3fa57461c97d68ad41371c2e5485"},{"path":"tests/fixtures/card_protocol/card_json_v1","mode":"040000","type":"tree","sha":"1b8d87236342a35abb84b5ce98d32c45c19e77b9","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/1b8d87236342a35abb84b5ce98d32c45c19e77b9"},{"path":"tests/fixtures/card_protocol/card_json_v1/all_components.json","mode":"100644","type":"blob","sha":"ca97f949ceab17b3e3fe0e40bcc8b3cf93cb8d16","size":3285,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ca97f949ceab17b3e3fe0e40bcc8b3cf93cb8d16"},{"path":"tests/fixtures/card_protocol/card_json_v1/basic_card.json","mode":"100644","type":"blob","sha":"ef120f004f94e1d308b509d8c4c98d671b5a94a9","size":653,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ef120f004f94e1d308b509d8c4c98d671b5a94a9"},{"path":"tests/fixtures/card_protocol/card_json_v1/callback_button.json","mode":"100644","type":"blob","sha":"715a1fc7af9d04adcd91b6241777462457a24d1a","size":241,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/715a1fc7af9d04adcd91b6241777462457a24d1a"},{"path":"tests/fixtures/card_protocol/card_json_v1/card_action_trigger.json","mode":"100644","type":"blob","sha":"6fa4e7564ec84e1b57acca5e932889d7d33e84f7","size":399,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6fa4e7564ec84e1b57acca5e932889d7d33e84f7"},{"path":"tests/fixtures/card_protocol/card_json_v1/modern_v1_chart_card.json","mode":"100644","type":"blob","sha":"278dfed8f086d28f61cd9e444849560f1becee3a","size":2567,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/278dfed8f086d28f61cd9e444849560f1becee3a"},{"path":"tests/fixtures/card_protocol/card_json_v2.json","mode":"100644","type":"blob","sha":"b809a9782cf6b07eaaf8e5cfb5d5c35a64ca5d44","size":54835,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b809a9782cf6b07eaaf8e5cfb5d5c35a64ca5d44"},{"path":"tests/fixtures/card_protocol/card_json_v2","mode":"040000","type":"tree","sha":"9685b2c0f386dce5f8d9bc82505f2f52465e59fe","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/9685b2c0f386dce5f8d9bc82505f2f52465e59fe"},{"path":"tests/fixtures/card_protocol/card_json_v2/callback_button.json","mode":"100644","type":"blob","sha":"5344ba2273ca9c4923fdb08dbd4652d463cd4940","size":522,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5344ba2273ca9c4923fdb08dbd4652d463cd4940"},{"path":"tests/fixtures/card_protocol/card_json_v2/column_layouts.json","mode":"100644","type":"blob","sha":"c4d961ef253bbb1bc93aab60ea0bca703f1f87e3","size":918,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c4d961ef253bbb1bc93aab60ea0bca703f1f87e3"},{"path":"tests/fixtures/card_protocol/card_json_v2/complete_root.json","mode":"100644","type":"blob","sha":"9e2c3384f156595b091e48f078d42d1c44810c8c","size":2580,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9e2c3384f156595b091e48f078d42d1c44810c8c"},{"path":"tests/fixtures/card_protocol/card_json_v2/containers_behaviors.json","mode":"100644","type":"blob","sha":"a7b5a38bc74c4d9171ae93213e0e244dfbe3bb37","size":2174,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a7b5a38bc74c4d9171ae93213e0e244dfbe3bb37"},{"path":"tests/fixtures/card_protocol/card_json_v2/controls.json","mode":"100644","type":"blob","sha":"f1e3d2d5eb428716da1a407c34835afa4a693085","size":1157,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f1e3d2d5eb428716da1a407c34835afa4a693085"},{"path":"tests/fixtures/card_protocol/card_json_v2/display_layout.json","mode":"100644","type":"blob","sha":"b774652fccbed6b9c7c4bfa7f74c9575ca5c4855","size":3914,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b774652fccbed6b9c7c4bfa7f74c9575ca5c4855"},{"path":"tests/fixtures/card_protocol/card_json_v2/form_action_controls.json","mode":"100644","type":"blob","sha":"ff348af08d5d684f3cfa904afeec639a32ccfcd2","size":3130,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ff348af08d5d684f3cfa904afeec639a32ccfcd2"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_button_text.json","mode":"100644","type":"blob","sha":"47b5f5d869a1a69a63944bc41a2b5037d6ca2f0f","size":236,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/47b5f5d869a1a69a63944bc41a2b5037d6ca2f0f"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_card_link.json","mode":"100644","type":"blob","sha":"dda8ddf60882de01d584781320a0ee152b22b53e","size":144,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/dda8ddf60882de01d584781320a0ee152b22b53e"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_collapsible_panel_form.json","mode":"100644","type":"blob","sha":"2061dcca3e39632ed069f20cd6dd72871ca91a17","size":277,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2061dcca3e39632ed069f20cd6dd72871ca91a17"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_column_layout.json","mode":"100644","type":"blob","sha":"93adab4a95801a75c557b6251e44a622b40c1234","size":383,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/93adab4a95801a75c557b6251e44a622b40c1234"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_cross_field_constraints.json","mode":"100644","type":"blob","sha":"ab7d8b9de6ac60870055bdebf3792396d1a8d968","size":346,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ab7d8b9de6ac60870055bdebf3792396d1a8d968"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_duplicate_form_control_name.json","mode":"100644","type":"blob","sha":"a49894965bbc9dfb5f5fae9a63edf283d4d1fc05","size":661,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a49894965bbc9dfb5f5fae9a63edf283d4d1fc05"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_form_action_outside_form.json","mode":"100644","type":"blob","sha":"6e6b6915ed0dec3786c8c2566d580912bbe11c03","size":273,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6e6b6915ed0dec3786c8c2566d580912bbe11c03"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_form_control_name.json","mode":"100644","type":"blob","sha":"6a803595b66c384eaee650d109f67d93fe2a2543","size":387,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6a803595b66c384eaee650d109f67d93fe2a2543"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_header_title_lines.json","mode":"100644","type":"blob","sha":"d0701e262d2d6db72f05f259f9541bfdfccf38d5","size":173,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d0701e262d2d6db72f05f259f9541bfdfccf38d5"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_image_corner_radius.json","mode":"100644","type":"blob","sha":"d9e9271a3661c25e588f2bd1f86d78b0d1825a99","size":244,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d9e9271a3661c25e588f2bd1f86d78b0d1825a99"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_image_preview_outside_form.json","mode":"100644","type":"blob","sha":"a91a9072c824958c4a75ed1bb3a928e9a4b1a313","size":274,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a91a9072c824958c4a75ed1bb3a928e9a4b1a313"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_img_combination_count.json","mode":"100644","type":"blob","sha":"b926ceca3082e7e5a93fcf564d9df01e6485efe1","size":250,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b926ceca3082e7e5a93fcf564d9df01e6485efe1"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_img_size_scale.json","mode":"100644","type":"blob","sha":"a9252aabbe1850f2a37b855a7abe255effbc705f","size":241,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a9252aabbe1850f2a37b855a7abe255effbc705f"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_open_url.json","mode":"100644","type":"blob","sha":"cd56a36fe3484034a20316275d9bfa0f41826836","size":268,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cd56a36fe3484034a20316275d9bfa0f41826836"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_picker_initial_value.json","mode":"100644","type":"blob","sha":"04671b99d64037b36a81e54e1b265187b6b7bd14","size":164,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/04671b99d64037b36a81e54e1b265187b6b7bd14"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_plain_text_slot.json","mode":"100644","type":"blob","sha":"a59d1a96c3fa5851911bd1e9c679e616c1cef50b","size":196,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a59d1a96c3fa5851911bd1e9c679e616c1cef50b"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_streaming_config.json","mode":"100644","type":"blob","sha":"5125b77a69c26a4b30a55d5ee8ae3a036d0411de","size":159,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5125b77a69c26a4b30a55d5ee8ae3a036d0411de"},{"path":"tests/fixtures/card_protocol/card_json_v2/invalid_table_row_max_height.json","mode":"100644","type":"blob","sha":"cf2e77523e37090a975f5c646f78f6b3fa08394b","size":237,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cf2e77523e37090a975f5c646f78f6b3fa08394b"},{"path":"tests/fixtures/card_protocol/card_json_v2/missing_body.json","mode":"100644","type":"blob","sha":"be4eb17c496611c043bd83fd8f772a506b4d6417","size":58,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/be4eb17c496611c043bd83fd8f772a506b4d6417"},{"path":"tests/fixtures/card_protocol/card_json_v2/missing_shared_config.json","mode":"100644","type":"blob","sha":"f64730efcd88a2d80834c046be8be29cc5bc7f00","size":50,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f64730efcd88a2d80834c046be8be29cc5bc7f00"},{"path":"tests/fixtures/card_protocol/card_json_v2/root_header.json","mode":"100644","type":"blob","sha":"76f2c17d5f9debfc0c182c01f32d7faeadd9d509","size":1812,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/76f2c17d5f9debfc0c182c01f32d7faeadd9d509"},{"path":"tests/fixtures/card_protocol/card_json_v2/selection_picker_controls.json","mode":"100644","type":"blob","sha":"dfd636e43ffbb023c511dcfc118596b3d9ccc9f7","size":3348,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/dfd636e43ffbb023c511dcfc118596b3d9ccc9f7"},{"path":"tests/fixtures/card_protocol/cardkit_v1.json","mode":"100644","type":"blob","sha":"37f86c4cd788c65b9670ce847c914379c41912c5","size":5184,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/37f86c4cd788c65b9670ce847c914379c41912c5"},{"path":"tests/fixtures/card_protocol/cardkit_v1","mode":"040000","type":"tree","sha":"360e9043645d1835aea61c5a3ea99c97dbe26274","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/360e9043645d1835aea61c5a3ea99c97dbe26274"},{"path":"tests/fixtures/card_protocol/cardkit_v1/batch_update.json","mode":"100644","type":"blob","sha":"c3dc4f9e3cb7b30aad77a29cad4983e669c5244f","size":712,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c3dc4f9e3cb7b30aad77a29cad4983e669c5244f"},{"path":"tests/fixtures/card_protocol/cardkit_v1/element_create.json","mode":"100644","type":"blob","sha":"01a721f25e708c59ed5155c29684fad7c2c8c76e","size":172,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/01a721f25e708c59ed5155c29684fad7c2c8c76e"},{"path":"tests/fixtures/card_protocol/cardkit_v1/element_delete.json","mode":"100644","type":"blob","sha":"50f4ba3301aecb7afde91ca0fe5842cb92a21fa0","size":50,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/50f4ba3301aecb7afde91ca0fe5842cb92a21fa0"},{"path":"tests/fixtures/card_protocol/cardkit_v1/element_patch.json","mode":"100644","type":"blob","sha":"8d60f746330cd6a79c8e2dc2e89650bfd97f337b","size":100,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8d60f746330cd6a79c8e2dc2e89650bfd97f337b"},{"path":"tests/fixtures/card_protocol/cardkit_v1/element_update.json","mode":"100644","type":"blob","sha":"8d9dec158a3245b2fbaf5a4625b6ce6f5332e113","size":148,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8d9dec158a3245b2fbaf5a4625b6ce6f5332e113"},{"path":"tests/fixtures/card_protocol/cardkit_v1/entity_message.json","mode":"100644","type":"blob","sha":"e4f8796e7030b90ea521b14430558208a259131f","size":75,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e4f8796e7030b90ea521b14430558208a259131f"},{"path":"tests/fixtures/card_protocol/cardkit_v1/settings.json","mode":"100644","type":"blob","sha":"0da82b87d9c9cf72aa54899937dea19b232b74d2","size":153,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0da82b87d9c9cf72aa54899937dea19b232b74d2"},{"path":"tests/fixtures/card_protocol/cardkit_v1/streaming_content.json","mode":"100644","type":"blob","sha":"61e7a54b766067e8a78feb72d3a4bc087a04e89d","size":76,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/61e7a54b766067e8a78feb72d3a4bc087a04e89d"},{"path":"tests/fixtures/card_protocol/cardkit_v1/streaming_document.json","mode":"100644","type":"blob","sha":"955eb4ecfff287b820e9f54e5de41071f52a484b","size":228,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/955eb4ecfff287b820e9f54e5de41071f52a484b"},{"path":"tests/fixtures/card_protocol/cardkit_v1/template_instance.json","mode":"100644","type":"blob","sha":"68e25b332c820c83b87573371507a90a2218c176","size":83,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/68e25b332c820c83b87573371507a90a2218c176"},{"path":"tests/generated_service_smoke.rs","mode":"100644","type":"blob","sha":"f36433586fb6dcb4b5c5ce72151cc0bfc1c1eea0","size":14771,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f36433586fb6dcb4b5c5ce72151cc0bfc1c1eea0"},{"path":"tests/generated_service_smoke","mode":"040000","type":"tree","sha":"5a7ea7858afc7f852f88383add10d025bedf7d4b","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/5a7ea7858afc7f852f88383add10d025bedf7d4b"},{"path":"tests/generated_service_smoke/acs","mode":"040000","type":"tree","sha":"3f8b9d20a94eb1237b9276b244792517a8313714","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/3f8b9d20a94eb1237b9276b244792517a8313714"},{"path":"tests/generated_service_smoke/acs/access_record.rs","mode":"100644","type":"blob","sha":"b3eb8860295456bea3a1297c8096affbfd0bdcd3","size":1483,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b3eb8860295456bea3a1297c8096affbfd0bdcd3"},{"path":"tests/generated_service_smoke/acs/device.rs","mode":"100644","type":"blob","sha":"ba8e5c0d01b5037dad26973e8842c71dfaaee2cf","size":709,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ba8e5c0d01b5037dad26973e8842c71dfaaee2cf"},{"path":"tests/generated_service_smoke/acs/face_visitor.rs","mode":"100644","type":"blob","sha":"51325b7f0f30983af87ff18db2420bcf4c6542a3","size":3585,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/51325b7f0f30983af87ff18db2420bcf4c6542a3"},{"path":"tests/generated_service_smoke/acs/rule.rs","mode":"100644","type":"blob","sha":"63fe57bf070aeed5c4b2958286a06c793b7f667d","size":3613,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/63fe57bf070aeed5c4b2958286a06c793b7f667d"},{"path":"tests/generated_service_smoke/acs/user.rs","mode":"100644","type":"blob","sha":"3d628b621cf70772432d14160421226297536354","size":2926,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3d628b621cf70772432d14160421226297536354"},{"path":"tests/generated_service_smoke/admin_v1","mode":"040000","type":"tree","sha":"f260a3b5a67d167f7bbf10c9f4399c791501297e","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/f260a3b5a67d167f7bbf10c9f4399c791501297e"},{"path":"tests/generated_service_smoke/admin_v1/audit.rs","mode":"100644","type":"blob","sha":"57b30befa2006b42516973da914787ce17a19d5f","size":1508,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/57b30befa2006b42516973da914787ce17a19d5f"},{"path":"tests/generated_service_smoke/admin_v1/badge.rs","mode":"100644","type":"blob","sha":"74045f0db1d55821d3a0232d03951d5ae2a37319","size":1942,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/74045f0db1d55821d3a0232d03951d5ae2a37319"},{"path":"tests/generated_service_smoke/admin_v1/badge_grant.rs","mode":"100644","type":"blob","sha":"a8b5b98ac4424e99f4fbbb09230937a19a93a26a","size":2321,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a8b5b98ac4424e99f4fbbb09230937a19a93a26a"},{"path":"tests/generated_service_smoke/admin_v1/stat.rs","mode":"100644","type":"blob","sha":"cafe160d59ba833f8d789778763099b76d54efb9","size":4360,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cafe160d59ba833f8d789778763099b76d54efb9"},{"path":"tests/generated_service_smoke/admin_v1/write.rs","mode":"100644","type":"blob","sha":"bb180e14a9fd16375fdb7ce5a6e671ff8392dc48","size":5514,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bb180e14a9fd16375fdb7ce5a6e671ff8392dc48"},{"path":"tests/generated_service_smoke/application_v1.rs","mode":"100644","type":"blob","sha":"1248be83e6dd1e35e92ea34464e4f03a35b11c6d","size":5708,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1248be83e6dd1e35e92ea34464e4f03a35b11c6d"},{"path":"tests/generated_service_smoke/application_v5_v7.rs","mode":"100644","type":"blob","sha":"18b831134ec156a245782c1b14f4db81a5a5609f","size":11636,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/18b831134ec156a245782c1b14f4db81a5a5609f"},{"path":"tests/generated_service_smoke/application_v6","mode":"040000","type":"tree","sha":"d2901a9c67af95cbd834ba2a5f58d26dc5b9426f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/d2901a9c67af95cbd834ba2a5f58d26dc5b9426f"},{"path":"tests/generated_service_smoke/application_v6/app_version.rs","mode":"100644","type":"blob","sha":"3eabf0f6c2010dd37609e6e79fb3f1327129b11e","size":4268,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3eabf0f6c2010dd37609e6e79fb3f1327129b11e"},{"path":"tests/generated_service_smoke/application_v6/application.rs","mode":"100644","type":"blob","sha":"de25a2d6cee41852eef29c95ebb0b8e3960f8625","size":7494,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/de25a2d6cee41852eef29c95ebb0b8e3960f8625"},{"path":"tests/generated_service_smoke/application_v6/collaboration.rs","mode":"100644","type":"blob","sha":"3b5776ccb5cd8025a176f9845ce48e13f76641fe","size":2379,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3b5776ccb5cd8025a176f9845ce48e13f76641fe"},{"path":"tests/generated_service_smoke/application_v6/feedback.rs","mode":"100644","type":"blob","sha":"d288e4d838f14d82f9a178836b199a0fcbac86c4","size":2334,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d288e4d838f14d82f9a178836b199a0fcbac86c4"},{"path":"tests/generated_service_smoke/application_v6/management.rs","mode":"100644","type":"blob","sha":"cc9eb53a044b9b0bd61d50702674ac7c617ce6f4","size":1313,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cc9eb53a044b9b0bd61d50702674ac7c617ce6f4"},{"path":"tests/generated_service_smoke/application_v6/recommend_rule.rs","mode":"100644","type":"blob","sha":"1137f994cb361cd3753370f757edee80740108c8","size":1100,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1137f994cb361cd3753370f757edee80740108c8"},{"path":"tests/generated_service_smoke/application_v6/utility.rs","mode":"100644","type":"blob","sha":"da61f246310511343044039bbb349327fc38bc4e","size":1382,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/da61f246310511343044039bbb349327fc38bc4e"},{"path":"tests/generated_service_smoke/application_v6/visibility.rs","mode":"100644","type":"blob","sha":"e5129dcd9dc6a89eee09e54c242c4d1676b8cb6b","size":1632,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e5129dcd9dc6a89eee09e54c242c4d1676b8cb6b"},{"path":"tests/generated_service_smoke/approval","mode":"040000","type":"tree","sha":"fda1d91bbfbdf6b739ee73e399a8b9d8629aae0a","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/fda1d91bbfbdf6b739ee73e399a8b9d8629aae0a"},{"path":"tests/generated_service_smoke/approval/comment.rs","mode":"100644","type":"blob","sha":"47c4fc2a0dccf2de3fa2905ae4c89850b93f6567","size":1111,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/47c4fc2a0dccf2de3fa2905ae4c89850b93f6567"},{"path":"tests/generated_service_smoke/approval/definition.rs","mode":"100644","type":"blob","sha":"8dbce959e4ad195e6cd6afe192028b2cc486d03d","size":1887,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8dbce959e4ad195e6cd6afe192028b2cc486d03d"},{"path":"tests/generated_service_smoke/approval/external.rs","mode":"100644","type":"blob","sha":"22afb28fc2069629c73d5480f18d42f35517d7c2","size":2152,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/22afb28fc2069629c73d5480f18d42f35517d7c2"},{"path":"tests/generated_service_smoke/approval/instance.rs","mode":"100644","type":"blob","sha":"327fa4b1a29a627693baf9c1775ce9d325cda8e0","size":4788,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/327fa4b1a29a627693baf9c1775ce9d325cda8e0"},{"path":"tests/generated_service_smoke/approval/task.rs","mode":"100644","type":"blob","sha":"2c4d2266d1f960454876091492c6abbe956c06e1","size":2338,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2c4d2266d1f960454876091492c6abbe956c06e1"},{"path":"tests/generated_service_smoke/attendance","mode":"040000","type":"tree","sha":"14abdd2b9d69bc9197d514f37328010a8ec13d74","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/14abdd2b9d69bc9197d514f37328010a8ec13d74"},{"path":"tests/generated_service_smoke/attendance/approval.rs","mode":"100644","type":"blob","sha":"ec770be075418174841bb3d873ea1ce889f127cc","size":1459,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ec770be075418174841bb3d873ea1ce889f127cc"},{"path":"tests/generated_service_smoke/attendance/archive_rule.rs","mode":"100644","type":"blob","sha":"cec0f84cbc79a494019f3112327dcc2a33c1132c","size":1101,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cec0f84cbc79a494019f3112327dcc2a33c1132c"},{"path":"tests/generated_service_smoke/attendance/file.rs","mode":"100644","type":"blob","sha":"59cd3e999acb1be605d4fb15ea04d8ae7ac44b9a","size":859,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/59cd3e999acb1be605d4fb15ea04d8ae7ac44b9a"},{"path":"tests/generated_service_smoke/attendance/group.rs","mode":"100644","type":"blob","sha":"2bf91f309dd55bbdbe8fadc1a5ead92d0ce07541","size":4672,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2bf91f309dd55bbdbe8fadc1a5ead92d0ce07541"},{"path":"tests/generated_service_smoke/attendance/leave.rs","mode":"100644","type":"blob","sha":"bf7805f8af5db6e736be91e4732da9199f11f8af","size":2645,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bf7805f8af5db6e736be91e4732da9199f11f8af"},{"path":"tests/generated_service_smoke/attendance/record.rs","mode":"100644","type":"blob","sha":"2c87aba3066e0d50d1efb29b87065a1f43ebbbc1","size":1284,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2c87aba3066e0d50d1efb29b87065a1f43ebbbc1"},{"path":"tests/generated_service_smoke/attendance/shift.rs","mode":"100644","type":"blob","sha":"7905f33c9f74685a560761799d55c18c55750146","size":1094,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7905f33c9f74685a560761799d55c18c55750146"},{"path":"tests/generated_service_smoke/attendance/user_setting.rs","mode":"100644","type":"blob","sha":"5bc088b9b5c3d3ca9c2ef89e9e83af743569dbee","size":1219,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5bc088b9b5c3d3ca9c2ef89e9e83af743569dbee"},{"path":"tests/generated_service_smoke/auth.rs","mode":"100644","type":"blob","sha":"43b687b005bcee51922a0130de7b602e016824db","size":9763,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/43b687b005bcee51922a0130de7b602e016824db"},{"path":"tests/generated_service_smoke/baike","mode":"040000","type":"tree","sha":"e4216771b72fd8d237cec6a5da72a8ff46f85241","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e4216771b72fd8d237cec6a5da72a8ff46f85241"},{"path":"tests/generated_service_smoke/baike/classification.rs","mode":"100644","type":"blob","sha":"038f6420d1f2a117acf973b25bf91cf5dd7996f1","size":3026,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/038f6420d1f2a117acf973b25bf91cf5dd7996f1"},{"path":"tests/generated_service_smoke/baike/entity.rs","mode":"100644","type":"blob","sha":"bd8aa4b77ec82bbc7730a74081b362d6c18a6052","size":5558,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bd8aa4b77ec82bbc7730a74081b362d6c18a6052"},{"path":"tests/generated_service_smoke/baike/file.rs","mode":"100644","type":"blob","sha":"4cf805259831842a46008fdab04b7d29b10f1897","size":836,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4cf805259831842a46008fdab04b7d29b10f1897"},{"path":"tests/generated_service_smoke/base.rs","mode":"100644","type":"blob","sha":"4d42aed4c9b5ffb82fbe088014b1d20720f58915","size":29903,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4d42aed4c9b5ffb82fbe088014b1d20720f58915"},{"path":"tests/generated_service_smoke/base_forms.rs","mode":"100644","type":"blob","sha":"449b3abc510f63675c988580fce2722ffea6d9ff","size":7107,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/449b3abc510f63675c988580fce2722ffea6d9ff"},{"path":"tests/generated_service_smoke/bitable","mode":"040000","type":"tree","sha":"79a6aec2cd955c43da9cc6d8d0cf7e47832046ea","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/79a6aec2cd955c43da9cc6d8d0cf7e47832046ea"},{"path":"tests/generated_service_smoke/bitable/dashboard.rs","mode":"100644","type":"blob","sha":"8fc98778572d41581e5133a06c169ee2157d50a7","size":1038,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8fc98778572d41581e5133a06c169ee2157d50a7"},{"path":"tests/generated_service_smoke/bitable/field.rs","mode":"100644","type":"blob","sha":"128aae6dd66a2a1cc09b2a3302ad746e0d06bc19","size":2435,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/128aae6dd66a2a1cc09b2a3302ad746e0d06bc19"},{"path":"tests/generated_service_smoke/bitable/form.rs","mode":"100644","type":"blob","sha":"4eea374af7bce286c0dcd34540698d778c624d7b","size":1100,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4eea374af7bce286c0dcd34540698d778c624d7b"},{"path":"tests/generated_service_smoke/bitable/record.rs","mode":"100644","type":"blob","sha":"4037827a55da8d5e241f356fc6fe3fe4e89097ef","size":4195,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4037827a55da8d5e241f356fc6fe3fe4e89097ef"},{"path":"tests/generated_service_smoke/bitable/role.rs","mode":"100644","type":"blob","sha":"e3ee0badaba0606026e400023ab073ee4a213ffd","size":2920,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e3ee0badaba0606026e400023ab073ee4a213ffd"},{"path":"tests/generated_service_smoke/bitable/table.rs","mode":"100644","type":"blob","sha":"7bf4cd1d0415ffc4a08fb22e56b1c4f4ec4a0f0d","size":1005,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7bf4cd1d0415ffc4a08fb22e56b1c4f4ec4a0f0d"},{"path":"tests/generated_service_smoke/bitable/view.rs","mode":"100644","type":"blob","sha":"83dc4081cf3738f6ad6efb0a4ec4af9fc9a66c04","size":2071,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/83dc4081cf3738f6ad6efb0a4ec4af9fc9a66c04"},{"path":"tests/generated_service_smoke/bitable/workflow.rs","mode":"100644","type":"blob","sha":"50d0075d3b7408c5c2ac054b769ff1412d63e9e1","size":1870,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/50d0075d3b7408c5c2ac054b769ff1412d63e9e1"},{"path":"tests/generated_service_smoke/block.rs","mode":"100644","type":"blob","sha":"95f865107a8ccdd7f23584058bd50e0c3abdc145","size":3476,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/95f865107a8ccdd7f23584058bd50e0c3abdc145"},{"path":"tests/generated_service_smoke/board.rs","mode":"100644","type":"blob","sha":"a67532dd1e7f6ccabb63bf5e1ea26ab4bb20b2b2","size":7264,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a67532dd1e7f6ccabb63bf5e1ea26ab4bb20b2b2"},{"path":"tests/generated_service_smoke/calendar","mode":"040000","type":"tree","sha":"96ea730f2107862ddabf3a399e42781f5f870b42","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/96ea730f2107862ddabf3a399e42781f5f870b42"},{"path":"tests/generated_service_smoke/calendar/acl.rs","mode":"100644","type":"blob","sha":"d1e20a8d37e43e174c6de5ccac0bfe195342cbd5","size":996,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d1e20a8d37e43e174c6de5ccac0bfe195342cbd5"},{"path":"tests/generated_service_smoke/calendar/attendee.rs","mode":"100644","type":"blob","sha":"45329828cd78bd8e629138f5831470c3f128c6e8","size":1063,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/45329828cd78bd8e629138f5831470c3f128c6e8"},{"path":"tests/generated_service_smoke/calendar/calendar.rs","mode":"100644","type":"blob","sha":"d99048e595b5fcb77b2f1fc062374991f20749b2","size":5037,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d99048e595b5fcb77b2f1fc062374991f20749b2"},{"path":"tests/generated_service_smoke/calendar/chat_member.rs","mode":"100644","type":"blob","sha":"e99cb101464a4e652eb19204ced26127a7a31f0f","size":1117,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e99cb101464a4e652eb19204ced26127a7a31f0f"},{"path":"tests/generated_service_smoke/calendar/event.rs","mode":"100644","type":"blob","sha":"51fadc90b8176a3e16456d60ad0a13e8dbf47d6e","size":6743,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/51fadc90b8176a3e16456d60ad0a13e8dbf47d6e"},{"path":"tests/generated_service_smoke/calendar/freebusy.rs","mode":"100644","type":"blob","sha":"4cd4681d14766c68e82ed78e30a5e3b6ccc1939a","size":2505,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4cd4681d14766c68e82ed78e30a5e3b6ccc1939a"},{"path":"tests/generated_service_smoke/calendar/timezone.rs","mode":"100644","type":"blob","sha":"1d09fc79e8c2a7b8d73e49c5fd1684d274e8bfa5","size":913,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1d09fc79e8c2a7b8d73e49c5fd1684d274e8bfa5"},{"path":"tests/generated_service_smoke/cardkit.rs","mode":"100644","type":"blob","sha":"aadc6e2d903b4a4aec57ab060e5ecd1396e073d1","size":6526,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/aadc6e2d903b4a4aec57ab060e5ecd1396e073d1"},{"path":"tests/generated_service_smoke/contact","mode":"040000","type":"tree","sha":"468448090a800c59c4b75be392e1fd6fd8160a00","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/468448090a800c59c4b75be392e1fd6fd8160a00"},{"path":"tests/generated_service_smoke/contact/department.rs","mode":"100644","type":"blob","sha":"a087f77d36be3330a6231e1706b24893b59693bd","size":8080,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a087f77d36be3330a6231e1706b24893b59693bd"},{"path":"tests/generated_service_smoke/contact/employee_type.rs","mode":"100644","type":"blob","sha":"bf61f5969521698725ef61566b9f4d300c43671b","size":1077,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bf61f5969521698725ef61566b9f4d300c43671b"},{"path":"tests/generated_service_smoke/contact/functional_role_member.rs","mode":"100644","type":"blob","sha":"859c28ade2943eaa50e2994ef0c41e0e00694b3c","size":6498,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/859c28ade2943eaa50e2994ef0c41e0e00694b3c"},{"path":"tests/generated_service_smoke/contact/group.rs","mode":"100644","type":"blob","sha":"8c1b3fab43c2bf3d0e76226656987235ab969a4d","size":4970,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8c1b3fab43c2bf3d0e76226656987235ab969a4d"},{"path":"tests/generated_service_smoke/contact/job.rs","mode":"100644","type":"blob","sha":"f6b96e15892d0ee18f79f98abd31a706c7c3aabd","size":3499,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f6b96e15892d0ee18f79f98abd31a706c7c3aabd"},{"path":"tests/generated_service_smoke/contact/scope.rs","mode":"100644","type":"blob","sha":"1b1f7b8292a55c85f9147941fdf4c18b795f03bb","size":1182,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1b1f7b8292a55c85f9147941fdf4c18b795f03bb"},{"path":"tests/generated_service_smoke/contact/unit.rs","mode":"100644","type":"blob","sha":"9e9110f80bf0d53db607d64d19e4a974f5a6ba32","size":2446,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9e9110f80bf0d53db607d64d19e4a974f5a6ba32"},{"path":"tests/generated_service_smoke/contact/user.rs","mode":"100644","type":"blob","sha":"38646dfb04f6512efcdc8e74e5f7480932013183","size":7853,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/38646dfb04f6512efcdc8e74e5f7480932013183"},{"path":"tests/generated_service_smoke/contact/workplace.rs","mode":"100644","type":"blob","sha":"f22b32023607803e020ceaaea140064e28e31096","size":2266,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f22b32023607803e020ceaaea140064e28e31096"},{"path":"tests/generated_service_smoke/contact_basic.rs","mode":"100644","type":"blob","sha":"79adc1db6357f8e3830ed7783daabc370c9f1e0f","size":1771,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/79adc1db6357f8e3830ed7783daabc370c9f1e0f"},{"path":"tests/generated_service_smoke/corehr","mode":"040000","type":"tree","sha":"ce9439bde6be6bebb5435988908fbb5d5b00fff9","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/ce9439bde6be6bebb5435988908fbb5d5b00fff9"},{"path":"tests/generated_service_smoke/corehr/job.rs","mode":"100644","type":"blob","sha":"bb18d6700776e691a6614d8c9fc002280a1d0d9b","size":4527,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bb18d6700776e691a6614d8c9fc002280a1d0d9b"},{"path":"tests/generated_service_smoke/corehr/offboarding.rs","mode":"100644","type":"blob","sha":"28523b2a6031014d49e2ebbcbae3d0d705ea73c0","size":1353,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/28523b2a6031014d49e2ebbcbae3d0d705ea73c0"},{"path":"tests/generated_service_smoke/corehr/organization.rs","mode":"100644","type":"blob","sha":"a9382789f54bef41482e5d66c955094519698cad","size":14064,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a9382789f54bef41482e5d66c955094519698cad"},{"path":"tests/generated_service_smoke/corehr/reference.rs","mode":"100644","type":"blob","sha":"d9fe505d61f34773a31f31642bb4f57c7030d699","size":9833,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d9fe505d61f34773a31f31642bb4f57c7030d699"},{"path":"tests/generated_service_smoke/corehr/v2_job.rs","mode":"100644","type":"blob","sha":"c17b3ebb15df73009b9b7b95d38fa914c65d7dac","size":2375,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c17b3ebb15df73009b9b7b95d38fa914c65d7dac"},{"path":"tests/generated_service_smoke/corehr/v2_signature.rs","mode":"100644","type":"blob","sha":"c250f370d6999c4c3bec964c1e6da41d006a732e","size":6481,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c250f370d6999c4c3bec964c1e6da41d006a732e"},{"path":"tests/generated_service_smoke/corehr/v2_workflow.rs","mode":"100644","type":"blob","sha":"507c76e0e1daf910f4f815ba8d293b480083afaf","size":5328,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/507c76e0e1daf910f4f815ba8d293b480083afaf"},{"path":"tests/generated_service_smoke/directory","mode":"040000","type":"tree","sha":"e1a55b5ab93495be831c9beb546be9effa7af81f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e1a55b5ab93495be831c9beb546be9effa7af81f"},{"path":"tests/generated_service_smoke/directory/collaboration_rule.rs","mode":"100644","type":"blob","sha":"8c8210ed7ed7426457c351b938377ff9efb929b3","size":4222,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8c8210ed7ed7426457c351b938377ff9efb929b3"},{"path":"tests/generated_service_smoke/directory/collaboration_tenant.rs","mode":"100644","type":"blob","sha":"08aa4262e92d20335d9d7451213fdd74f8498e9b","size":1843,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/08aa4262e92d20335d9d7451213fdd74f8498e9b"},{"path":"tests/generated_service_smoke/directory/department.rs","mode":"100644","type":"blob","sha":"5d7820dda9b56a15d7d362f8c8fcfdf1c0ace0bd","size":3192,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5d7820dda9b56a15d7d362f8c8fcfdf1c0ace0bd"},{"path":"tests/generated_service_smoke/directory/employee.rs","mode":"100644","type":"blob","sha":"473392b4494cd943829384615217580efc3a91c6","size":6231,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/473392b4494cd943829384615217580efc3a91c6"},{"path":"tests/generated_service_smoke/directory/share_entity.rs","mode":"100644","type":"blob","sha":"58b901b4a989eeadac628aea7f3d6a73fa7923d4","size":2528,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/58b901b4a989eeadac628aea7f3d6a73fa7923d4"},{"path":"tests/generated_service_smoke/directory/user.rs","mode":"100644","type":"blob","sha":"996ad6002cd3d1a658fb82b19f4cab8bf5108c6c","size":1946,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/996ad6002cd3d1a658fb82b19f4cab8bf5108c6c"},{"path":"tests/generated_service_smoke/docs.rs","mode":"100644","type":"blob","sha":"31bc1132cefb96cdf72930cfdfcc33c90138a240","size":1963,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/31bc1132cefb96cdf72930cfdfcc33c90138a240"},{"path":"tests/generated_service_smoke/docs_ai.rs","mode":"100644","type":"blob","sha":"e2c789ea3062b122bd262f01e131862a7473edbf","size":6560,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e2c789ea3062b122bd262f01e131862a7473edbf"},{"path":"tests/generated_service_smoke/docx","mode":"040000","type":"tree","sha":"434036acd740654328271d3a5418b8de90c70f86","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/434036acd740654328271d3a5418b8de90c70f86"},{"path":"tests/generated_service_smoke/docx/announcement.rs","mode":"100644","type":"blob","sha":"817ecc0c82dc4d1e7c47cdbda32e8e55c616c5ec","size":3677,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/817ecc0c82dc4d1e7c47cdbda32e8e55c616c5ec"},{"path":"tests/generated_service_smoke/docx/block.rs","mode":"100644","type":"blob","sha":"248dd6afe5b8ff47594882e4a6ce22213401b7af","size":5082,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/248dd6afe5b8ff47594882e4a6ce22213401b7af"},{"path":"tests/generated_service_smoke/docx/building_block.rs","mode":"100644","type":"blob","sha":"57ac4c5d7964aa029a1831a3bb448c17b1975ec7","size":3411,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/57ac4c5d7964aa029a1831a3bb448c17b1975ec7"},{"path":"tests/generated_service_smoke/docx/document.rs","mode":"100644","type":"blob","sha":"44d963639213073b5224bc10e63020e4a1729bb7","size":3303,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/44d963639213073b5224bc10e63020e4a1729bb7"},{"path":"tests/generated_service_smoke/drive","mode":"040000","type":"tree","sha":"9c878dd2e00076a296baab0a670cac032bb74ad3","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/9c878dd2e00076a296baab0a670cac032bb74ad3"},{"path":"tests/generated_service_smoke/drive/comment.rs","mode":"100644","type":"blob","sha":"e74d8cbb26f97a3eb971f02d05aa434a089c5c73","size":4417,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e74d8cbb26f97a3eb971f02d05aa434a089c5c73"},{"path":"tests/generated_service_smoke/drive/event.rs","mode":"100644","type":"blob","sha":"a952cecdf0779f51091d88669f98e217a1ba3294","size":884,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a952cecdf0779f51091d88669f98e217a1ba3294"},{"path":"tests/generated_service_smoke/drive/export.rs","mode":"100644","type":"blob","sha":"5fd89c7762b785b227c51c1336936cc0dc3e69d6","size":1578,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5fd89c7762b785b227c51c1336936cc0dc3e69d6"},{"path":"tests/generated_service_smoke/drive/file.rs","mode":"100644","type":"blob","sha":"009af687c834ba22c97184830ae3467a0049f7b4","size":8243,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/009af687c834ba22c97184830ae3467a0049f7b4"},{"path":"tests/generated_service_smoke/drive/like.rs","mode":"100644","type":"blob","sha":"76b3061dfc1ac0290747c786681d40cd2ca3bc60","size":2043,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/76b3061dfc1ac0290747c786681d40cd2ca3bc60"},{"path":"tests/generated_service_smoke/drive/media.rs","mode":"100644","type":"blob","sha":"81c567f5f07a567119e4807ea970c3469c822f22","size":5532,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/81c567f5f07a567119e4807ea970c3469c822f22"},{"path":"tests/generated_service_smoke/drive/permission.rs","mode":"100644","type":"blob","sha":"0136627aad84f2ac91a59bad10a7915f36ca3a28","size":4310,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0136627aad84f2ac91a59bad10a7915f36ca3a28"},{"path":"tests/generated_service_smoke/drive_token.rs","mode":"100644","type":"blob","sha":"7c6d7ac77b15453f637e6c89edff58269a6ec8fd","size":2475,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7c6d7ac77b15453f637e6c89edff58269a6ec8fd"},{"path":"tests/generated_service_smoke/ehr.rs","mode":"100644","type":"blob","sha":"f34311762622f07fa992ac1a59e5e09decc13bbe","size":2198,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f34311762622f07fa992ac1a59e5e09decc13bbe"},{"path":"tests/generated_service_smoke/ext.rs","mode":"100644","type":"blob","sha":"cedf2f79e7ebe16712780c656ac60ed5251bb8ea","size":1069,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cedf2f79e7ebe16712780c656ac60ed5251bb8ea"},{"path":"tests/generated_service_smoke/harness.rs","mode":"100644","type":"blob","sha":"b7deeadd49268c5700e194f16ffaf026f31a4f0e","size":455,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b7deeadd49268c5700e194f16ffaf026f31a4f0e"},{"path":"tests/generated_service_smoke/helpdesk","mode":"040000","type":"tree","sha":"ad02c7f1bb43ddd948f7386ea4b33aeb67cc9a22","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/ad02c7f1bb43ddd948f7386ea4b33aeb67cc9a22"},{"path":"tests/generated_service_smoke/helpdesk/agent.rs","mode":"100644","type":"blob","sha":"437f39b7a22e4d51f2492331da7c4f62b3f69b67","size":3115,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/437f39b7a22e4d51f2492331da7c4f62b3f69b67"},{"path":"tests/generated_service_smoke/helpdesk/category.rs","mode":"100644","type":"blob","sha":"c7864400fe53d8872b63bbdb6d95bd179229cfac","size":1625,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c7864400fe53d8872b63bbdb6d95bd179229cfac"},{"path":"tests/generated_service_smoke/helpdesk/download.rs","mode":"100644","type":"blob","sha":"dfe6864b2672d5e356e740c5c1438b0ac93bee53","size":2810,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/dfe6864b2672d5e356e740c5c1438b0ac93bee53"},{"path":"tests/generated_service_smoke/helpdesk/faq.rs","mode":"100644","type":"blob","sha":"f6936c59b00cdafeb22d24ec88b2492c70a824cc","size":3520,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f6936c59b00cdafeb22d24ec88b2492c70a824cc"},{"path":"tests/generated_service_smoke/helpdesk/ticket.rs","mode":"100644","type":"blob","sha":"9e6a90b0ee6428b9f88248ef17d44640d41eb5ab","size":4700,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9e6a90b0ee6428b9f88248ef17d44640d41eb5ab"},{"path":"tests/generated_service_smoke/hire","mode":"040000","type":"tree","sha":"6b7d3c32e5b3706f5e2fa83956f3ade7e0cbc954","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/6b7d3c32e5b3706f5e2fa83956f3ade7e0cbc954"},{"path":"tests/generated_service_smoke/hire/action_response.rs","mode":"100644","type":"blob","sha":"56112d9c2c900a5b7365d191a68cfca907ad4d91","size":1118,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/56112d9c2c900a5b7365d191a68cfca907ad4d91"},{"path":"tests/generated_service_smoke/hire/agency.rs","mode":"100644","type":"blob","sha":"73b960fb54138258a3ba4ff9d78bee533bd057ff","size":3048,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/73b960fb54138258a3ba4ff9d78bee533bd057ff"},{"path":"tests/generated_service_smoke/hire/application.rs","mode":"100644","type":"blob","sha":"ce4fc397b8bc507d8b6f401701048d19d3b667eb","size":11315,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ce4fc397b8bc507d8b6f401701048d19d3b667eb"},{"path":"tests/generated_service_smoke/hire/auxiliary_read.rs","mode":"100644","type":"blob","sha":"06a9f3a96b015923d8d0d3949ec7585953c673f8","size":2492,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/06a9f3a96b015923d8d0d3949ec7585953c673f8"},{"path":"tests/generated_service_smoke/hire/catalog.rs","mode":"100644","type":"blob","sha":"d73e02a4604bcc0f2833ecf6d13ccafd2fbaaba7","size":3213,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d73e02a4604bcc0f2833ecf6d13ccafd2fbaaba7"},{"path":"tests/generated_service_smoke/hire/code_only_response.rs","mode":"100644","type":"blob","sha":"a64d3212b2799d6c451da7cd956e344038659c0f","size":4813,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a64d3212b2799d6c451da7cd956e344038659c0f"},{"path":"tests/generated_service_smoke/hire/eco_code_only_response.rs","mode":"100644","type":"blob","sha":"bd7ac2328be1b876e752500b9173cff2c6f9e3e0","size":6935,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/bd7ac2328be1b876e752500b9173cff2c6f9e3e0"},{"path":"tests/generated_service_smoke/hire/existing_model_write.rs","mode":"100644","type":"blob","sha":"523750243533ae42cf5e173eed3fd9c61a2e76b5","size":3856,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/523750243533ae42cf5e173eed3fd9c61a2e76b5"},{"path":"tests/generated_service_smoke/hire/external_read.rs","mode":"100644","type":"blob","sha":"a686b20a895bb51d86e10bbad7513d6278f1e383","size":4210,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a686b20a895bb51d86e10bbad7513d6278f1e383"},{"path":"tests/generated_service_smoke/hire/external_write.rs","mode":"100644","type":"blob","sha":"470fd98ce5754452df08bafa584922f1d55bb0c0","size":7952,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/470fd98ce5754452df08bafa584922f1d55bb0c0"},{"path":"tests/generated_service_smoke/hire/interview.rs","mode":"100644","type":"blob","sha":"36cdff742cb11983599fc4c3c04c62709cea5dad","size":7280,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/36cdff742cb11983599fc4c3c04c62709cea5dad"},{"path":"tests/generated_service_smoke/hire/job.rs","mode":"100644","type":"blob","sha":"e0a43c184ddb0cd248c76144fff045bcc8de7780","size":5855,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e0a43c184ddb0cd248c76144fff045bcc8de7780"},{"path":"tests/generated_service_smoke/hire/job_requirement.rs","mode":"100644","type":"blob","sha":"8fcd25bb4206f5a8d83d7fc49e3ec33ac701dd01","size":3747,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8fcd25bb4206f5a8d83d7fc49e3ec33ac701dd01"},{"path":"tests/generated_service_smoke/hire/job_response.rs","mode":"100644","type":"blob","sha":"70dabe320f0dacddc4768e2f499c1cd58d59d7d7","size":2004,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/70dabe320f0dacddc4768e2f499c1cd58d59d7d7"},{"path":"tests/generated_service_smoke/hire/job_utility_read.rs","mode":"100644","type":"blob","sha":"6cae1bb22412d535b0b02b3b2ce66ab14e796f59","size":3170,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6cae1bb22412d535b0b02b3b2ce66ab14e796f59"},{"path":"tests/generated_service_smoke/hire/offer.rs","mode":"100644","type":"blob","sha":"af43bfc0b1618dd6a7f86a9566f241c288c4b425","size":3820,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/af43bfc0b1618dd6a7f86a9566f241c288c4b425"},{"path":"tests/generated_service_smoke/hire/read_models.rs","mode":"100644","type":"blob","sha":"797b83eef07c10495aeed16277ce098270adb186","size":12803,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/797b83eef07c10495aeed16277ce098270adb186"},{"path":"tests/generated_service_smoke/hire/reference.rs","mode":"100644","type":"blob","sha":"517bc8f52eced0c5799ae09f13d17f54742775ee","size":5008,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/517bc8f52eced0c5799ae09f13d17f54742775ee"},{"path":"tests/generated_service_smoke/hire/referral_talent.rs","mode":"100644","type":"blob","sha":"e3a7ab92b5117dbab5609a70880ea9213cfccf72","size":2982,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e3a7ab92b5117dbab5609a70880ea9213cfccf72"},{"path":"tests/generated_service_smoke/hire/talent.rs","mode":"100644","type":"blob","sha":"033b07c89a4261d426cbcb8a605e33eba37a3dd2","size":2976,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/033b07c89a4261d426cbcb8a605e33eba37a3dd2"},{"path":"tests/generated_service_smoke/hire/talent_exam_write.rs","mode":"100644","type":"blob","sha":"009cbac9bc28c15bdd9cc6e03b6bb0b9810963ec","size":3171,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/009cbac9bc28c15bdd9cc6e03b6bb0b9810963ec"},{"path":"tests/generated_service_smoke/hire/task.rs","mode":"100644","type":"blob","sha":"b87ab7d83e775ed61172c87f4c579ea2884f3b25","size":3684,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b87ab7d83e775ed61172c87f4c579ea2884f3b25"},{"path":"tests/generated_service_smoke/hire/utility_test.rs","mode":"100644","type":"blob","sha":"6530c79e0695306ae052b36bde7f8bdb97f96478","size":3460,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6530c79e0695306ae052b36bde7f8bdb97f96478"},{"path":"tests/generated_service_smoke/hire/v2.rs","mode":"100644","type":"blob","sha":"40285e6b98efd1a67cb634582e3c9fe99cd9abce","size":4350,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/40285e6b98efd1a67cb634582e3c9fe99cd9abce"},{"path":"tests/generated_service_smoke/hire/website.rs","mode":"100644","type":"blob","sha":"146170f582f884f145fe28c812af4888e1bdedd2","size":3742,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/146170f582f884f145fe28c812af4888e1bdedd2"},{"path":"tests/generated_service_smoke/hire/write_utility.rs","mode":"100644","type":"blob","sha":"5cb68e60b5614cfe494d321296fa34ba2254d0ad","size":3243,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5cb68e60b5614cfe494d321296fa34ba2254d0ad"},{"path":"tests/generated_service_smoke/human_authentication.rs","mode":"100644","type":"blob","sha":"2936f000d8b3a8538ec2a99ce79854416ae9053e","size":1227,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2936f000d8b3a8538ec2a99ce79854416ae9053e"},{"path":"tests/generated_service_smoke/im","mode":"040000","type":"tree","sha":"ea20f74fa3ec2b25cdbdecfd09f94fbdcba53cc5","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/ea20f74fa3ec2b25cdbdecfd09f94fbdcba53cc5"},{"path":"tests/generated_service_smoke/im/announcement.rs","mode":"100644","type":"blob","sha":"8b22bcfa3f200ba785af8077dbe60d9b2d35d393","size":874,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8b22bcfa3f200ba785af8077dbe60d9b2d35d393"},{"path":"tests/generated_service_smoke/im/chat.rs","mode":"100644","type":"blob","sha":"47d4e76dc698976d251b762b4b0b3b2fdab58eef","size":6013,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/47d4e76dc698976d251b762b4b0b3b2fdab58eef"},{"path":"tests/generated_service_smoke/im/download.rs","mode":"100644","type":"blob","sha":"5f5dff93c3a8415bf89956ceac2068c9af7d63aa","size":9671,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5f5dff93c3a8415bf89956ceac2068c9af7d63aa"},{"path":"tests/generated_service_smoke/im/message.rs","mode":"100644","type":"blob","sha":"d2f5010763a21c0e13681fffd107cb13064e5482","size":5584,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d2f5010763a21c0e13681fffd107cb13064e5482"},{"path":"tests/generated_service_smoke/im/moderation.rs","mode":"100644","type":"blob","sha":"e15a9f11c64e3d0724b1904fda7dccdd8a5d84e7","size":1058,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e15a9f11c64e3d0724b1904fda7dccdd8a5d84e7"},{"path":"tests/generated_service_smoke/im/pin.rs","mode":"100644","type":"blob","sha":"b28d21c999c5c784e6667510161b863c035f4750","size":1112,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b28d21c999c5c784e6667510161b863c035f4750"},{"path":"tests/generated_service_smoke/im/reaction.rs","mode":"100644","type":"blob","sha":"a33f6d277913508be471e432ce2c054420013457","size":1140,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a33f6d277913508be471e432ce2c054420013457"},{"path":"tests/generated_service_smoke/im_v2.rs","mode":"100644","type":"blob","sha":"ec74bb48415d5e061c3e680aa5a1b37ed24f3b07","size":7951,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ec74bb48415d5e061c3e680aa5a1b37ed24f3b07"},{"path":"tests/generated_service_smoke/lingo","mode":"040000","type":"tree","sha":"e53b40cff8b0cbc62c7732d80b5be5c8f742877f","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/e53b40cff8b0cbc62c7732d80b5be5c8f742877f"},{"path":"tests/generated_service_smoke/lingo/classification.rs","mode":"100644","type":"blob","sha":"696bf05365ab42456580a472e4066f335df85f18","size":986,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/696bf05365ab42456580a472e4066f335df85f18"},{"path":"tests/generated_service_smoke/lingo/download.rs","mode":"100644","type":"blob","sha":"123e8d1631d1fd8e872758dcb5a51620db1f3831","size":836,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/123e8d1631d1fd8e872758dcb5a51620db1f3831"},{"path":"tests/generated_service_smoke/lingo/draft_file.rs","mode":"100644","type":"blob","sha":"9c03dc31e977cb86d259239fa1167d6dd3bc8483","size":2709,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9c03dc31e977cb86d259239fa1167d6dd3bc8483"},{"path":"tests/generated_service_smoke/lingo/entity.rs","mode":"100644","type":"blob","sha":"24fb7e1703ed0ca16fc919a245a08370046fc24b","size":7584,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/24fb7e1703ed0ca16fc919a245a08370046fc24b"},{"path":"tests/generated_service_smoke/mail","mode":"040000","type":"tree","sha":"0aa62bb279ffde5dcaee73d73e0486426ca0f9b4","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/0aa62bb279ffde5dcaee73d73e0486426ca0f9b4"},{"path":"tests/generated_service_smoke/mail/mailgroup.rs","mode":"100644","type":"blob","sha":"686c1aebced93bf8fec4f7031337362f95913722","size":3569,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/686c1aebced93bf8fec4f7031337362f95913722"},{"path":"tests/generated_service_smoke/mail/mailgroup_member.rs","mode":"100644","type":"blob","sha":"2c8b16e489d2178773465480de7e6317326ab139","size":4014,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2c8b16e489d2178773465480de7e6317326ab139"},{"path":"tests/generated_service_smoke/mail/public_mailbox.rs","mode":"100644","type":"blob","sha":"6507553fae0c527e6bddc8e74e07e8aef6b415b5","size":3539,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6507553fae0c527e6bddc8e74e07e8aef6b415b5"},{"path":"tests/generated_service_smoke/mail/public_mailbox_member.rs","mode":"100644","type":"blob","sha":"15483af82893c32d07398dc4808881c344cc0bdc","size":4085,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/15483af82893c32d07398dc4808881c344cc0bdc"},{"path":"tests/generated_service_smoke/mail/user_mailbox_thread.rs","mode":"100644","type":"blob","sha":"5c1368197e66dc0d7bb1f15b9a1f26fb59c07644","size":4378,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5c1368197e66dc0d7bb1f15b9a1f26fb59c07644"},{"path":"tests/generated_service_smoke/mdm.rs","mode":"100644","type":"blob","sha":"74ed3492b8bec7e7b0e5516298f5835f488a75ee","size":8017,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/74ed3492b8bec7e7b0e5516298f5835f488a75ee"},{"path":"tests/generated_service_smoke/minutes.rs","mode":"100644","type":"blob","sha":"3bce30727e40da573c34a13e6ab4718adddf0a16","size":5460,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3bce30727e40da573c34a13e6ab4718adddf0a16"},{"path":"tests/generated_service_smoke/okr.rs","mode":"100644","type":"blob","sha":"c08e5e7459f74bf43140fa52fae4f684eea11693","size":8652,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c08e5e7459f74bf43140fa52fae4f684eea11693"},{"path":"tests/generated_service_smoke/passport.rs","mode":"100644","type":"blob","sha":"0c7cb23011a23be92b78fc12a2c944cd3831692b","size":1664,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0c7cb23011a23be92b78fc12a2c944cd3831692b"},{"path":"tests/generated_service_smoke/payroll.rs","mode":"100644","type":"blob","sha":"0fb0c0dc896aeb31d2d32e0946ffe4729e697d71","size":7833,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0fb0c0dc896aeb31d2d32e0946ffe4729e697d71"},{"path":"tests/generated_service_smoke/performance","mode":"040000","type":"tree","sha":"37cac918810a4deb43f7f7e6e77621088784b9f1","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/37cac918810a4deb43f7f7e6e77621088784b9f1"},{"path":"tests/generated_service_smoke/performance/activity.rs","mode":"100644","type":"blob","sha":"db8494b37b8241d4a852e3e8c9cebeeb1fe22ea2","size":1831,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/db8494b37b8241d4a852e3e8c9cebeeb1fe22ea2"},{"path":"tests/generated_service_smoke/performance/additional_information.rs","mode":"100644","type":"blob","sha":"cebb0ea2e71e25efb50fb1e301edbe36dce6b975","size":2626,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cebb0ea2e71e25efb50fb1e301edbe36dce6b975"},{"path":"tests/generated_service_smoke/performance/metric.rs","mode":"100644","type":"blob","sha":"d935c3203b2c8ba0626d46f4e95ead96aa1abe2c","size":2651,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d935c3203b2c8ba0626d46f4e95ead96aa1abe2c"},{"path":"tests/generated_service_smoke/performance/metric_tag.rs","mode":"100644","type":"blob","sha":"50a16952b0f6c4d329f7bad8ec1f8e303cf11cad","size":1981,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/50a16952b0f6c4d329f7bad8ec1f8e303cf11cad"},{"path":"tests/generated_service_smoke/performance/review_user.rs","mode":"100644","type":"blob","sha":"ea79cb9e58fba914057be256a9abe44ff4a7c422","size":2355,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ea79cb9e58fba914057be256a9abe44ff4a7c422"},{"path":"tests/generated_service_smoke/personal_settings.rs","mode":"100644","type":"blob","sha":"ab62728f96c6e00185e1b7b19c39ecea2d60dcb0","size":4136,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ab62728f96c6e00185e1b7b19c39ecea2d60dcb0"},{"path":"tests/generated_service_smoke/prelude.rs","mode":"100644","type":"blob","sha":"5c0b62b0d6d4e68ca008c65c6b055faa431c866a","size":32987,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5c0b62b0d6d4e68ca008c65c6b055faa431c866a"},{"path":"tests/generated_service_smoke/recognition_and_speech.rs","mode":"100644","type":"blob","sha":"005c26dac67f767956670741f99143457a9c6977","size":2975,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/005c26dac67f767956670741f99143457a9c6977"},{"path":"tests/generated_service_smoke/report.rs","mode":"100644","type":"blob","sha":"5c93b2d25000097b77fd4448e697619c9fd77d8a","size":5477,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5c93b2d25000097b77fd4448e697619c9fd77d8a"},{"path":"tests/generated_service_smoke/search","mode":"040000","type":"tree","sha":"919615d3367ac9701a0744ba0080b6a0d5db45e5","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/919615d3367ac9701a0744ba0080b6a0d5db45e5"},{"path":"tests/generated_service_smoke/search/data_record.rs","mode":"100644","type":"blob","sha":"2d6f233c7066db157dea13156ce72cf010320247","size":2210,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2d6f233c7066db157dea13156ce72cf010320247"},{"path":"tests/generated_service_smoke/search/data_source.rs","mode":"100644","type":"blob","sha":"900f30f2fb4023230b99d33f0924a763de8c486e","size":3195,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/900f30f2fb4023230b99d33f0924a763de8c486e"},{"path":"tests/generated_service_smoke/search/message.rs","mode":"100644","type":"blob","sha":"e6f7b56fa79d23a2acd8fb1a61b8cddc47fa1aac","size":2791,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e6f7b56fa79d23a2acd8fb1a61b8cddc47fa1aac"},{"path":"tests/generated_service_smoke/search/schema.rs","mode":"100644","type":"blob","sha":"b49a39c7d7ccea925ec50c8b76decadc50efbb96","size":5083,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b49a39c7d7ccea925ec50c8b76decadc50efbb96"},{"path":"tests/generated_service_smoke/security_and_compliance.rs","mode":"100644","type":"blob","sha":"1073b45b09b1c12be48f758eedba64d4c9a3f0cf","size":8537,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1073b45b09b1c12be48f758eedba64d4c9a3f0cf"},{"path":"tests/generated_service_smoke/sheets.rs","mode":"100644","type":"blob","sha":"b65a2f839db9ab5b8690a8e1b2c9f992b1ebb66f","size":8131,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b65a2f839db9ab5b8690a8e1b2c9f992b1ebb66f"},{"path":"tests/generated_service_smoke/slides_ai.rs","mode":"100644","type":"blob","sha":"09fdc6a6539d90caf1e70f5967085f6cef592a00","size":10908,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/09fdc6a6539d90caf1e70f5967085f6cef592a00"},{"path":"tests/generated_service_smoke/spark","mode":"040000","type":"tree","sha":"86f7e2e2a530bb79060f6cef484fc090b3322e90","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/86f7e2e2a530bb79060f6cef484fc090b3322e90"},{"path":"tests/generated_service_smoke/spark/app.rs","mode":"100644","type":"blob","sha":"ae834546594cd352894ad10c4b13928e93c94461","size":3727,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ae834546594cd352894ad10c4b13928e93c94461"},{"path":"tests/generated_service_smoke/spark/db_sync.rs","mode":"100644","type":"blob","sha":"f97aae533d669f51cf26e291155b7342c458e692","size":4722,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f97aae533d669f51cf26e291155b7342c458e692"},{"path":"tests/generated_service_smoke/spark/metadata.rs","mode":"100644","type":"blob","sha":"38c068bd897066711a86501366f40dc72c27f901","size":1528,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/38c068bd897066711a86501366f40dc72c27f901"},{"path":"tests/generated_service_smoke/spark/record.rs","mode":"100644","type":"blob","sha":"58640c8a3ce7c37b5bec98e031e5bfb74d5685bd","size":2658,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/58640c8a3ce7c37b5bec98e031e5bfb74d5685bd"},{"path":"tests/generated_service_smoke/spark/storage.rs","mode":"100644","type":"blob","sha":"5c9166cc1b9a2eee927a32596fb528ee1fa22e3d","size":2964,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5c9166cc1b9a2eee927a32596fb528ee1fa22e3d"},{"path":"tests/generated_service_smoke/spark/view.rs","mode":"100644","type":"blob","sha":"1c3f98478f09c91579bed858a43d758af7eca6bb","size":1518,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1c3f98478f09c91579bed858a43d758af7eca6bb"},{"path":"tests/generated_service_smoke/task","mode":"040000","type":"tree","sha":"8e85dd3bace09f4c6b6b5cc53173c59cb9720d4b","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/8e85dd3bace09f4c6b6b5cc53173c59cb9720d4b"},{"path":"tests/generated_service_smoke/task/comment.rs","mode":"100644","type":"blob","sha":"9dcb92881ec51ab37d9b50a3e9be6f6fa3d327b9","size":2993,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9dcb92881ec51ab37d9b50a3e9be6f6fa3d327b9"},{"path":"tests/generated_service_smoke/task/core.rs","mode":"100644","type":"blob","sha":"fcbc6d3dfecde36f77e0119b8948af457247210c","size":3728,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fcbc6d3dfecde36f77e0119b8948af457247210c"},{"path":"tests/generated_service_smoke/task/member.rs","mode":"100644","type":"blob","sha":"97ae48e110330c68a6d05d8c73364c623580b744","size":5276,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/97ae48e110330c68a6d05d8c73364c623580b744"},{"path":"tests/generated_service_smoke/task/reminder.rs","mode":"100644","type":"blob","sha":"d6122a5eef2a7cfd58e6d3bf81f9371013179caf","size":3100,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d6122a5eef2a7cfd58e6d3bf81f9371013179caf"},{"path":"tests/generated_service_smoke/task/task.rs","mode":"100644","type":"blob","sha":"e1228daa5b00e15aeaaf94af5b80ba1b7f12a6e3","size":3920,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e1228daa5b00e15aeaaf94af5b80ba1b7f12a6e3"},{"path":"tests/generated_service_smoke/task_v2","mode":"040000","type":"tree","sha":"63fe59d766b108e3603ecd2fcd308ff60d2c3070","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/63fe59d766b108e3603ecd2fcd308ff60d2c3070"},{"path":"tests/generated_service_smoke/task_v2/attachment.rs","mode":"100644","type":"blob","sha":"85fcbe0d15c046c7b8ecaa9e7890e578fd5449be","size":4268,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/85fcbe0d15c046c7b8ecaa9e7890e578fd5449be"},{"path":"tests/generated_service_smoke/task_v2/comment.rs","mode":"100644","type":"blob","sha":"940a77f697fb999553f874d9af94038fe1166516","size":4509,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/940a77f697fb999553f874d9af94038fe1166516"},{"path":"tests/generated_service_smoke/task_v2/custom_field.rs","mode":"100644","type":"blob","sha":"a221c6dcb6577f20fb79b5fae20ea8a6e3a824c3","size":7426,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a221c6dcb6577f20fb79b5fae20ea8a6e3a824c3"},{"path":"tests/generated_service_smoke/task_v2/section.rs","mode":"100644","type":"blob","sha":"e41a337262f164cdee8c783bf3c3b3497b716e0c","size":3363,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e41a337262f164cdee8c783bf3c3b3497b716e0c"},{"path":"tests/generated_service_smoke/task_v2/task.rs","mode":"100644","type":"blob","sha":"628c0e369a92ae21242d4301901482f81862fc98","size":9911,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/628c0e369a92ae21242d4301901482f81862fc98"},{"path":"tests/generated_service_smoke/task_v2/tasklist.rs","mode":"100644","type":"blob","sha":"770c24ed08f4e8e2461562d1cf67af6627a0311a","size":7299,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/770c24ed08f4e8e2461562d1cf67af6627a0311a"},{"path":"tests/generated_service_smoke/tenant.rs","mode":"100644","type":"blob","sha":"78e00fe7b53451dadc788437245b1cc491969f18","size":1791,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/78e00fe7b53451dadc788437245b1cc491969f18"},{"path":"tests/generated_service_smoke/translation.rs","mode":"100644","type":"blob","sha":"c7664fedf81a53d0ace6ecae8e4d21e73e91066b","size":2454,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c7664fedf81a53d0ace6ecae8e4d21e73e91066b"},{"path":"tests/generated_service_smoke/trust_party.rs","mode":"100644","type":"blob","sha":"b94b0e6a9fb2413fd67eef60051b7d2fe4725243","size":4529,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b94b0e6a9fb2413fd67eef60051b7d2fe4725243"},{"path":"tests/generated_service_smoke/unified_kms.rs","mode":"100644","type":"blob","sha":"d6ca8dae694161b9b85d033b125c88d9f6a3f88b","size":3159,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d6ca8dae694161b9b85d033b125c88d9f6a3f88b"},{"path":"tests/generated_service_smoke/vc","mode":"040000","type":"tree","sha":"78599b6d60cef3566827e1c8d0a3870cc619cc2b","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/78599b6d60cef3566827e1c8d0a3870cc619cc2b"},{"path":"tests/generated_service_smoke/vc/meeting.rs","mode":"100644","type":"blob","sha":"53d47c154e74fa628ac9f6f0910f55fa0b2504ee","size":3585,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/53d47c154e74fa628ac9f6f0910f55fa0b2504ee"},{"path":"tests/generated_service_smoke/vc/reporting.rs","mode":"100644","type":"blob","sha":"c52a4533fd50b90506abb723108b49c513e01629","size":7822,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c52a4533fd50b90506abb723108b49c513e01629"},{"path":"tests/generated_service_smoke/vc/reserve.rs","mode":"100644","type":"blob","sha":"fd316148d62adab27a4741381eacaaed83ce7311","size":6029,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fd316148d62adab27a4741381eacaaed83ce7311"},{"path":"tests/generated_service_smoke/vc/room.rs","mode":"100644","type":"blob","sha":"032360ca96559b5e336c1851d599530f2c0a43ca","size":9401,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/032360ca96559b5e336c1851d599530f2c0a43ca"},{"path":"tests/generated_service_smoke/vc_bot.rs","mode":"100644","type":"blob","sha":"95783e772d80519d9bffc917b653295932675317","size":5747,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/95783e772d80519d9bffc917b653295932675317"},{"path":"tests/generated_service_smoke/verification.rs","mode":"100644","type":"blob","sha":"733fe434e7772b810fbdde8b49555e2885ca52c9","size":1950,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/733fe434e7772b810fbdde8b49555e2885ca52c9"},{"path":"tests/generated_service_smoke/wiki.rs","mode":"100644","type":"blob","sha":"ac60aa5d2dfcffc8e0f51494bb83b47c3d0acf16","size":3399,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ac60aa5d2dfcffc8e0f51494bb83b47c3d0acf16"},{"path":"tests/generated_service_smoke/workplace.rs","mode":"100644","type":"blob","sha":"4be7495a9b8a11bd73536b8a0a45fd4578b5aeaa","size":2972,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4be7495a9b8a11bd73536b8a0a45fd4578b5aeaa"},{"path":"tests/helpdesk_event.rs","mode":"100644","type":"blob","sha":"058ae502f9f6590f01cc407092c066a69f60b665","size":10804,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/058ae502f9f6590f01cc407092c066a69f60b665"},{"path":"tests/hire_action_response_wrappers.rs","mode":"100644","type":"blob","sha":"cf07f6561a60974c6aeb50eedc13ffc7495b6c9d","size":2777,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/cf07f6561a60974c6aeb50eedc13ffc7495b6c9d"},{"path":"tests/hire_agency_read.rs","mode":"100644","type":"blob","sha":"44f88a6b058f619393e0c63c03e3353a471d8e38","size":6429,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/44f88a6b058f619393e0c63c03e3353a471d8e38"},{"path":"tests/hire_auxiliary_read.rs","mode":"100644","type":"blob","sha":"a1cb4ac311ef35fc52889f3c48ae947d0dac278e","size":5189,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a1cb4ac311ef35fc52889f3c48ae947d0dac278e"},{"path":"tests/hire_catalog.rs","mode":"100644","type":"blob","sha":"2c73abbd3806158d7a5f6771773c1a34cacda24f","size":6262,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2c73abbd3806158d7a5f6771773c1a34cacda24f"},{"path":"tests/hire_code_only_responses.rs","mode":"100644","type":"blob","sha":"26f393b6e6e740d2ac7b804a63499deae2e1abe9","size":5048,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/26f393b6e6e740d2ac7b804a63499deae2e1abe9"},{"path":"tests/hire_code_only_sweep.rs","mode":"100644","type":"blob","sha":"70cd23c111a23735c566de39f7c7250970264eea","size":9015,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/70cd23c111a23735c566de39f7c7250970264eea"},{"path":"tests/hire_event.rs","mode":"100644","type":"blob","sha":"b7da90656dd54b0712b4a5c0a68d7a3199c1080e","size":6483,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b7da90656dd54b0712b4a5c0a68d7a3199c1080e"},{"path":"tests/hire_existing_model_write_responses.rs","mode":"100644","type":"blob","sha":"ca872c6527b3af6aae158cfa693d90d8004c254a","size":7252,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/ca872c6527b3af6aae158cfa693d90d8004c254a"},{"path":"tests/hire_external_read.rs","mode":"100644","type":"blob","sha":"0f6996fc75d267c99964b7b1a2270c454d303b3a","size":11003,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0f6996fc75d267c99964b7b1a2270c454d303b3a"},{"path":"tests/hire_external_write_responses.rs","mode":"100644","type":"blob","sha":"3c357a8e9fb5d2e7d3a0d86dedaa15513439bd0b","size":11723,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3c357a8e9fb5d2e7d3a0d86dedaa15513439bd0b"},{"path":"tests/hire_i18n_models.rs","mode":"100644","type":"blob","sha":"1c204bb22fa40c5e725909947c30995e5d3c7748","size":1157,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1c204bb22fa40c5e725909947c30995e5d3c7748"},{"path":"tests/hire_interview_support.rs","mode":"100644","type":"blob","sha":"abe6195ff2118f4f5fb4d93e98880bf4b377cc81","size":10983,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/abe6195ff2118f4f5fb4d93e98880bf4b377cc81"},{"path":"tests/hire_job_utility_read.rs","mode":"100644","type":"blob","sha":"764c0b14cfa4e25d1fa0b2fd3321d94145527ce7","size":5306,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/764c0b14cfa4e25d1fa0b2fd3321d94145527ce7"},{"path":"tests/hire_read_models.rs","mode":"100644","type":"blob","sha":"1748643e5e97b613677f9a1110857896bcc80eb4","size":43209,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1748643e5e97b613677f9a1110857896bcc80eb4"},{"path":"tests/hire_reference.rs","mode":"100644","type":"blob","sha":"87d60e28a3c232fee817d41bf8e9c6ed3e990cc2","size":10530,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/87d60e28a3c232fee817d41bf8e9c6ed3e990cc2"},{"path":"tests/hire_referral_talent.rs","mode":"100644","type":"blob","sha":"e497c7b950c9c53ebf733e91f3e73712c80e2246","size":8087,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e497c7b950c9c53ebf733e91f3e73712c80e2246"},{"path":"tests/hire_replayable_request_body.rs","mode":"100644","type":"blob","sha":"23f57ab35f7331bb710001155b58d24bfa312238","size":617,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/23f57ab35f7331bb710001155b58d24bfa312238"},{"path":"tests/hire_talent_exam_write_responses.rs","mode":"100644","type":"blob","sha":"6e33315b10d4037b06595aa433c2aa2f62a85375","size":5845,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6e33315b10d4037b06595aa433c2aa2f62a85375"},{"path":"tests/hire_talent_profile.rs","mode":"100644","type":"blob","sha":"27730cdd99c3a27e3f5c7ea373479b882211b5b2","size":2377,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/27730cdd99c3a27e3f5c7ea373479b882211b5b2"},{"path":"tests/hire_task.rs","mode":"100644","type":"blob","sha":"4bb93fb6c1328ac095a23dccec26a02c0f7f4b88","size":6206,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4bb93fb6c1328ac095a23dccec26a02c0f7f4b88"},{"path":"tests/hire_utility_test_models.rs","mode":"100644","type":"blob","sha":"62002df6d8efd084f7c279be94ccdcf05b8c46a4","size":9759,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/62002df6d8efd084f7c279be94ccdcf05b8c46a4"},{"path":"tests/hire_website_posting.rs","mode":"100644","type":"blob","sha":"c10ecee2b674d0b550537cbe597a7b699322d5af","size":12163,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c10ecee2b674d0b550537cbe597a7b699322d5af"},{"path":"tests/hire_write_utility_responses.rs","mode":"100644","type":"blob","sha":"7219bd7f7692d2dca7c2cab6a1ea3a3c16a07c4d","size":5372,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/7219bd7f7692d2dca7c2cab6a1ea3a3c16a07c4d"},{"path":"tests/im_event.rs","mode":"100644","type":"blob","sha":"def83f4ee7b33da49cdfbd965ce8ef29489898c0","size":14845,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/def83f4ee7b33da49cdfbd965ce8ef29489898c0"},{"path":"tests/im_iterator.rs","mode":"100644","type":"blob","sha":"6003aa635e13fe6661d34f4c1f23ffa9eac1aabc","size":6267,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6003aa635e13fe6661d34f4c1f23ffa9eac1aabc"},{"path":"tests/im_message_flows.rs","mode":"100644","type":"blob","sha":"0c05334a30ad50ffd636787138a3eaa3c6f7cee7","size":6670,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/0c05334a30ad50ffd636787138a3eaa3c6f7cee7"},{"path":"tests/json_value.rs","mode":"100644","type":"blob","sha":"2f511902a9a29a6107023feb9f90a4539788e1ef","size":1688,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2f511902a9a29a6107023feb9f90a4539788e1ef"},{"path":"tests/mail_event.rs","mode":"100644","type":"blob","sha":"8202f6cf00d16377947ecfab6355c7490b762d3d","size":1480,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/8202f6cf00d16377947ecfab6355c7490b762d3d"},{"path":"tests/meeting_room_event.rs","mode":"100644","type":"blob","sha":"e5f93d7fa5f9a915a666403be54ac60a8332c1c8","size":1938,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e5f93d7fa5f9a915a666403be54ac60a8332c1c8"},{"path":"tests/minutes_event.rs","mode":"100644","type":"blob","sha":"87ac1e3af2ae7e3c2a19b265805a3f7416cb926b","size":1463,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/87ac1e3af2ae7e3c2a19b265805a3f7416cb926b"},{"path":"tests/moments_event.rs","mode":"100644","type":"blob","sha":"4b8ca4ed4946f838f6ab05528a7e99ddc3a64b40","size":5059,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/4b8ca4ed4946f838f6ab05528a7e99ddc3a64b40"},{"path":"tests/oauth_live.rs","mode":"100644","type":"blob","sha":"2819fda7e1ef301c55abda123601504350f69739","size":2079,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2819fda7e1ef301c55abda123601504350f69739"},{"path":"tests/okr_v2.rs","mode":"100644","type":"blob","sha":"b0bda14bfe8f21e4e546e181a361156902f22efb","size":7458,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b0bda14bfe8f21e4e546e181a361156902f22efb"},{"path":"tests/payroll_event.rs","mode":"100644","type":"blob","sha":"c15e52fcdccbdbc260df1b37d7a8b8b0241ce09b","size":1313,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/c15e52fcdccbdbc260df1b37d7a8b8b0241ce09b"},{"path":"tests/performance_event.rs","mode":"100644","type":"blob","sha":"1761f88cd694d226e3bcf87d6b208491b0ce9d76","size":2788,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1761f88cd694d226e3bcf87d6b208491b0ce9d76"},{"path":"tests/pre_release_refactor.rs","mode":"100644","type":"blob","sha":"e7bbc17c5a1e7d69994f47f6436989b524b9f79b","size":1013,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/e7bbc17c5a1e7d69994f47f6436989b524b9f79b"},{"path":"tests/req.rs","mode":"100644","type":"blob","sha":"2e71d56b93f83e774c1d83bf662184cec2b6fda0","size":4536,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2e71d56b93f83e774c1d83bf662184cec2b6fda0"},{"path":"tests/request_contract_alignment.rs","mode":"100644","type":"blob","sha":"b7f1a622e5cabfdf829731de95ba774c065d60c8","size":2421,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b7f1a622e5cabfdf829731de95ba774c065d60c8"},{"path":"tests/resp.rs","mode":"100644","type":"blob","sha":"d48f3a5c760dc81d082a57430a9263ec38cb69c1","size":8556,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d48f3a5c760dc81d082a57430a9263ec38cb69c1"},{"path":"tests/security_and_compliance_event.rs","mode":"100644","type":"blob","sha":"fd2c82039cb3b02018435f2ee1a5aae9c81b6829","size":4411,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fd2c82039cb3b02018435f2ee1a5aae9c81b6829"},{"path":"tests/spark_database_migration.rs","mode":"100644","type":"blob","sha":"adc55514564903df865a7bec7949a6c2d849c044","size":6152,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/adc55514564903df865a7bec7949a6c2d849c044"},{"path":"tests/spark_database_quota.rs","mode":"100644","type":"blob","sha":"da3374c0f86c73d5754186dbd30b9417d671eea1","size":4050,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/da3374c0f86c73d5754186dbd30b9417d671eea1"},{"path":"tests/spark_db_sync_live.rs","mode":"100644","type":"blob","sha":"9469c1fd921dec277d466d30c761590c987e1f5f","size":4256,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/9469c1fd921dec277d466d30c761590c987e1f5f"},{"path":"tests/spark_export.rs","mode":"100644","type":"blob","sha":"3395689666530a242b4701d9d32bfc5de4466378","size":9638,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/3395689666530a242b4701d9d32bfc5de4466378"},{"path":"tests/spark_storage.rs","mode":"100644","type":"blob","sha":"f96a906d7f8b9493a52c9c365bb22f61e7e6574e","size":6409,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f96a906d7f8b9493a52c9c365bb22f61e7e6574e"},{"path":"tests/task_event.rs","mode":"100644","type":"blob","sha":"97de39d8bba291f5d2f6b7b5f257a3d99d5b03f2","size":2849,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/97de39d8bba291f5d2f6b7b5f257a3d99d5b03f2"},{"path":"tests/token.rs","mode":"100644","type":"blob","sha":"2c69b7146a1294790400cf6933977390af54dbd7","size":17552,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2c69b7146a1294790400cf6933977390af54dbd7"},{"path":"tests/transport.rs","mode":"100644","type":"blob","sha":"2677e94cd137e28c28e1bdfb11acda1762fb230f","size":3678,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2677e94cd137e28c28e1bdfb11acda1762fb230f"},{"path":"tests/transport_mock.rs","mode":"100644","type":"blob","sha":"fd4e4d14a21a033064b7f7466104794c2a593533","size":38133,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/fd4e4d14a21a033064b7f7466104794c2a593533"},{"path":"tests/typed_domain_models.rs","mode":"100644","type":"blob","sha":"d27f5f02c29af254be1521ebec063f6dfdc0f4ba","size":29639,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/d27f5f02c29af254be1521ebec063f6dfdc0f4ba"},{"path":"tests/vc_event.rs","mode":"100644","type":"blob","sha":"695ed0710aa454a5f24cd155032c79d2d5a36b0c","size":11877,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/695ed0710aa454a5f24cd155032c79d2d5a36b0c"},{"path":"tools","mode":"040000","type":"tree","sha":"6639e2494fede2364490a3bd7e874807b40a9ab1","url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/trees/6639e2494fede2364490a3bd7e874807b40a9ab1"},{"path":"tools/generate_go_compatibility_metadata.go","mode":"100644","type":"blob","sha":"5fcdfb93b06d22350300e9a0d1b05fdd8f0a3192","size":11729,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5fcdfb93b06d22350300e9a0d1b05fdd8f0a3192"},{"path":"tools/generate_go_compatibility_metadata_test.go","mode":"100644","type":"blob","sha":"f0a80868a2de7ea3bafa27bbcc4b428c2e26faaa","size":1422,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/f0a80868a2de7ea3bafa27bbcc4b428c2e26faaa"},{"path":"tools/generate_go_event_catalog.go","mode":"100644","type":"blob","sha":"2c7e4bd7e290a8fc2a7c5a640e4da24f3aeb3f18","size":9939,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/2c7e4bd7e290a8fc2a7c5a640e4da24f3aeb3f18"},{"path":"tools/generate_go_event_catalog_test.go","mode":"100644","type":"blob","sha":"6bdc953d40641b2c0bd89f931f44bb0b6014c4da","size":2479,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/6bdc953d40641b2c0bd89f931f44bb0b6014c4da"},{"path":"tools/generate_go_rust_event_parity.go","mode":"100644","type":"blob","sha":"5e05edca9552cd4817bb659d07b1ba93e1c4a990","size":10417,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/5e05edca9552cd4817bb659d07b1ba93e1c4a990"},{"path":"tools/generate_go_rust_event_parity_test.go","mode":"100644","type":"blob","sha":"722743d9aa5accaf60b450cf61b086158df400e4","size":1419,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/722743d9aa5accaf60b450cf61b086158df400e4"},{"path":"tools/generate_go_rust_service_parity.go","mode":"100644","type":"blob","sha":"a99391244e5fbf1a87320e6dba5ffc1eb906901d","size":25034,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a99391244e5fbf1a87320e6dba5ffc1eb906901d"},{"path":"tools/generate_go_rust_service_parity_test.go","mode":"100644","type":"blob","sha":"a3f975de7aad83a84cc14bba090f6cca19b74137","size":9060,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/a3f975de7aad83a84cc14bba090f6cca19b74137"},{"path":"tools/generate_go_service_catalog.go","mode":"100644","type":"blob","sha":"80b461e65dbda6edde80e70f7d7213e4a20efad2","size":9446,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/80b461e65dbda6edde80e70f7d7213e4a20efad2"},{"path":"tools/generate_go_service_catalog_test.go","mode":"100644","type":"blob","sha":"b0d46d978d28a3915ccb86ed0d6d96604e54ece7","size":3489,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/b0d46d978d28a3915ccb86ed0d6d96604e54ece7"},{"path":"tools/go_event_catalog.json","mode":"100644","type":"blob","sha":"458e2d4425a6efdeb513963924d7e7bbab8b66a2","size":96567,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/458e2d4425a6efdeb513963924d7e7bbab8b66a2"},{"path":"tools/go_rust_event_parity.json","mode":"100644","type":"blob","sha":"639135877310fc36038754f54952dca987547911","size":2230,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/639135877310fc36038754f54952dca987547911"},{"path":"tools/go_rust_service_parity.json","mode":"100644","type":"blob","sha":"1fdc2a54e6a593bfd184272ca8964f8e6e20cbc6","size":1100,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/1fdc2a54e6a593bfd184272ca8964f8e6e20cbc6"},{"path":"tools/go_rust_service_parity_omissions.json","mode":"100644","type":"blob","sha":"511528a18447d85bbf9b075a8b63de580dfb92ea","size":397,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/511528a18447d85bbf9b075a8b63de580dfb92ea"},{"path":"tools/go_service_catalog.json","mode":"100644","type":"blob","sha":"895db1ed90bb1d842e2306e62acf4837a524c297","size":500915,"url":"https://api.github.com/repos/adamcavendish/larksuite-oapi-sdk-rs/git/blobs/895db1ed90bb1d842e2306e62acf4837a524c297"}],"truncated":false} \ No newline at end of file diff --git a/docs/02-架构设计/专项设计/.research/larkrs-ws-client-example.rs b/docs/02-架构设计/专项设计/.research/larkrs-ws-client-example.rs new file mode 100644 index 0000000..9e5b39a --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/larkrs-ws-client-example.rs @@ -0,0 +1,97 @@ +use larksuite_oapi_sdk_rs::event::EventDispatcher; +use larksuite_oapi_sdk_rs::events::im::P2MessageReceiveV1; +use larksuite_oapi_sdk_rs::service::im::v1::CreateMessageReqBody; +use larksuite_oapi_sdk_rs::{LarkClient, LarkError, RequestOption}; +use std::time::Duration; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + let app_id = std::env::var("APP_ID").expect("APP_ID env var required"); + let app_secret = std::env::var("APP_SECRET").expect("APP_SECRET env var required"); + + let client = LarkClient::builder(&app_id, &app_secret).build()?; + + // Keep the SDK event handler short: the WebSocket client can acknowledge + // delivery as soon as the event is queued. A worker performs outbound I/O. + let (event_tx, mut event_rx) = tokio::sync::mpsc::channel::(128); + let worker_client = client.clone(); + let worker = tokio::spawn(async move { + while let Some(event) = event_rx.recv().await { + if event.message.message_type != "text" { + continue; + } + + let message_id = event.message.message_id; + let chat_id = event.message.chat_id; + println!( + "[worker] message: id={message_id} content={}", + event.message.content + ); + + let body = CreateMessageReqBody { + receive_id: Some(chat_id), + msg_type: Some("text".to_string()), + content: Some(r#"{"text":"Hello from the queued worker!"}"#.to_string()), + // Keep retries for the same inbound message idempotent. + uuid: Some(format!("reply-{message_id}")), + }; + + match worker_client + .im() + .message + .create("chat_id", &body, &RequestOption::default()) + .await + { + Ok(response) if response.success() => { + println!("[worker] reply sent"); + } + Ok(response) => { + eprintln!("[worker] reply rejected: {:?}", response.code_error); + } + Err(error) => { + eprintln!("[worker] reply failed: {error}"); + } + } + } + }); + + let dispatcher = EventDispatcher::new("", "").on_p2_im_message_receive_v1( + move |event: P2MessageReceiveV1| { + let event_tx = event_tx.clone(); + async move { + event_tx.try_send(event).map_err(|error| { + LarkError::Event(format!("event queue is full or closed: {error}")) + }) + } + }, + ); + + let mut ws_client = client + .ws_client(dispatcher) + .on_ready(|| println!("websocket connected")) + .on_disconnected(|| eprintln!("websocket disconnected")) + .on_error(|error| eprintln!("websocket error: {error}")); + if let Ok(channel_tag) = std::env::var("WS_CHANNEL_TAG") { + ws_client = ws_client.channel_tag(channel_tag); + } + let user_access_token = std::env::var("USER_ACCESS_TOKEN").ok(); + let control = user_access_token.as_ref().map(|_| ws_client.control()); + + println!("starting ws client (Ctrl+C to stop)..."); + let ws_task = tokio::spawn(ws_client.start()); + + if let (Some(control), Some(user_access_token)) = (control, user_access_token) { + while control.connection_id().is_none() { + tokio::time::sleep(Duration::from_millis(100)).await; + } + control.attach_user(&user_access_token).await?; + println!("trusted user channel attached"); + } + + ws_task.await??; + worker.await?; + + Ok(()) +} diff --git a/docs/02-架构设计/专项设计/.research/show-comments.ps1 b/docs/02-架构设计/专项设计/.research/show-comments.ps1 new file mode 100644 index 0000000..28203a9 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/show-comments.ps1 @@ -0,0 +1,8 @@ +$c = Get-Content 'E:\wk-lab\devflow\docs\02-架构设计\专项设计\.research\issue-130-comments.json' -Raw | ConvertFrom-Json +$out = foreach ($x in $c) { + "=== $($x.user.login) @ $($x.created_at) ===" + $x.body + "" +} +$out | Set-Content 'E:\wk-lab\devflow\docs\02-架构设计\专项设计\.research\issue-130-comments.txt' -Encoding UTF8 +Write-Output "written" diff --git a/docs/02-架构设计/专项设计/.research/wecom-aibot-readme.md b/docs/02-架构设计/专项设计/.research/wecom-aibot-readme.md new file mode 100644 index 0000000..b8d5836 --- /dev/null +++ b/docs/02-架构设计/专项设计/.research/wecom-aibot-readme.md @@ -0,0 +1,546 @@ +# wecom-aibot-rust-sdk (Rust) + +企业微信智能机器人 Rust SDK —— 基于 WebSocket 长连接通道,提供消息收发、流式回复、模板卡片、事件回调、文件下载解密等核心能力。 + +> 本项目是 Python 版本 [@wecom/aibot-python-sdk](https://pypi.org/project/wecom-aibot-python-sdk/) 的 Rust 改写版本。 + +## ✨ 特性 + +- 🔗 **WebSocket 长连接** — 基于 `wss://openws.work.weixin.qq.com` 内置默认地址,开箱即用 +- 🔐 **自动认证** — 连接建立后自动发送认证帧(bot_id + secret) +- 💓 **心跳保活** — 自动维护心跳,连续未收到 ack 时自动判定连接异常 +- 🔄 **断线重连** — 指数退避重连策略(1s → 2s → 4s → ... → 30s 上限),支持自定义最大重连次数 +- 📨 **消息分发** — 自动解析消息类型并触发对应事件(text / image / mixed / voice / file) +- 🌊 **流式回复** — 内置流式回复方法,支持 Markdown 和图文混排 +- 🃏 **模板卡片** — 支持回复模板卡片消息、流式 + 卡片组合回复、更新卡片 +- 📤 **主动推送** — 支持向指定会话主动发送 Markdown 或模板卡片消息,无需依赖回调帧 +- 📡 **事件回调** — 支持进入会话、模板卡片按钮点击、用户反馈等事件 +- ⏩ **串行回复队列** — 同一 req_id 的回复消息串行发送,自动等待回执 +- 🔑 **文件下载解密** — 内置 AES-256-CBC 文件解密,每个图片/文件消息自带独立的 aeskey +- 🪵 **可插拔日志** — 支持自定义 Logger,内置带时间戳的 DefaultLogger +- 🦀 **tokio 原生** — 基于 Rust tokio 异步运行时,支持 async/await + +## 📦 安装 + +在 `Cargo.toml` 中添加依赖: + +```toml +[dependencies] +wecom-aibot-rust-sdk = "1.0.2" +tokio = { version = "1.35", features = ["full"] } +``` + +**依赖:** +- Rust >= 1.70 +- tokio >= 1.35 +- tokio-tungstenite >= 0.21 +- tokio-stream >= 0.1 +- reqwest >= 0.11 +- serde >= 1.0 +- serde_json >= 1.0 +- aes >= 0.8 +- cbc >= 0.1 +- base64 >= 0.21 +- cpufeatures >= 0.2 +- thiserror >= 1.0 +- futures >= 0.3 +- parking_lot >= 0.12 +- rand >= 0.9 +- hex >= 0.4 +- chrono >= 0.4 +- regex >= 1.10 +- urlencoding >= 2.1 + +## ⚙️ 配置 + +```bash +# 复制示例配置文件 +cp .env.example .env + +# 编辑 .env 文件,填入真实配置 +WECHAT_BOT_ID=your-bot-id +WECHAT_BOT_SECRET=your-bot-secret + +# (可选)配置下载文件存放目录,不设置则使用系统 temp 目录 +WECHAT_BOT_DOWNLOAD_DIR=/path/to/download/dir +``` + +## 🚀 快速开始 + +```rust +use dotenv::dotenv; +use std::env; +use tokio::time::{self, Duration}; +use wecom_aibot_rust_sdk::{generate_req_id, WSClient, WSClientOptions}; + +#[tokio::main] +async fn main() { + // 加载 .env 文件 + dotenv().ok(); + + // 1. 创建客户端实例 + let client = WSClient::new(WSClientOptions::new( + env::var("WECHAT_BOT_ID").unwrap(), + env::var("WECHAT_BOT_SECRET").unwrap(), + )); + + // 2. 监听认证成功 + client.on_authenticated(|| { + println!("🔐 认证成功"); + }); + + // 3. 监听文本消息并进行流式回复 + let client_text = client.clone(); + client.on_message_text(move |frame| { + let client_text = client_text.clone(); + let frame = frame.clone(); + + tokio::spawn(async move { + let content = frame + .body + .as_ref() + .and_then(|v| v.as_object()) + .and_then(|b| b.get("text")) + .and_then(|v| v.as_object()) + .and_then(|t| t.get("content")) + .and_then(|v| v.as_str()) + .unwrap_or(""); + + println!("收到文本:{}", content); + + let stream_id = generate_req_id("stream"); + + // 发送流式中间内容 + let _ = client_text + .reply_stream(&frame, &stream_id, "正在思考中...", false, None, None) + .await; + + // 发送最终结果 + time::sleep(Duration::from_secs(1)).await; + let _ = client_text + .reply_stream( + &frame, + &stream_id, + &format!("你好!你说的是:\"{}\"", content), + true, + None, + None, + ) + .await; + }); + }); + + // 4. 监听进入会话事件(发送欢迎语) + let client_enter = client.clone(); + client.on_event_enter_chat(move |frame| { + let client_enter = client_enter.clone(); + let frame = frame.clone(); + + tokio::spawn(async move { + use serde_json::json; + let _ = client_enter + .reply_welcome( + &frame, + json!({ + "msgtype": "text", + "text": {"content": "您好!我是智能助手,有什么可以帮您的吗?"} + }), + ) + .await; + }); + }); + + // 5. 启动客户端 + println!("正在启动机器人..."); + if let Err(e) = client.connect().await { + eprintln!("启动失败:{}", e); + return; + } + + println!("机器人启动成功,按 Ctrl+C 退出"); + + // 等待退出信号 + tokio::signal::ctrl_c().await.ok(); + + println!("\n正在停止机器人..."); + client.disconnect(); +} +``` + +## 📚 示例说明 + +SDK 提供了多个示例代码,位于 `examples/` 目录下: + +### 1. basic.rs - 基础使用示例 + +演示基本的连接、消息监听和回复功能。 + +```bash +cargo run --example basic +``` + +### 2. echo_test.rs - Echo 测试机器人 + +收到消息后自动原样回复,适合测试基本功能。 + +```bash +cargo run --example echo_test +``` + +### 3. test_files.rs - 文件/图片下载测试 + +演示如何下载和解密企业微信发送的图片和文件,支持: +- 下载加密图片并保存 +- 下载加密文件并保存 +- 自动处理未加密的文件(如截屏图片) + +```bash +cargo run --example test_files +``` + +### 4. sender_info.rs - 消息发送人信息示例 + +演示如何获取消息发送人的详细信息,包括: +- 发送人 userid +- 发送人 username(如果可用) +- 发送人 gender(如果可用) + +```bash +cargo run --example sender_info +``` + +### 5. upload_media.rs - 上传临时素材示例 + +演示如何通过 WebSocket 分片上传临时素材到企业微信,支持: +- 图片(image):≤10MB, JPG/PNG 格式 +- 语音(voice):≤2MB, AMR 格式 +- 视频(video):≤10MB, MP4 格式 +- 文件(file):≤10MB + +```bash +# 上传文件 +cargo run --example upload_media <文件路径> + +# 示例:上传图片 +cargo run --example upload_media test.jpg +``` + +上传成功后返回 `media_id`,可用于发送消息时引用该素材。 + +## 📖 API 文档 + +### `WSClient` + +核心客户端类,提供连接管理、消息收发等功能。 + +#### 构造函数 + +```rust +let client = WSClient::new(WSClientOptions::new(bot_id, secret)); +``` + +#### 配置选项 + +`WSClientOptions` 完整配置: + +| 参数 | 类型 | 必填 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| `bot_id` | `String` | ✅ | — | 机器人 ID(企业微信后台获取) | +| `secret` | `String` | ✅ | — | 机器人 Secret(企业微信后台获取) | +| `reconnect_interval` | `u64` | — | `1000` | 重连基础延迟(毫秒),实际延迟按指数退避递增 | +| `max_reconnect_attempts` | `i32` | — | `10` | 最大重连次数(`-1` 表示无限重连) | +| `heartbeat_interval` | `u64` | — | `30000` | 心跳间隔(毫秒) | +| `request_timeout` | `u64` | — | `10000` | HTTP 请求超时时间(毫秒) | +| `ws_url` | `Option` | — | `None` | 自定义 WebSocket 连接地址 | +| `logger` | `Option>` | — | `None` | 自定义日志实例 | + +```rust +let options = WSClientOptions { + bot_id: "your-bot-id".to_string(), + secret: "your-bot-secret".to_string(), + reconnect_interval: 1000, + max_reconnect_attempts: 10, + heartbeat_interval: 30000, + request_timeout: 10000, + ws_url: None, + logger: None, +}; +``` + +#### 方法 + +| 方法 | 说明 | 返回值 | +| --- | --- | --- | +| `async connect()` | 建立 WebSocket 连接,连接后自动认证 | `Result<(), SdkError>` | +| `disconnect()` | 主动断开连接 | `()` | +| `async reply(frame, body, cmd)` | 通过 WebSocket 通道发送回复消息(通用方法) | `Result` | +| `async reply_stream(frame, stream_id, content, finish, msg_item, feedback)` | 发送流式文本回复(便捷方法,支持 Markdown) | `Result` | +| `async reply_welcome(frame, body)` | 发送欢迎语回复(支持文本或模板卡片格式),需在收到事件 5s 内调用 | `Result` | +| `async reply_template_card(frame, template_card, feedback)` | 回复模板卡片消息 | `Result` | +| `async reply_stream_with_card(frame, stream_id, content, finish, ...)` | 发送流式消息 + 模板卡片组合回复 | `Result` | +| `async update_template_card(frame, template_card, userids)` | 更新模板卡片,需在收到事件 5s 内调用 | `Result` | +| `async send_message(chatid, body)` | 主动发送消息(支持 Markdown 或模板卡片),无需依赖回调帧 | `Result` | +| `async download_file(url, aes_key)` | 下载文件并使用 AES 密钥解密 | `Result<(Vec, Option), SdkError>` | +| `async upload_media(media_type, file_data, filename)` | 上传临时素材(通过 WebSocket 分片上传) | `Result` | + +#### 属性 + +| 方法 | 说明 | 返回类型 | +| --- | --- | --- | +| `is_connected()` | 当前 WebSocket 连接状态 | `bool` | +| `api()` | 内部 API 客户端实例(高级用途) | `Arc` | + +### 事件监听 + +所有事件均通过 `on_*` 方法监听: + +```rust +// 连接事件 +client.on_connected(|| { println!("WebSocket 已连接"); }); +client.on_authenticated(|| { println!("认证成功"); }); +client.on_disconnected(|reason| { println!("连接已断开:{}", reason); }); +client.on_reconnecting(|attempt| { println!("正在进行第 {} 次重连...", attempt); }); +client.on_error(|error| { eprintln!("发生错误:{}", error); }); + +// 消息事件 +client.on_message(|frame| { println!("收到消息:{:?}", frame); }); +client.on_message_text(|frame| { println!("收到文本消息"); }); +client.on_message_image(|frame| { println!("收到图片消息"); }); +client.on_message_mixed(|frame| { println!("收到图文混排消息"); }); +client.on_message_voice(|frame| { println!("收到语音消息"); }); +client.on_message_file(|frame| { println!("收到文件消息"); }); + +// 事件回调 +client.on_event(|frame| { println!("收到事件回调"); }); +client.on_event_enter_chat(|frame| { println!("用户进入会话"); }); +client.on_event_template_card(|frame| { println!("模板卡片事件"); }); +client.on_event_feedback(|frame| { println!("用户反馈事件"); }); +``` + +### `reply_stream` 详细说明 + +```rust +await client.reply_stream( + &frame, // 收到的原始 WebSocket 帧(透传 req_id) + &stream_id, // 流式消息 ID(使用 generate_req_id("stream") 生成) + "回复内容", // 回复内容(支持 Markdown) + false, // 是否结束流式消息 + None, // 图文混排项(仅 finish=true 时有效) + None, // 反馈信息(仅首次回复时设置) +); +``` + +### `reply_welcome` 详细说明 + +发送欢迎语回复,需在收到 `event.enter_chat` 事件 5 秒内调用。 + +```rust +// 文本欢迎语 +use serde_json::json; +await client.reply_welcome(&frame, json!({ + "msgtype": "text", + "text": {"content": "欢迎!"}, +})); + +// 模板卡片欢迎语 +await client.reply_welcome(&frame, json!({ + "msgtype": "template_card", + "template_card": {"card_type": "text_notice", "main_title": {"title": "欢迎"}}, +})); +``` + +### `send_message` 详细说明 + +主动向指定会话推送消息,无需依赖收到的回调帧。 + +```rust +// 发送 Markdown 消息 +await client.send_message("userid_or_chatid", json!({ + "msgtype": "markdown", + "markdown": {"content": "这是一条**主动推送**的消息"}, +})); + +// 发送模板卡片消息 +await client.send_message("userid_or_chatid", json!({ + "msgtype": "template_card", + "template_card": {"card_type": "text_notice", "main_title": {"title": "通知"}}, +})); +``` + +### `upload_media` 上传临时素材 + +通过 WebSocket 分片上传临时素材到企业微信,支持图片、语音、视频和文件。 + +```rust +use std::fs; + +// 读取文件 +let file_data = fs::read("test.pdf").unwrap(); + +// 上传文件(media_type: "image", "voice", "video", "file") +match client.upload_media("file", &file_data, "test.pdf").await { + Ok(result) => { + let media_id = result.get("media_id").and_then(|v| v.as_str()).unwrap(); + println!("上传成功,media_id: {}", media_id); + } + Err(e) => eprintln!("上传失败:{}", e), +} +``` + +**文件大小限制:** +| 类型 | 大小限制 | 格式 | +| --- | --- | --- | +| image | ≤10MB | JPG, PNG | +| voice | ≤2MB | AMR (≤60s) | +| video | ≤10MB | MP4 | +| file | ≤10MB | - | + +**注意:** 临时素材 `media_id` 有效期为 3 天。 + +### `download_file` 使用示例 + +```rust +// aes_key 取自消息体中的 image.aeskey 或 file.aeskey +client.on_message_image(|frame| { + let client = client.clone(); + let frame = frame.clone(); + + tokio::spawn(async move { + let aes_key = frame + .body + .as_ref() + .and_then(|v| v.as_object()) + .and_then(|b| b.get("image")) + .and_then(|v| v.as_object()) + .and_then(|i| i.get("aeskey")) + .and_then(|v| v.as_str()) + .map(String::from); + + let url = frame + .body + .as_ref() + .and_then(|v| v.as_object()) + .and_then(|b| b.get("image")) + .and_then(|v| v.as_object()) + .and_then(|i| i.get("url")) + .and_then(|v| v.as_str()) + .map(String::from); + + if let (Some(url), aes_key) = (url, aes_key) { + let (buffer, filename) = client.download_file(&url, aes_key.as_deref()).await.unwrap(); + println!("文件名:{:?}, 大小:{} bytes", filename, buffer.len()); + } + }); +}); +``` + +## 📋 消息类型 + +SDK 支持以下消息类型(`MessageType` 枚举): + +| 类型 | 值 | 说明 | +| --- | --- | --- | +| `MessageType::Text` | `"text"` | 文本消息 | +| `MessageType::Image` | `"image"` | 图片消息 | +| `MessageType::Mixed` | `"mixed"` | 图文混排消息 | +| `MessageType::Voice` | `"voice"` | 语音消息 | +| `MessageType::File` | `"file"` | 文件消息 | + +SDK 支持以下事件类型(`EventType` 枚举): + +| 类型 | 值 | 说明 | +| --- | --- | --- | +| `EventType::EnterChat` | `"enter_chat"` | 进入会话事件 | +| `EventType::TemplateCardEvent` | `"template_card_event"` | 模板卡片事件 | +| `EventType::FeedbackEvent` | `"feedback_event"` | 用户反馈事件 | + +## 🪵 自定义日志 + +实现 `Logger` Trait 接口即可自定义日志输出: + +```rust +use wecom_aibot_rust_sdk::types::Logger; + +struct MyLogger; + +impl Logger for MyLogger { + fn debug(&self, message: &str) { + // 静默 debug 日志 + } + + fn info(&self, message: &str) { + println!("[INFO] {}", message); + } + + fn warn(&self, message: &str) { + println!("[WARN] {}", message); + } + + fn error(&self, message: &str) { + println!("[ERROR] {}", message); + } +} + +let options = WSClientOptions { + bot_id: "your-bot-id".to_string(), + secret: "your-bot-secret".to_string(), + logger: Some(Box::new(MyLogger)), + ..Default::default() +}; + +let client = WSClient::new(options); +``` + +## 📂 项目结构 + +``` +rust_wecom_bot_rust_sdk/ +├── src/ +│ ├── lib.rs # 库入口文件,统一导出 +│ ├── client.rs # WSClient 核心客户端 +│ ├── ws.rs # WebSocket 长连接管理器 +│ ├── message_handler.rs # 消息解析与事件分发 +│ ├── api.rs # HTTP API 客户端(文件下载) +│ ├── crypto_utils.rs # AES-256-CBC 文件解密 +│ ├── logger.rs # 默认日志实现 +│ ├── utils.rs # 工具方法(generate_req_id 等) +│ └── types.rs # 类型定义(枚举、结构体、常量) +├── examples/ +│ ├── basic.rs # 基础使用示例 +│ ├── send_test_message.rs # 发送测试消息示例 +│ ├── echo_test.rs # Echo 测试机器人 +│ ├── test_files.rs # 文件/图片下载测试 +│ ├── sender_info.rs # 消息发送人信息示例 +│ └── upload_media.rs # 上传临时素材示例 +├── Cargo.toml # 项目配置 +├── README.md # 本文件 +└── .env.example # 环境变量示例 +``` + +## 🏗️ 架构说明 + +本 SDK 采用异步事件驱动架构,主要组件: + +``` +┌─────────────────────────────────────────────────────┐ +│ WSClient │ +│ (核心客户端,提供事件注册和消息发送接口) │ +└─────────────────────────────────────────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ WsConnection │ │ MessageHandler │ │ WeComApiClient │ +│ Manager │ │ (消息分发) │ │ (文件下载) │ +│ (连接/心跳/重连) │ │ │ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ WebSocket LWS │ │ Event Types │ +│ (长连接) │ │ (文本/图片/事件)│ +└─────────────────┘ └─────────────────┘ +``` + +## 📄 License + +MIT diff --git a/docs/02-架构设计/专项设计/devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md b/docs/02-架构设计/专项设计/devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md index 306908b..4efabee 100644 --- a/docs/02-架构设计/专项设计/devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md +++ b/docs/02-架构设计/专项设计/devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md @@ -152,3 +152,194 @@ IM 用户 @机器人 "把登录任务推进到 in_review" - **df-mcp**:对外 stdio 暴露数据层(本地进程级);本规划是远程人类入口(IM 级),互补不重叠 - **u-relay**:零改动(除非 D5 触发多 token 需求) - **df-tunnel / AI 引擎**:零改动 + +## 附录A、PostgreSQL 多租户 × DevFlow 结合分析(§十 S3 展开论证) + +### A.1 概念:多租户三模式 + +| 模式 | 结构 | 优点 | 缺点 | 适合 | +|---|---|---|---|---| +| **行级隔离** | 同库同表,每行 `tenant_id` 列,查询强制 WHERE | 成本最低、迁移跑一份、跨租户统计易 | 隔离最弱(漏写 WHERE 即泄漏)、noisy neighbor | 租户多单租户数据小,SaaS 主流 | +| **独立 Schema** | 一实例每租户一 schema | 隔离较强,按 schema 备份 | 租户多时爆炸;DDL 迁移跑 N 遍 | 租户几百内 | +| **独立 Database** | 每租户一库 | 最强隔离 | 资源贵、连接池复杂、跨租户查询难 | 租户少而大、合规严 | + +行级隔离通常配合 **PG RLS(Row Level Security)**:租户过滤下沉数据库层——session 设 `SET app.tenant_id`,策略自动过滤,应用层漏写 WHERE 也不泄漏。这是 PG 多租户的核心王牌。 + +### A.2 激活时机 + +DevFlow 当前是本地优先单机应用(SQLite/单用户/零运维),「租户」不存在。多租户仅在 **S3 AI 引擎上云**时被激活:SQLite 单写者模型扛不住云端多用户并发写,且「本地优先」范式失效——隔离必须显式设计。**多租户是需求,PostgreSQL 是载体**(PG 因并发写 + RLS + 生态成为默认答案)。 + +### A.3 租户模型与选型 + +- 租户模型:`tenant`=团队(付费主体),`user`=成员;现有全部表(projects/tasks/ideas/workflows/ai_messages/knowledge/audit…)挂 `tenant_id` +- 选型:**行级隔离 + RLS**(非 schema/database 隔离)。决定性理由是迁移演进节奏:V1-V16+ 已 20+ 表且快速迭代,per-tenant schema 下每次迁移要循环跑 N 遍并处理失败重试,直接拖死迭代速度;行级隔离迁移只跑一份 + +### A.4 落点三抓手 + +1. **df-storage trait 层**(对齐 F-07 下沉思路):存储访问收敛 Repository trait,多租户化 = trait 实现层注入租户上下文(依赖 PG session 变量),上层 commands 无感——现有分层红利 +2. **迁移体系**:SQLite 手写迁移换 sqlx/refinery 版本化迁移,一套脚本跑一份 +3. **RLS 策略**:每表 `USING (tenant_id = current_setting('app.tenant_id')::uuid)`,连接池(pgbouncer)按事务设租户变量 + +### A.5 配套改造清单(多租户 ≠ 换数据库驱动) + +缓存键带租户(防 Redis 串数据)/ 对象存储按租户前缀隔离 / 日志审计按租户切分 / 备份恢复按租户粒度。**全链路改造**——这正是 §十 把 PG 多租户列入「显式不做」的原因:现在做 = 为不存在的需求付全链路成本。 + +### A.6 与本期规划的交集 + +唯一交集 = P0 已预留的会话映射表 `owner_user_id` 列(S2 时是「用户归属」,S3 升级语义为「租户内用户」),成本≈0。时序红线重申:S2 各人数据仍在各自桌面端 SQLite,云端只有路由表,**不需要 PG**;S3 才引入 PG+RLS,与引擎多租户化统一做。 + +## 附录B、引入云端后的多人架构模式(C4 视图) + +> 按 C4 模型整理:C1 上下文(现状基线 → S2 多人 → S3 引擎上云)+ C2 容器(网关内部)+ 部署视图 + 数据归属演进表。 +> 贯穿不变量:**TunnelCommand/TunnelEvent 是网关↔执行端唯一契约**,执行端可从「桌面端 df-tunnel」无缝换成「云端 df-agent」,网关与 IM 端零改动。 + +### B.1 C1 上下文 —— 现状基线(S0:单机 + 小程序遥控) + +```mermaid +flowchart TB + subgraph local["💻 用户本地(数据主权 100% 在个人)"] + desktop["DevFlow 桌面端 (Tauri v2)
AI Chat 引擎(Agentic Loop+审批) · SQLite · df-tunnel(Agent)"] + end + subgraph cloud["☁️ 云端 u-relay.1216.top(nginx TLS 终止)"] + relay["u-relay :9180
channel_id 配对 · payload 透传零业务耦合"] + end + mini["📱 df-miniapp 微信小程序
(Commander)"] + mini <-->|"WSS"| relay + relay <-.->|"WS Agent 出站穿NAT
单 channel 配对"| desktop +``` + +### B.2 C1 上下文 —— S2 团队多人(云端路由化,数据仍归各人) + +本期 IM 网关落地后的直接形态:**云端只有「路由与审计」,业务数据仍在各桌面端**。 + +```mermaid +flowchart TB + subgraph users["👥 多人入口"] + direction LR + im["飞书 / 钉钉 / 企微
企业机器人"] + web["Web Chat
浏览器/手机"] + mini["df-miniapp
微信小程序"] + end + + subgraph cloud["☁️ 云端(nginx TLS 终止)"] + direction TB + gw["df-im-gateway :9190
平台适配器 · 会话映射 · 白名单
节流聚合 · 审批分级 · 审计"] + relay["u-relay :9180
多 channel 配对转发"] + gw <-->|"WS Commander(多channel)
TunnelCommand / TunnelEvent"| relay + end + + subgraph deskA["用户A 本地"] + dA["DevFlow 桌面端
AI引擎 + SQLite(A) · df-tunnel"] + end + subgraph deskB["用户B 本地"] + dB["DevFlow 桌面端
AI引擎 + SQLite(B) · df-tunnel"] + end + + im --> gw + web <-->|"HTTP/WS"| gw + mini <-->|"WSS Commander"| relay + relay <-.->|"WS Agent · chan-userA"| dA + relay <-.->|"WS Agent · chan-userB"| dB +``` + +**路由链路**:IM 消息 → 网关查映射表 `chat_id → (owner_user_id, channel_id, conv_id)` → 发 `TunnelCommand::Send` 到对应 channel → 该用户桌面端执行。**用户隔离靠 channel 隔离**(一人一 channel,per-channel token 为 u-relay 演进观察项)。 + +### B.3 C2 容器 —— df-im-gateway 内部(适配器 trait 分层,S4 扩展点) + +```mermaid +flowchart TB + subgraph adapters["平台适配器层(各平台差异收敛于此)"] + feishu["feishu_adapter
长连接+卡片流式"] + dingtalk["dingtalk_adapter
Stream Mode"] + wecom["wecom_adapter
回调验签+AES"] + webui["web_adapter
WS 直连+静态页"] + end + subgraph core["核心(平台无关,S4 加端零改动)"] + unify["统一模型
InboundEvent / OutboundAction"] + sess["会话映射表 (SQLite)
chat_id | conv_id | owner_user_id | platform | channel_id"] + throttle["节流聚合器
TextDelta 时间窗"] + acl["白名单 + 审批分级"] + audit["审计日志
IM消息 ↔ TunnelCommand"] + unify --> sess + sess --> throttle + acl --> sess + sess --> audit + end + relay["u-relay WS Commander"] + feishu --> unify + dingtalk --> unify + wecom --> unify + webui <--> unify + core <-->|"TunnelCommand / TunnelEvent"| relay +``` + +### B.4 C1 上下文 —— S3 引擎上云(云端多人完整形态,PG 多租户) + +执行端从「各人桌面端」切换为「云端 df-agent 服务」,**同一协议**,网关/IM 端零改动;桌面端退化为瘦客户端/管理台,本地优先形态仍可用(不废弃)。 + +```mermaid +flowchart TB + subgraph users["👥 多人入口"] + direction LR + im["飞书/钉钉/企微"] + web["Web Chat"] + end + subgraph cloud["☁️ 云端"] + direction TB + gw["df-im-gateway
(零改动)"] + relay["u-relay
(零改动)"] + agent["df-agent 云端执行服务
AI 引擎(Agentic Loop) · 工具沙箱
多会话/多租户调度"] + gw <--> relay + relay <-.->|"同协议
TunnelCommand / TunnelEvent"| agent + end + subgraph data["数据层(附录A 落点)"] + pg[("PostgreSQL
行级 tenant_id + RLS
projects/tasks/messages/audit")] + redis[("Redis
租户前缀缓存键")] + oss[("对象存储
租户前缀隔离")] + end + thin["💻 桌面端=瘦客户端/管理台
本地 SQLite 留个人配置"] + im --> gw + web <--> gw + agent --> pg + agent --> redis + agent --> oss + thin <-.-> relay +``` + +### B.5 部署视图(S2 落地形态,单机双服务 + nginx) + +```mermaid +flowchart LR + subgraph inet["互联网"] + u["用户 IM / 浏览器 / 小程序"] + end + subgraph server["云端主机(u-relay 同机,复用 nginx+systemd 模式)"] + nginx["nginx :443 TLS 终止
/ws/* → 9180
/im/* /chat/* → 9190"] + ur["u-relay :9180
systemd · musl 单二进制 ~3.6MB"] + gig["df-im-gateway :9190
systemd · musl 单二进制
静态 Web Chat 页托管"] + nginx --> ur + nginx --> gig + end + subgraph homes["各用户本地"] + dt["DevFlow 桌面端
出站 WSS → /ws/agent"] + end + u -->|"wss://u-relay.1216.top"| nginx + dt <-.->|"出站穿NAT"| nginx +``` + +### B.6 数据归属与信任边界演进表 + +| 维度 | S0 现状 | S2 团队多人 | S3 引擎上云 | +|---|---|---|---| +| 身份/会话映射 | 桌面端 Settings | **网关 SQLite**(owner/channel 列已预留) | 网关 → 并入 PG | +| 项目/任务/工作流 | 桌面端 SQLite | **各桌面端 SQLite**(主权在个人) | PG(tenant_id+RLS) | +| AI 消息/溯源 | 桌面端 ai_messages | 各桌面端 | PG | +| IM↔指令审计 | — | 网关审计日志 | PG(租户维度) | +| 信任边界 | 个人设备 | 云端只见「指令与脱敏结果」 | 云端见全部业务数据(需租户隔离+合规) | +| 桌面端角色 | 全栈主体 | 执行主体 | 瘦客户端/管理台(可选保留本地优先) | + +### B.7 演进的三条设计不变量 + +1. **协议不变性**:网关↔执行端唯一契约是 TunnelCommand/TunnelEvent(df-tunnel crate 类型),S3 换执行端零成本——这是全方案最重要的远期红利 +2. **u-relay 纯转发零耦合**不破例:业务逻辑只进 df-im-gateway / df-agent,中继永远轻 +3. **数据主权渐进上移**:S0/S2 数据在个人(本地优先不被废弃),S3 才集中上云并配套 RLS 隔离——每一步都由真实需求触发,不过度设计 diff --git a/docs/02-架构设计/专项设计/三方IM-Rust接入与SDK调研-2026-10-04.md b/docs/02-架构设计/专项设计/三方IM-Rust接入与SDK调研-2026-10-04.md new file mode 100644 index 0000000..0c2fe4e --- /dev/null +++ b/docs/02-架构设计/专项设计/三方IM-Rust接入与SDK调研-2026-10-04.md @@ -0,0 +1,109 @@ +# 飞书/企微/钉钉 Rust 接入与 SDK 调研 + +> 创建: 2026-10-04 | 状态: ✅ 调研完成 +> 服务于: [devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md](./devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md) P2/P3/P4 +> 原始材料: [.research/](./.research/)(larkrs 全套 + 本次新增 dingtalk/wecom README 与 crates 数据) + +## 一、总览:三家选型结论 + +| 维度 | 飞书 | 钉钉 | 企微 | +|---|---|---|---| +| **选型** | ⭐ larksuite-oapi-sdk-rs 0.3.12 | ⭐ dingtalk-stream 0.1.7 | ⭐ wecom-aibot-rust-sdk 1.0.2 | +| 作者 | adamcavendish(非官方,显式对齐 Go 官方 SDK) | spensercai(非官方,对齐官方 Python SDK commit 6381a7b) | camark(非官方,Python 版 wecom/aibot 的 Rust 改写) | +| 仓库 | github.com/adamcavendish/larksuite-oapi-sdk-rs | github.com/spensercai/dingtalk-stream-sdk-rs | github.com/camark/wecom-aibot-rust-sdk | +| 最近发版/提交 | 2026-10-03(活跃) | 2026-03-26 | 2026-04-07 | +| crates 下载(近期) | 4276(openlark-auth 关联) | 3459 | 106 | +| 连接方式 | **WS 长连接**(features=["ws"]) | **WS Stream Mode** | **WS 长连接**(wss://openws.work.weixin.qq.com)✅ | +| 免公网回调 | ✅ | ✅ | ✅(智能机器人形态,**非**传统自建应用回调模式) | +| 流式回复 | ✅ CardKit(CardEntity + sequence 递增) | ✅ 卡片 AI 流式 | ✅ reply_stream(stream_id + finish 标志,支持 Markdown) | +| 审批按钮回调 | ⚠️ card.action.trigger 新版事件(编译冒烟验证) | ✅ 卡片按钮回调 | ✅ template_card_event(5s 内 update_template_card) | +| Rust 版本要求 | 1.95+/edition 2024 | 1.85+/edition 2024 | 1.70+(tokio 1.35+) | + +**核心结论**:三家全部有可用的 Rust 原生 SDK,且三家全部支持 WS 长连接——**df-im-gateway 三平台均不需要公网回调/可信 IP/AES 回调解密**。原规划中「企微接入复杂度最高(回调验签+AES+可信域名)」的判断基于传统自建应用形态,改用**智能机器人**形态后三平台接入复杂度拉平,P4 工作量从 3-4 天降到 2-3 天。 + +## 二、飞书:larksuite-oapi-sdk-rs(已有源码级勘察) + +详见 [.research/larkrs-readme.md](./.research/larkrs-readme.md) / [larkrs-events-and-channels.md](./.research/larkrs-events-and-channels.md) / [larkrs-cards.md](./.research/larkrs-cards.md) 及任务 P2 描述中的勘察补充。要点: + +- `features=["channel"]` 一站式:`channel_messaging()` 高层封装(send/reply/reply_in_thread/edit_text/edit_card),uuid 1h 幂等窗口 +- ws feature:bootstrap 握手(POST /callback/ws/endpoint 换 wss URL)+ protobuf framing + 有限重连 + liveness(入站静默 2×ping+5s 判失败)+ write 超时 10s + 回调与收包循环解耦(慢回调不掩盖网关静默) +- 卡片三套:card::v1(JSON 1.0)/ card::v2(JSON 2.0)/ CardKit 流式(CardEntity 一次发送 + sequence 递增更新,14 天生命周期);CardDocument 带 JSON Pointer 级严格校验 +- 事件:EventDispatcher typed handler(如 on_p2_im_message_receive_v1),mpsc 队列 + worker 出站 I/O 模式可直接套用([.research/larkrs-ws-client-example.rs](./.research/larkrs-ws-client-example.rs)) +- ⚠️ 遗留验证项:CardActionHandler 官方示例是 HTTP webhook 形态;长连接下 card.action.trigger 应经事件通道分发,typed 注册口需编译冒烟确认(docs/card-protocol.md + examples/card_action_handler.rs 已存档) +- ⚠️ 坑(源码级实证,知识库已沉淀):卡片回调必须用**新版** card.action.trigger(EventDispatcher 注册);旧版 card 帧被 ws 客户端直接丢弃(Go SDK v3.12.0 中 WithCardHandler 被注释) + +## 三、钉钉:dingtalk-stream(本次新调研) + +完整对齐官方 Python SDK(open-dingtalk/dingtalk-stream-sdk-python @ 6381a7b),结构清晰(client/credential/handlers/messages/card/transport 分层)。 + +**能力覆盖**([.research/dingtalk-stream-readme.md](./.research/dingtalk-stream-readme.md)): +- Stream 协议连接:自动重连 + 60s Keepalive +- 消息收发:文本/Markdown/卡片;Rust 独有扩展——机器人可收全部 6 种消息类型(audio/file/video 仅单聊) +- 交互卡片:Markdown / **AI 流式** / 按钮 / 轮播 / RPA 插件——审批按钮与流式都覆盖 +- 事件订阅 + Graph API +- OAuth2 Token 自动管理(缓存 + 5 分钟提前刷新) +- 主动单聊:send_oto_message(POST /v1.0/robot/oToMessages/batchSend) +- 媒体下载:get_all_download_codes 统一提取 + download_bytes_with_limit 流式限速下载 + +**接入姿势**: +```rust +let credential = Credential::new(client_id, client_secret); +let mut client = DingTalkStreamClient::builder(credential) + .register_callback_handler(ChatbotMessage::TOPIC, handler) + .build(); +client.start_forever()?; +``` +Handler trait 回 `(u16, String)` ACK——**注意 ACK 语义与飞书不同**(飞书是 Response 帧回执,钉钉是 handler 返回值)。 + +**评估**:能力全覆盖、架构对齐官方、文档含时序图、有 e2e_test 示例可直接冒烟。社区量小(3459 下载)但对齐官方 Python SDK 降低了漂移风险。备选 dingtalk-stream-sdk 0.2.5(wenhaozhao,更早但更新至 2026-04)可作 fallback。 + +## 四、企微:wecom-aibot-rust-sdk(本次新调研,重大发现) + +**关键发现:企业微信「智能机器人」走 WS 长连接**(wss://openws.work.weixin.qq.com,bot_id+secret 认证帧),**完全不需要**传统自建应用的公网回调 URL / GET 验签 / POST AES 解密 / 可信 IP 白名单 / 可信域名。 + +**能力覆盖**([.research/wecom-aibot-readme.md](./.research/wecom-aibot-readme.md),546 行完整 API 文档): +- 连接管理:自动认证 + 心跳保活(30s)+ 指数退避重连(1s→30s 上限,可设无限) +- 消息分发:text / image / mixed / voice / file 五类 on_message_* 事件 +- **流式回复**:reply_stream(frame, stream_id, content, finish, ...)——stream_id + finish 标志,天然贴合 TunnelEvent::TextDelta 聚合刷新模式 +- 模板卡片:reply_template_card / **reply_stream_with_card**(流式+卡片组合)/ update_template_card(需事件 5s 内调用) +- 主动推送:send_message(chatid, body)——无需依赖回调帧,Markdown/卡片均可 +- 事件回调:enter_chat(进会话,5s 内 reply_welcome)/ **template_card_event(卡片按钮点击)** / feedback +- 文件:download_file(AES-256-CBC 自动解密,媒体自带独立 aeskey)+ upload_media(WS 分片上传,media_id 3 天有效) +- 串行回复队列:同 req_id 回复串行发送自动等回执;可插拔 Logger;tokio 原生 + +**API 形态**:回调式(on_* 闭包注册)+ client 可 clone(Arc 内部)——与 df-im-gateway 的 adapter 模式兼容性极好,直接把 on_message_text 桥到 TunnelCommand::Send。 + +**限制与验证项**: +1. **形态确认**:智能机器人 vs 自建应用的能力边界需在管理后台确认(智能机器人是否支持群聊场景、可见范围配置)。README 场景以单聊(userid)+ chatid 为主,群聊支持度落地时验证 +2. 模板卡片更新有 **5s 时窗限制**(enter_chat/template_card_event 后 5s 内)——审批卡片的「按钮点击后更新卡片状态」必须用时窗内 update_template_card 或直接发新卡片 +3. 社区量小(346 下载)且为个人项目——锁版本 + 冒烟测试对冲;协议本身简单(WS+JSON 帧),最坏情况自实现成本可控(远低于原 AES 回调模式) +4. 备选:WecomTeam/wecom-cli 官方系(wecomx-* crate 族,2026-08 活跃)但为 CLI 形态服务化集成不便;传统自建应用路线(wecom-crypto + wecom-rs)仅在智能机器人不满足时启用 + +## 五、对 df-im-gateway 实施的影响 + +### 5.1 规划修正 +1. **§四 四端对比表**:企微「事件接收」从「HTTP 回调(GET 验签+POST AES,需公网 URL)」修正为「WS 长连接(智能机器人形态,免公网回调)」;接入复杂度从「高」降为「中」 +2. **P4 任务描述**:接入路线改为智能机器人(wecom-aibot-rust-sdk),传统回调模式降级为 fallback +3. **三平台统一形态**:三家全是「WS 长连接出站 + JSON/protobuf 帧 + 卡片流式 + 按钮回调」——df-im-gateway 的 adapter trait 可以高度统一(InboundEvent/OutboundAction 映射逻辑三平台趋同),印证 P0 的 ④ 预留项价值 + +### 5.2 统一技术底座 +| 关注点 | 三家公共形态 | 网关侧统一处理 | +|---|---|---| +| 连接 | WS 出站长连接 + 心跳 + 自动重连 | adapter trait 的 connect/health_check 统一签名 | +| 收消息 | 事件/消息帧 → text content | InboundEvent::Message{chat_id, user, content} | +| 流式回复 | stream_id + finish 标志(企微)/ CardKit sequence(飞书)/ AI 卡片流式(钉钉) | OutboundAction::StreamChunk{conv_id, delta, finished} → 各 adapter 自映射 | +| 审批按钮 | 卡片按钮点击事件 | InboundEvent::CardAction{chat_id, action_id, value} → TunnelCommand::Approve | +| 媒体 | 下载码/aeskey(企微钉钉)、message resource(飞书) | 二期再做,一期纯文本 | + +### 5.3 风险清单(落地时冒烟) +| # | 风险 | 对冲 | +|---|---|---| +| R1 | 飞书 card.action.trigger 长连接下 typed 注册口未编译验证 | P2 开工首个冒烟项(examples/card_action_handler.rs 已存档) | +| R2 | 企微智能机器人群聊支持度/可见范围未确认 | P4 开工前管理后台确认;不满足则回退传统自建应用回调路线 | +| R3 | 三家 SDK 均非官方(个人维护) | 锁精确版本 + e2e 冒烟进 CI + 协议简单最坏自实现 | +| R4 | 钉钉 SDK handler ACK 语义与飞书 Response 帧不同 | adapter 层各自封装,不追求网关核心层统一 | +| R5 | 企微卡片 5s 更新时窗 | 审批结果用新卡片回复而非更新原卡片,规避时窗 | + +## 六、结论 + +三平台 Rust 接入**全部可行且路线统一**(WS 长连接 + SDK 现成 + 卡片流式 + 按钮审批),无阻塞性技术风险。规划文档的 P2/P3/P4 工作量估算维持 3-4 天/平台不变(复杂度下降但均摊了锁版本冒烟与群聊验证成本);企微从「最繁琐」降为与钉钉同级,**实施顺序可按 Web → 飞书 → 企微 → 钉钉 或按企业实际使用优先级自由排**。