From 5a84558d89136223beea22d4401634bbb6b2cdcc Mon Sep 17 00:00:00 2001 From: TotallyNot <44345987+TotallyNot@users.noreply.github.com> Date: Thu, 24 Apr 2025 13:32:02 +0200 Subject: [PATCH] feat(v2): initial commit --- .gitignore | 12 +- Cargo.lock | 1746 ++ Cargo.toml | 11 +- flake.lock | 114 + flake.nix | 32 + torn-api-codegen/Cargo.toml | 15 + torn-api-codegen/openapi.json | 14602 ++++++++++++++++ torn-api-codegen/overrides.toml | 0 torn-api-codegen/src/lib.rs | 2 + torn-api-codegen/src/model/enum.rs | 310 + torn-api-codegen/src/model/mod.rs | 189 + torn-api-codegen/src/model/newtype.rs | 144 + torn-api-codegen/src/model/object.rs | 448 + torn-api-codegen/src/model/parameter.rs | 431 + torn-api-codegen/src/model/path.rs | 482 + torn-api-codegen/src/model/scope.rs | 64 + torn-api-codegen/src/model/union.rs | 50 + torn-api-codegen/src/openapi/mod.rs | 4 + torn-api-codegen/src/openapi/parameter.rs | 40 + torn-api-codegen/src/openapi/path.rs | 81 + torn-api-codegen/src/openapi/schema.rs | 38 + torn-api-codegen/src/openapi/type.rs | 98 + torn-api/Cargo.toml | 66 +- torn-api/benches/deserialisation_benchmark.rs | 90 - torn-api/build.rs | 75 + torn-api/src/awc.rs | 22 - torn-api/src/common.rs | 172 - torn-api/src/de_util.rs | 245 - torn-api/src/executor.rs | 156 + torn-api/src/faction.rs | 278 - torn-api/src/into_owned.rs | 79 - torn-api/src/key.rs | 256 - torn-api/src/lib.rs | 582 +- torn-api/src/local.rs | 282 - torn-api/src/market.rs | 34 - torn-api/src/models.rs | 1 + torn-api/src/parameters.rs | 1 + torn-api/src/request/mod.rs | 104 + torn-api/src/request/models.rs | 1 + torn-api/src/reqwest.rs | 12 - torn-api/src/scopes.rs | 686 + torn-api/src/send.rs | 280 - torn-api/src/torn.rs | 417 - torn-api/src/user.rs | 828 - 44 files changed, 20091 insertions(+), 3489 deletions(-) create mode 100644 Cargo.lock create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 torn-api-codegen/Cargo.toml create mode 100644 torn-api-codegen/openapi.json create mode 100644 torn-api-codegen/overrides.toml create mode 100644 torn-api-codegen/src/lib.rs create mode 100644 torn-api-codegen/src/model/enum.rs create mode 100644 torn-api-codegen/src/model/mod.rs create mode 100644 torn-api-codegen/src/model/newtype.rs create mode 100644 torn-api-codegen/src/model/object.rs create mode 100644 torn-api-codegen/src/model/parameter.rs create mode 100644 torn-api-codegen/src/model/path.rs create mode 100644 torn-api-codegen/src/model/scope.rs create mode 100644 torn-api-codegen/src/model/union.rs create mode 100644 torn-api-codegen/src/openapi/mod.rs create mode 100644 torn-api-codegen/src/openapi/parameter.rs create mode 100644 torn-api-codegen/src/openapi/path.rs create mode 100644 torn-api-codegen/src/openapi/schema.rs create mode 100644 torn-api-codegen/src/openapi/type.rs delete mode 100644 torn-api/benches/deserialisation_benchmark.rs create mode 100644 torn-api/build.rs delete mode 100644 torn-api/src/awc.rs delete mode 100644 torn-api/src/common.rs delete mode 100644 torn-api/src/de_util.rs create mode 100644 torn-api/src/executor.rs delete mode 100644 torn-api/src/faction.rs delete mode 100644 torn-api/src/into_owned.rs delete mode 100644 torn-api/src/key.rs delete mode 100644 torn-api/src/local.rs delete mode 100644 torn-api/src/market.rs create mode 100644 torn-api/src/models.rs create mode 100644 torn-api/src/parameters.rs create mode 100644 torn-api/src/request/mod.rs create mode 100644 torn-api/src/request/models.rs delete mode 100644 torn-api/src/reqwest.rs create mode 100644 torn-api/src/scopes.rs delete mode 100644 torn-api/src/send.rs delete mode 100644 torn-api/src/torn.rs delete mode 100644 torn-api/src/user.rs diff --git a/.gitignore b/.gitignore index 4bcb2ce..f1c0c10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,12 @@ +# rust /target -/Cargo.lock -.env + +# direnv +.envrc +.direnv + +# vim +*.swp + +# mac os .DS_Store diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..eceb0f2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1746 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "async-compression" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" +dependencies = [ + "brotli", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bon" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced38439e7a86a4761f7f7d5ded5ff009135939ecb464a24452eaa4c1696af7d" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce61d2d3844c6b8d31b2353d9f66cf5e632b3e9549583fe3cac2f4f6136725e" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +dependencies = [ + "bytes", + "getrandom 0.3.2", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "async-compression", + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustls" +version = "0.23.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "torn-api" +version = "1.0.0" +dependencies = [ + "bon", + "bytes", + "http", + "prettyplease", + "proc-macro2", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "syn", + "thiserror", + "tokio", + "torn-api-codegen", +] + +[[package]] +name = "torn-api-codegen" +version = "0.1.0" +dependencies = [ + "heck", + "indexmap", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 085cd49..5893664 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,12 @@ [workspace] resolver = "2" -members = [ "torn-api-macros", "torn-api", "torn-key-pool" ] +members = ["torn-api", "torn-api-codegen"] + +[workspace.dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1" } +syn = { version = "2" } +proc-macro2 = { version = "1" } + +[profile.dev.package.torn-api-codegen] +opt-level = 3 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..3ea16a5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,114 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": "nixpkgs", + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1744618085, + "narHash": "sha256-+VdhZsIiIDtyOL88c4U/Os1PsCMLOCyScIeWL4hxJRM=", + "owner": "nix-community", + "repo": "fenix", + "rev": "a85d390a5607188dca2dbc39b5b37571651d69ce", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1744463964, + "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1744463964, + "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1744539868, + "narHash": "sha256-NPUnfDAwLD69aKetxjC7lV5ysrvs1IKC0Sy4Zai10Mw=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "8365cf853e791c93fa8bc924f031f11949bb1a3c", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..97b9197 --- /dev/null +++ b/flake.nix @@ -0,0 +1,32 @@ +{ + description = "A very basic flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + fenix.url = "github:nix-community/fenix"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + nixpkgs, + fenix, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; }; + toolchain = fenix.packages.${system}.stable.toolchain; + in + { + devShells.default = pkgs.mkShell { + packages = [ + toolchain + ]; + }; + } + + ); +} diff --git a/torn-api-codegen/Cargo.toml b/torn-api-codegen/Cargo.toml new file mode 100644 index 0000000..caceb6f --- /dev/null +++ b/torn-api-codegen/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "torn-api-codegen" +authors = ["Pyrit [2111649]"] +version = "0.1.0" +edition = "2024" +description = "Contains the v2 torn API model descriptions and codegen for the bindings" + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +heck = "0.5" +indexmap = { version = "2.9", features = ["serde"] } +quote = "1" +proc-macro2 = { workspace = true } +syn = { workspace = true } diff --git a/torn-api-codegen/openapi.json b/torn-api-codegen/openapi.json new file mode 100644 index 0000000..289075b --- /dev/null +++ b/torn-api-codegen/openapi.json @@ -0,0 +1,14602 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Torn API", + "description": "\n * The development of Torn's API v2 is still ongoing.\n * If selections remain unaltered, they will default to the API v1 version.\n * Unlike API v1, API v2 accepts both selections and IDs as path and query parameters.\n * If any discrepancies or errors are found, please submit a [bug report](https://www.torn.com/forums.php#/p=forums&f=19&b=0&a=0) on the Torn Forums.", + "version": "1.1.0" + }, + "servers": [ + { + "url": "https://api.torn.com/v2", + "description": "This is the base URL" + } + ], + "paths": { + "/user/attacks": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your detailed attacks", + "description": "Requires limited access key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionAttacksResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/attacksfull": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your simplified attacks", + "description": "Requires limited access key.
Returns up to 1,000 rows.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit1000" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionAttacksFullResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/bounties": { + "get": { + "tags": [ + "User" + ], + "summary": "Get bounties placed on you", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserBountiesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/{id}/bounties": { + "get": { + "tags": [ + "User" + ], + "summary": "Get bounties placed on a specific user", + "description": "Requires public access key.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User id", + "required": true, + "schema": { + "$ref": "#/components/schemas/UserId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserBountiesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/calendar": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your competition's event start time", + "description": "Requires minimal access key.
Only available to yourself.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserCalendarResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/{crimeId}/crimes": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your crime statistics", + "description": "Requires minimal access key.
Return the details and statistics about for a specific crime.", + "parameters": [ + { + "name": "crimeId", + "in": "path", + "description": "Crime id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserCrimesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/enlistedcars": { + "get": { + "tags": [ + "User" + ], + "summary": "Get user enlisted cars", + "description": "Requires minimal access key.
Returns a list of all user enlisted cars.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserEnlistedCarsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/factionbalance": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your current faction balance", + "description": "Requires limited access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserFactionBalanceResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/forumfeed": { + "get": { + "tags": [ + "User" + ], + "summary": "Get updates on your threads and posts", + "description": "Requires minimal access key.
This selection returns data visible in 'Feed' section on forum page. Feed is sorted by timestamp descending. Only a maximum of 100 rows are returned.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserForumFeedResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/forumfriends": { + "get": { + "tags": [ + "User" + ], + "summary": "Get updates on your friends' activity", + "description": "Requires minimal access key.
This selection returns data visible in 'Friends' section on forum page. Feed is sorted by timestamp descending. Only a maximum of 100 rows are returned.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserForumFriendsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/forumposts": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your posts", + "description": "Requires public access key.
Returns 20 posts per page.", + "parameters": [ + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + }, + { + "$ref": "#/components/parameters/ApiLimit20" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserForumPostsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/{id}/forumposts": { + "get": { + "tags": [ + "User" + ], + "summary": "Get posts for a specific player", + "description": "Requires public access key.
Returns 20 posts per page for a specific player.", + "parameters": [ + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + }, + { + "name": "id", + "in": "path", + "description": "User id", + "required": true, + "schema": { + "$ref": "#/components/schemas/UserId" + } + }, + { + "$ref": "#/components/parameters/ApiLimit20" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserForumPostsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/forumsubscribedthreads": { + "get": { + "tags": [ + "User" + ], + "summary": "Get updates on threads you subscribed to", + "description": "Requires minimal access key.
This selection returns data visible in 'Subscribed Threads' section on forum page. Threads are sorted in the same way as on site.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserForumSubscribedThreadsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/forumthreads": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your threads", + "description": "Requires public access key.
Returns 100 threads per page. The field 'new_posts' is also available, indicating the amount of unread posts with a Minimum API key (or higher).", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100Default20" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserForumThreadsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/{id}/forumthreads": { + "get": { + "tags": [ + "User" + ], + "summary": "Get threads for a specific player", + "description": "Requires public access key.
Returns 100 threads per page for a specific player. When requesting data for the key owner, a field 'new_posts' is also available, indicating the amount of unread posts. Minimum API key is required for that.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User id", + "required": true, + "schema": { + "$ref": "#/components/schemas/UserId" + } + }, + { + "$ref": "#/components/parameters/ApiLimit100Default20" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserForumThreadsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/hof": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your hall of fame rankings", + "description": "Requires public access key.
When requesting selection with Limited, Full or Custom key, battle_stats selection will be populated.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserHofResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/{id}/hof": { + "get": { + "tags": [ + "User" + ], + "summary": "Get hall of fame rankings for a specific player", + "description": "Requires public access key.
The battle_stats selection will be populated only when requesting selection with Limited, Full or Custom key and for yourself.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User id", + "required": true, + "schema": { + "$ref": "#/components/schemas/UserId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserHofResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/itemmarket": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your item market listings for a specific item", + "description": "Requires limited access key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiOffset" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemMarketResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/jobranks": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your starter job positions", + "description": "Requires minimal access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserJobRanksResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/list": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your friends, enemies or targets list", + "description": "Requires limited access key.
", + "parameters": [ + { + "name": "cat", + "in": "query", + "description": "Select list type", + "required": true, + "schema": { + "$ref": "#/components/schemas/UserListEnum" + } + }, + { + "$ref": "#/components/parameters/ApiLimit50" + }, + { + "$ref": "#/components/parameters/ApiOffsetNoDefault" + }, + { + "$ref": "#/components/parameters/ApiSortAsc" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserListResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/organizedcrime": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your current ongoing organized crime", + "description": "Requires minimal access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserOrganizedCrimeResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/personalstats": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your personal stats", + "description": "Requires public access key.
\n * UserPersonalStatsFull is returned only when this selection is requested with Limited, Full or Custom key access key.\n * UserPersonalStatsFullPublic is returned when the requested category is 'all'.\n * UserPersonalStatsPopular is returned when the requested category is 'popular'. Please try to use UserPersonalStatsPopular over UserPersonalStatsFullPublic wherever possible in order to reduce the server load.\n * Otherwise, UserPersonalStatsCategory is returned for the matched category.\n * It's possible to request specific stats via 'stat' parameter. In this case the response will vary depending on the stats requested. Private stats are still available only to the key owner (with Limited or higher key).\n * Additionally, historical stats can also be fetched via 'stat' query parameter, but 'timestamp' parameter must be provided as well. It's only possible to pass up to 10 historical stats at once (the rest is trimmed). When requesting historical stats the response will be of type UserPersonalStatsHistoric.", + "parameters": [ + { + "name": "cat", + "in": "query", + "description": "Stats category. Required unless requesting specific stats via 'stat' query parameter", + "required": false, + "schema": { + "$ref": "#/components/schemas/PersonalStatsCategoryEnum" + } + }, + { + "name": "stat", + "in": "query", + "description": "Stat names (10 maximum). Used to fetch historical stat values", + "required": false, + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PersonalStatsStatName" + } + } + }, + { + "name": "timestamp", + "in": "query", + "description": "Returns stats until this timestamp (converted to nearest date).", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserPersonalStatsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/{id}/personalstats": { + "get": { + "tags": [ + "User" + ], + "summary": "Get a player's personal stats", + "description": "Requires public access key.
\n * UserPersonalStatsFull is returned only when this selection is requested for the key owner with Limited, Full or Custom key.\n * UserPersonalStatsFullPublic is returned when the requested category is 'all'.\n * UserPersonalStatsPopular is returned when the requested category is 'popular'. Please try to use UserPersonalStatsPopular over UserPersonalStatsFullPublic wherever possible in order to reduce the server load.\n * Otherwise, UserPersonalStatsCategory is returned for the matched category.\n * It's possible to request specific stats via 'stat' parameter. In this case the response will vary depending on the stats requested. Private stats are still available only to the key owner (with Limited or higher key).\n * Additionally, historical stats can also be fetched via 'stat' query parameter, but 'timestamp' parameter must be provided as well. It's only possible to pass up to 10 historical stats at once (the rest is trimmed). When requesting historical stats the response will be of type UserPersonalStatsHistoric.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User id", + "required": true, + "schema": { + "$ref": "#/components/schemas/UserId" + } + }, + { + "name": "cat", + "in": "query", + "description": "", + "required": false, + "schema": { + "$ref": "#/components/schemas/PersonalStatsCategoryEnum" + } + }, + { + "name": "stat", + "in": "query", + "description": "Stat names (10 maximum). Used to fetch historical stat values", + "required": false, + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PersonalStatsStatName" + } + } + }, + { + "name": "timestamp", + "in": "query", + "description": "Returns stats until this timestamp (converted to nearest date).", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserPersonalStatsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/races": { + "get": { + "tags": [ + "User" + ], + "summary": "Get user races", + "description": "Requires minimal access key.
Returns a list of user races, ordered by race start timestamp.", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100Default20" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "name": "cat", + "in": "query", + "description": "Category of races returned", + "required": false, + "schema": { + "type": "string", + "default": "custom", + "enum": [ + "official", + "custom" + ] + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserRacesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/revives": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your detailed revives", + "description": "Requires limited access key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100Default20" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RevivesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/revivesFull": { + "get": { + "tags": [ + "User" + ], + "summary": "Get your simplified revives", + "description": "Requires limited access key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit1000Default20" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RevivesFullResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/lookup": { + "get": { + "tags": [ + "User" + ], + "summary": "Get all available user selections", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserLookupResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user/timestamp": { + "get": { + "tags": [ + "User" + ], + "summary": "Get current server time", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimestampResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/user": { + "get": { + "tags": [ + "User" + ], + "summary": "Get any User selection", + "description": "Key access level depends on the required selections.
Choose one or more selections (comma separated).", + "parameters": [ + { + "name": "selections", + "in": "query", + "description": "Selection names", + "required": false, + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserSelectionName" + } + } + }, + { + "name": "id", + "in": "query", + "description": "selection id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiLimit" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "name": "cat", + "in": "query", + "description": "Selection category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "stat", + "in": "query", + "description": "Selection stat", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiStripTags" + }, + { + "$ref": "#/components/parameters/ApiOffsetNoDefault" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/UserCrimesResponse" + }, + { + "$ref": "#/components/schemas/UserRacesResponse" + }, + { + "$ref": "#/components/schemas/UserEnlistedCarsResponse" + }, + { + "$ref": "#/components/schemas/UserForumPostsResponse" + }, + { + "$ref": "#/components/schemas/UserForumThreadsResponse" + }, + { + "$ref": "#/components/schemas/UserForumSubscribedThreadsResponse" + }, + { + "$ref": "#/components/schemas/UserForumFeedResponse" + }, + { + "$ref": "#/components/schemas/UserForumFriendsResponse" + }, + { + "$ref": "#/components/schemas/UserHofResponse" + }, + { + "$ref": "#/components/schemas/UserCalendarResponse" + }, + { + "$ref": "#/components/schemas/UserBountiesResponse" + }, + { + "$ref": "#/components/schemas/UserJobRanksResponse" + }, + { + "$ref": "#/components/schemas/UserItemMarketResponse" + }, + { + "$ref": "#/components/schemas/UserListResponse" + }, + { + "$ref": "#/components/schemas/UserPersonalStatsResponse" + }, + { + "$ref": "#/components/schemas/UserOrganizedCrimeResponse" + }, + { + "$ref": "#/components/schemas/FactionAttacksResponse" + }, + { + "$ref": "#/components/schemas/FactionAttacksFullResponse" + }, + { + "$ref": "#/components/schemas/UserLookupResponse" + }, + { + "$ref": "#/components/schemas/TimestampResponse" + } + ] + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/applications": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's applications", + "description": "Requires minimal access key with faction API access permissions.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionApplicationsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/attacks": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's detailed attacks", + "description": "Requires limited access key with faction API access permissions.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionAttacksResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/attacksfull": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's simplified attacks", + "description": "Requires limited access key with faction API access permissions.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit1000" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionAttacksFullResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/balance": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's & member's balance details", + "description": "Requires limited access key with faction API access permissions.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionBalanceResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/basic": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's basic details", + "description": "Requires public access key.
The 'is_enlisted' value will be populated if you have API faction permissions (with custom, limited or full access keys), otherwise it will be set as null.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionBasicResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/basic": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a faction's basic details", + "description": "Requires public access key.
The 'is_enlisted' value will be populated if you're requesting data for your faction and have faction permissions (with custom, limited or full access keys), otherwise it will be set as null.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Faction id", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionBasicResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/chain": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's current chain", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionOngoingChainResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/chain": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a faction's current chain", + "description": "Requires public access key.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Faction id", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionOngoingChainResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/chains": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a list of your faction's completed chains", + "description": "Requires public access key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionChainsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/chains": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a list of a faction's completed chains", + "description": "Requires public access key.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Faction id", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionId" + } + }, + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionChainsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/chainreport": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's latest chain report", + "description": "Requires public access key.
This includes currently ongoing chains.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionChainReportResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{chainId}/chainreport": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a chain report", + "description": "Requires public access key.
Chain reports for ongoing chains are available only for your own faction.", + "parameters": [ + { + "name": "chainId", + "in": "path", + "description": "Chain id", + "required": true, + "schema": { + "$ref": "#/components/schemas/ChainId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionChainReportResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/contributors": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's challenge contributors", + "description": "Requires limiteed access key with faction API access permissions.
", + "parameters": [ + { + "name": "stat", + "in": "query", + "description": "Get contributors for this field.", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionStatEnum" + } + }, + { + "name": "cat", + "in": "query", + "description": "By default, this selection will return only current faction's member contributions, and the option 'all' will return all contributors.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "all", + "current" + ] + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionContributorsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Unstable" + } + }, + "/faction/crimes": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's organized crimes", + "description": "Requires minimal access key with faction API access permissions.
It's possible to get older entries either by timestamp range (from, to) or with offset.", + "parameters": [ + { + "name": "cat", + "in": "query", + "description": "Category of organized crimes returned. Category 'available' includes both 'recruiting' & 'planning', and category 'completed' includes both 'successful' & 'failure'
Default category is 'all'", + "required": false, + "schema": { + "type": "string", + "enum": [ + "all", + "recruiting", + "planning", + "failure", + "successful", + "expired", + "available", + "completed" + ] + } + }, + { + "$ref": "#/components/parameters/ApiOffset" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionCrimesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/crime": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a specific organized crime", + "description": "Requires minimal access key with faction API access permissions.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Crime id", + "required": true, + "schema": { + "$ref": "#/components/schemas/ItemId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionCrimeResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/hof": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's hall of fame rankings.", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionHofResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/hof": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a faction's hall of fame rankings.", + "description": "Requires public access key.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Faction id", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionHofResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/members": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a list of your faction's members", + "description": "Requires public access key.
The 'revive_setting' value will be populated (not Unknown) if you have faction permissions (with custom, limited or full access keys), otherwise it will be set as 'Unknown'.", + "parameters": [ + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionMembersResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/members": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a list of a faction's members", + "description": "Requires public access key.
The 'revive_setting' value will be populated (not Unknown) if you're requesting data for your own faction and have faction permissions (with custom, limited or full access keys), otherwise it will be set as 'Unknown'.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Faction id", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionId" + } + }, + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionMembersResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/news": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's news details", + "description": "Requires minimal access key with faction API access permissions.
It is possible to pass up to 10 categories at the time (comma separated). Categories 'attack', 'depositFunds' and 'giveFunds' are only available with 'Custom', 'Limited' or 'Full' access keys.", + "parameters": [ + { + "$ref": "#/components/parameters/ApiStripTagsFalse" + }, + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "name": "cat", + "in": "query", + "description": "News category type", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionNewsCategory" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionNewsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/rankedwars": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get ranked wars list", + "description": "Requires public access key.
When category 'all' is chosen, you can use 'from', 'to' & 'sort' query parameters.
When category 'ongoing' is chosen, all currently active ranked wars are returned.
When no category is chosen, this selection will return ranked war history of your own faction (if any).", + "parameters": [ + { + "name": "cat", + "in": "query", + "description": "Stats category. Required unless requesting specific stats via 'stat' query parameter", + "required": false, + "schema": { + "$ref": "#/components/schemas/FactionRankedWarsCategoryEnum" + } + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionRankedWarResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/rankedwars": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a faction's ranked wars history", + "description": "Requires public access key.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Faction id", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionRankedWarResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/rankedwarreport": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get ranked war details", + "description": "Requires public access key.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Ranked war id", + "required": true, + "schema": { + "$ref": "#/components/schemas/RankedWarId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionRankedWarReportResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/revives": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's detailed revives", + "description": "Requires limited access key with faction API access permissions.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RevivesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/revivesFull": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's simplified revives", + "description": "Requires limited access key with faction API access permissions.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit1000" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RevivesFullResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/stats": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's challenges stats", + "description": "Requires minimal access key with faction API access permissions.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionStatsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Unstable" + } + }, + "/faction/upgrades": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's upgrades", + "description": "Requires minimal access key with faction API access permissions.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionUpgradesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Unstable" + } + }, + "/faction/wars": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get your faction's wars & pacts details", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionWarsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/{id}/wars": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get a faction's wars & pacts details", + "description": "Requires public access key.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Faction id", + "required": true, + "schema": { + "$ref": "#/components/schemas/FactionId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionWarsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/lookup": { + "get": { + "tags": [ + "Faction" + ], + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FactionLookupResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction/timestamp": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get current server time", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimestampResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/faction": { + "get": { + "tags": [ + "Faction" + ], + "summary": "Get any Faction selection", + "description": "Key access level depends on the required selections.
Choose one or more selections (comma separated).", + "parameters": [ + { + "name": "selections", + "in": "query", + "description": "Selection names", + "required": false, + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionSelectionName" + } + } + }, + { + "name": "id", + "in": "query", + "description": "selection id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiLimit" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "name": "cat", + "in": "query", + "description": "Selection category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "stat", + "in": "query", + "description": "Stat category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiStripTags" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiOffsetNoDefault" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/FactionHofResponse" + }, + { + "$ref": "#/components/schemas/FactionMembersResponse" + }, + { + "$ref": "#/components/schemas/FactionBasicResponse" + }, + { + "$ref": "#/components/schemas/FactionWarsResponse" + }, + { + "$ref": "#/components/schemas/FactionNewsResponse" + }, + { + "$ref": "#/components/schemas/FactionAttacksResponse" + }, + { + "$ref": "#/components/schemas/FactionAttacksFullResponse" + }, + { + "$ref": "#/components/schemas/FactionApplicationsResponse" + }, + { + "$ref": "#/components/schemas/FactionOngoingChainResponse" + }, + { + "$ref": "#/components/schemas/FactionChainsResponse" + }, + { + "$ref": "#/components/schemas/FactionChainReportResponse" + }, + { + "$ref": "#/components/schemas/FactionCrimesResponse" + }, + { + "$ref": "#/components/schemas/FactionCrimeResponse" + }, + { + "$ref": "#/components/schemas/FactionRankedWarReportResponse" + }, + { + "$ref": "#/components/schemas/FactionUpgradesResponse" + }, + { + "$ref": "#/components/schemas/FactionStatsResponse" + }, + { + "$ref": "#/components/schemas/FactionContributorsResponse" + }, + { + "$ref": "#/components/schemas/FactionLookupResponse" + }, + { + "$ref": "#/components/schemas/TimestampResponse" + } + ] + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/forum/categories": { + "get": { + "tags": [ + "Forum" + ], + "summary": "Get publicly available forum categories", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForumCategoriesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/forum/{threadId}/posts": { + "get": { + "tags": [ + "Forum" + ], + "summary": "Get specific forum thread posts", + "description": "Requires public access key.
Returns 20 posts per page for a specific thread.", + "parameters": [ + { + "$ref": "#/components/parameters/ApiOffsetNoDefault" + }, + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + }, + { + "name": "threadId", + "in": "path", + "description": "Thread id", + "required": true, + "schema": { + "$ref": "#/components/schemas/ForumThreadId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForumPostsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/forum/{threadId}/thread": { + "get": { + "tags": [ + "Forum" + ], + "summary": "Get specific thread details", + "description": "Requires public access key.
Contains details of a thread including topic content and poll (if any).", + "parameters": [ + { + "name": "threadId", + "in": "path", + "description": "Thread id", + "required": true, + "schema": { + "$ref": "#/components/schemas/ForumThreadId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForumThreadResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/forum/threads": { + "get": { + "tags": [ + "Forum" + ], + "summary": "Get threads across all forum categories", + "description": "Requires public access key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForumThreadsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/forum/{categoryIds}/threads": { + "get": { + "tags": [ + "Forum" + ], + "summary": "Get threads for specific public forum category or categories", + "description": "Requires public access key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "name": "categoryIds", + "in": "path", + "description": "Category id or a list of category ids (comma separated)", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForumThreadsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/forum/lookup": { + "get": { + "tags": [ + "Forum" + ], + "summary": "Get all available forum selections", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForumLookupResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/forum/timestamp": { + "get": { + "tags": [ + "Forum" + ], + "summary": "Get current server time", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimestampResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/forum": { + "get": { + "tags": [ + "Forum" + ], + "summary": "Get any Forum selection", + "description": "Requires public access key.
Choose one or more selections (comma separated).", + "parameters": [ + { + "name": "selections", + "in": "query", + "description": "Selection names", + "required": false, + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumSelectionName" + } + } + }, + { + "name": "id", + "in": "query", + "description": "selection id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiStripTags" + }, + { + "$ref": "#/components/parameters/ApiLimit" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiOffsetNoDefault" + }, + { + "name": "cat", + "in": "query", + "description": "Selection category", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ForumCategoriesResponse" + }, + { + "$ref": "#/components/schemas/ForumThreadsResponse" + }, + { + "$ref": "#/components/schemas/ForumThreadResponse" + }, + { + "$ref": "#/components/schemas/ForumPostsResponse" + }, + { + "$ref": "#/components/schemas/ForumLookupResponse" + }, + { + "$ref": "#/components/schemas/TimestampResponse" + } + ] + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/market/{id}/itemmarket": { + "get": { + "tags": [ + "Market" + ], + "summary": "Get item market listings", + "description": "Requires public access key.
", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Item id", + "required": true, + "schema": { + "$ref": "#/components/schemas/ItemId" + } + }, + { + "name": "bonus", + "in": "query", + "description": "Used to filter weapons with a specific bonus.", + "required": false, + "schema": { + "$ref": "#/components/schemas/WeaponBonusEnum" + } + }, + { + "$ref": "#/components/parameters/ApiOffset" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MarketItemMarketResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Unstable" + } + }, + "/market/lookup": { + "get": { + "tags": [ + "Market" + ], + "summary": "Get all available market selections", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MarketLookupResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/market/timestamp": { + "get": { + "tags": [ + "Market" + ], + "summary": "Get current server time", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimestampResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/market": { + "get": { + "tags": [ + "Market" + ], + "summary": "Get any Market selection", + "description": "Requires public access key.
Choose one or more selections (comma separated).", + "parameters": [ + { + "name": "selections", + "in": "query", + "description": "Selection names", + "required": false, + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MarketSelectionName" + } + } + }, + { + "name": "id", + "in": "query", + "description": "selection id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "bonus", + "in": "query", + "description": "Used to filter weapons with a specific bonus", + "required": false, + "schema": { + "$ref": "#/components/schemas/WeaponBonusEnum" + } + }, + { + "name": "cat", + "in": "query", + "description": "Selection category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Direction to sort rows in", + "required": false, + "schema": { + "type": "string", + "enum": [ + "DESC", + "ASC" + ] + } + }, + { + "$ref": "#/components/parameters/ApiOffsetNoDefault" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/MarketItemMarketResponse" + }, + { + "$ref": "#/components/schemas/MarketLookupResponse" + }, + { + "$ref": "#/components/schemas/TimestampResponse" + } + ] + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing/cars": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get cars and their racing stats", + "description": "Requires public access key.
Returns the stat details about racing cars.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RacingCarsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing/carupgrades": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get all possible car upgrades", + "description": "Requires public access key.
Returns the details about all possible car upgrades.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RacingCarUpgradesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing/races": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get races", + "description": "Requires public access key.
Returns a list of races, ordered by race start timestamp.", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiSortDesc" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "name": "cat", + "in": "query", + "description": "Category of races returned", + "required": false, + "schema": { + "type": "string", + "default": "custom", + "enum": [ + "official", + "custom" + ] + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RacingRacesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing/{raceId}/race": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get specific race details", + "description": "Requires public access key.
Returns the details of a race.", + "parameters": [ + { + "name": "raceId", + "in": "path", + "description": "Race id", + "required": true, + "schema": { + "$ref": "#/components/schemas/RaceId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RacingRaceDetailsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing/{trackId}/records": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get track records", + "description": "Requires public access key.
Returns a list of 10 best lap records for the chosen track and car class. Results are cached globally 1 hour.", + "parameters": [ + { + "name": "trackId", + "in": "path", + "description": "Track id", + "required": true, + "schema": { + "$ref": "#/components/schemas/RaceTrackId" + } + }, + { + "name": "cat", + "in": "query", + "description": "Car class", + "required": true, + "schema": { + "$ref": "#/components/schemas/RaceClassEnum" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RacingTrackRecordsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing/tracks": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get race tracks and descriptions", + "description": "Requires public access key.
Returns the details about racing tracks.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RacingTracksResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing/lookup": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get all available racing selections", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RacingLookupResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing/timestamp": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get current server time", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimestampResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/racing": { + "get": { + "tags": [ + "Racing" + ], + "summary": "Get any Racing selection", + "description": "Requires public access key.
Choose one or more selections (comma separated).", + "parameters": [ + { + "name": "selections", + "in": "query", + "description": "Selection names", + "required": false, + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RacingSelectionName" + } + } + }, + { + "name": "id", + "in": "query", + "description": "selection id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiLimit" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiTo" + }, + { + "$ref": "#/components/parameters/ApiFrom" + }, + { + "name": "cat", + "in": "query", + "description": "Selection category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiOffsetNoDefault" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/RacingRacesResponse" + }, + { + "$ref": "#/components/schemas/RacingTrackRecordsResponse" + }, + { + "$ref": "#/components/schemas/RacingRaceDetailsResponse" + }, + { + "$ref": "#/components/schemas/RacingCarsResponse" + }, + { + "$ref": "#/components/schemas/RacingTracksResponse" + }, + { + "$ref": "#/components/schemas/RacingCarUpgradesResponse" + }, + { + "$ref": "#/components/schemas/RacingLookupResponse" + }, + { + "$ref": "#/components/schemas/TimestampResponse" + } + ] + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/attacklog": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get attack log details", + "description": "Requires public key.
", + "parameters": [ + { + "name": "log", + "in": "query", + "description": "Code of the attack log. E.g. d51ad4fe6be884b309b142e2d1d4f807", + "required": true, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiOffset" + }, + { + "$ref": "#/components/parameters/ApiSort" + }, + { + "$ref": "#/components/parameters/ApiStripTagsTrue" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AttackLogResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/bounties": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get bounties", + "description": "Requires public key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiOffset" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornBountiesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/calendar": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get calendar information", + "description": "Requires public access key.
Get the details about competitions & events in the running year.", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornCalendarResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/crimes": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get crimes information", + "description": "Requires public access key.
Return the details about released crimes.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornCrimesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/factionhof": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get faction hall of fame positions for a specific category", + "description": "Requires public access key.
", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiOffset" + }, + { + "name": "cat", + "in": "query", + "description": "Leaderboards category", + "required": true, + "schema": { + "$ref": "#/components/schemas/TornFactionHofCategory" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornFactionHofResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/factiontree": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get full faction tree", + "description": "Requires public access key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornFactionTreeResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Unstable" + } + }, + "/torn/hof": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get player hall of fame positions for a specific category", + "description": "Requires public key.", + "parameters": [ + { + "$ref": "#/components/parameters/ApiLimit100" + }, + { + "$ref": "#/components/parameters/ApiOffset" + }, + { + "name": "cat", + "in": "query", + "description": "Leaderboards category", + "required": true, + "schema": { + "$ref": "#/components/schemas/TornHofCategory" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornHofResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/itemammo": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get information about ammo", + "description": "Requires public key.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornItemAmmoResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/itemmods": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get information about weapon upgrades", + "description": "Requires public key.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornItemModsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/items": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get information about items", + "description": "Requires public key.
Default category is 'All'.
Details are not populated when requesting the category 'All'.", + "parameters": [ + { + "name": "cat", + "in": "query", + "description": "Item category type", + "required": false, + "schema": { + "$ref": "#/components/schemas/TornItemCategory" + } + }, + { + "$ref": "#/components/parameters/ApiSortAsc" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornItemsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/{ids}/items": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get information about items", + "description": "Requires public key.
Details are always populated when available.", + "parameters": [ + { + "name": "ids", + "in": "path", + "description": "Item id or a list of item ids (comma separated)", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiSortAsc" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornItemsResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/logcategories": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get available log categories", + "description": "Requires public key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornLogCategoriesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/logtypes": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get all available log ids", + "description": "Requires public key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornLogTypesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/{logCategoryId}/logtypes": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get available log ids for a specific log category", + "description": "Requires public key.
", + "parameters": [ + { + "name": "logCategoryId", + "in": "path", + "description": "Log category id", + "required": true, + "schema": { + "$ref": "#/components/schemas/LogCategoryId" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornLogTypesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/{crimeId}/subcrimes": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get Subcrimes information", + "description": "Requires public access key.
Return the details about possible actions for a specific crime.", + "parameters": [ + { + "name": "crimeId", + "in": "path", + "description": "Crime id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornSubcrimesResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/lookup": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get all available torn selections", + "description": "Requires public key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TornLookupResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn/timestamp": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get current server time", + "description": "Requires public key.
", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimestampResponse" + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + }, + "/torn": { + "get": { + "tags": [ + "Torn" + ], + "summary": "Get any Torn selection", + "description": "Requires public access key.
Choose one or more selections (comma separated).", + "parameters": [ + { + "name": "selections", + "in": "query", + "description": "Selection names", + "required": false, + "style": "form", + "explode": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornSelectionName" + } + } + }, + { + "name": "id", + "in": "query", + "description": "selection id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/ApiStripTags" + }, + { + "$ref": "#/components/parameters/ApiLimit" + }, + { + "name": "to", + "in": "query", + "description": "Timestamp until when rows are returned", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "from", + "in": "query", + "description": "Timestamp after when rows are returned", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "cat", + "in": "query", + "description": "Selection category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Direction to sort rows in", + "required": false, + "schema": { + "type": "string", + "enum": [ + "DESC", + "ASC" + ] + } + }, + { + "$ref": "#/components/parameters/ApiOffsetNoDefault" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/TornSubcrimesResponse" + }, + { + "$ref": "#/components/schemas/TornCrimesResponse" + }, + { + "$ref": "#/components/schemas/TornCalendarResponse" + }, + { + "$ref": "#/components/schemas/TornHofResponse" + }, + { + "$ref": "#/components/schemas/TornFactionHofResponse" + }, + { + "$ref": "#/components/schemas/TornLogTypesResponse" + }, + { + "$ref": "#/components/schemas/TornLogCategoriesResponse" + }, + { + "$ref": "#/components/schemas/TornBountiesResponse" + }, + { + "$ref": "#/components/schemas/TornItemAmmoResponse" + }, + { + "$ref": "#/components/schemas/TornFactionTreeResponse" + }, + { + "$ref": "#/components/schemas/TornItemModsResponse" + }, + { + "$ref": "#/components/schemas/TornLookupResponse" + }, + { + "$ref": "#/components/schemas/TimestampResponse" + } + ] + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-stability": "Stable" + } + } + }, + "components": { + "schemas": { + "RaceClassEnum": { + "type": "string", + "enum": [ + "A", + "B", + "C", + "D", + "E" + ] + }, + "FactionStatEnum": { + "type": "string", + "enum": [ + "medicalitemsused", + "criminaloffences", + "organisedcrimerespect", + "organisedcrimemoney", + "organisedcrimesuccess", + "organisedcrimefail", + "attackswon", + "attackslost", + "attackschain", + "attacksleave", + "attacksmug", + "attackshosp", + "bestchain", + "busts", + "revives", + "jails", + "hosps", + "medicalitemrecovery", + "medicalcooldownused", + "gymtrains", + "gymstrength", + "gymspeed", + "gymdefense", + "gymdexterity", + "candyused", + "alcoholused", + "energydrinkused", + "drugsused", + "drugoverdoses", + "rehabs", + "caymaninterest", + "traveltimes", + "traveltime", + "hunting", + "attacksdamagehits", + "attacksdamage", + "hosptimegiven", + "hosptimereceived", + "attacksdamaging", + "attacksrunaway", + "highestterritories", + "territoryrespect" + ] + }, + "FactionBranchStateEnum": { + "type": "string", + "enum": [ + "war", + "peace" + ] + }, + "FactionOrganizedCrimePayoutType": { + "type": "string", + "enum": [ + "balance", + "wallet", + "inventory" + ] + }, + "FactionNewsCategory": { + "type": "string", + "enum": [ + "main", + "attack", + "armoryDeposit", + "armoryAction", + "territoryWar", + "rankedWar", + "territoryGain", + "chain", + "crime", + "membership", + "depositFunds", + "giveFunds" + ] + }, + "FactionRankEnum": { + "type": "string", + "enum": [ + "Unranked", + "Bronze", + "Silver", + "Gold", + "Platinum", + "Diamond" + ] + }, + "UserCrimeUniquesRewardAmmoEnum": { + "type": "string", + "enum": [ + "standard", + "special" + ] + }, + "RaceStatusEnum": { + "type": "string", + "enum": [ + "open", + "in_progress", + "finished" + ] + }, + "TornHofCategory": { + "type": "string", + "enum": [ + "level", + "busts", + "rank", + "traveltime", + "workstats", + "networth", + "revives", + "defends", + "offences", + "attacks", + "awards", + "racingwins", + "racingpoints", + "racingskill" + ] + }, + "TornFactionHofCategory": { + "type": "string", + "enum": [ + "respect", + "chains", + "rank" + ] + }, + "FactionAttackResult": { + "type": "string", + "enum": [ + "None", + "Attacked", + "Mugged", + "Hospitalized", + "Arrested", + "Looted", + "Lost", + "Stalemate", + "Assist", + "Escape", + "Timeout", + "Special", + "Bounty", + "Interrupted" + ] + }, + "RaceCarUpgradeCategory": { + "type": "string", + "enum": [ + "Aerodynamics", + "Brakes", + "Engine", + "Exhaust and Induction", + "Fuel", + "Safety", + "Suspension", + "Transmission", + "Weight Reduction", + "Wheels and Tyres" + ] + }, + "JobPositionArmyEnum": { + "type": "string", + "enum": [ + "Private", + "Corporal", + "Sergeant", + "Master Sergeant", + "Warrant Officer", + "Lieutenant", + "Major", + "Colonel", + "Brigadier", + "General" + ] + }, + "JobPositionGrocerEnum": { + "type": "string", + "enum": [ + "Bagboy", + "Price Labeler", + "Cashier", + "Food Delivery", + "Manager" + ] + }, + "WeaponBonusEnum": { + "type": "string", + "enum": [ + "Any", + "Double", + "Yellow", + "Orange", + "Red", + "Achilles", + "Assassinate", + "Backstab", + "Berserk", + "Bleed", + "Blindfire", + "Blindside", + "Bloodlust", + "Burn", + "Comeback", + "Conserve", + "Cripple", + "Crusher", + "Cupid", + "Deadeye", + "Deadly", + "Demoralize", + "Disarm", + "Double-edged", + "Double Tap", + "Emasculate", + "Empower", + "Eviscerate", + "Execute", + "Expose", + "Finale", + "Focus", + "Freeze", + "Frenzy", + "Fury", + "Grace", + "Hazardous", + "Home run", + "Irradiate", + "Lacerate", + "Motivation", + "Paralyze", + "Parry", + "Penetrate", + "Plunder", + "Poison", + "Powerful", + "Proficience", + "Puncture", + "Quicken", + "Rage", + "Revitalize", + "Roshambo", + "Shock", + "Sleep", + "Slow", + "Smash", + "Smurf", + "Specialist", + "Spray", + "Storage", + "Stricken", + "Stun", + "Suppress", + "Sure Shot", + "Throttle", + "Toxin", + "Warlord", + "Weaken", + "Wind-up", + "Wither" + ] + }, + "UserListEnum": { + "type": "string", + "enum": [ + "Friends", + "Enemies", + "Targets" + ] + }, + "JobPositionCasinoEnum": { + "type": "string", + "enum": [ + "Gaming Consultant", + "Marketing Manager", + "Revenue Manager", + "Casino Manager", + "Casino President" + ] + }, + "AttackFinishingHitEffect": { + "type": "string", + "enum": [ + "proficience", + "stricken", + "revitalize", + "warlord", + "plunder", + "irradiate" + ] + }, + "JobPositionMedicalEnum": { + "type": "string", + "enum": [ + "Medical Student", + "Houseman", + "Senior Houseman", + "GP", + "Consultant", + "Surgeon", + "Brain Surgeon" + ] + }, + "JobPositionLawEnum": { + "type": "string", + "enum": [ + "Law Student", + "Paralegal", + "Probate Lawyer", + "Trial Lawyer", + "Circuit Court Judge", + "Federal Judge" + ] + }, + "JobPositionEducationEnum": { + "type": "string", + "enum": [ + "Recess Supervisor", + "Substitute Teacher", + "Elementary Teacher", + "Secondary Teacher", + "Professor", + "Vice-Principal", + "Principal" + ] + }, + "RaceCarUpgradeSubCategory": { + "type": "string", + "enum": [ + "Engine Cooling", + "Front Diffuser", + "Rear Diffuser", + "Spoiler", + "Brake Accessory", + "Brake Control", + "Callipers", + "Discs", + "Brake Cooling", + "Fluid", + "Rear Control Arms", + "Springs", + "Upper Front Brace", + "Clutch", + "Differential", + "Flywheel", + "Gearbox", + "Shifting", + "Boot", + "Hood", + "Interior", + "Roof", + "Steering wheel", + "Strip out", + "Windows", + "Tyres", + "Wheels", + "Rear Bushes", + "Rear Brace", + "Lower Front Brace", + "Front Tie Rods", + "Front Bushes", + "Seat", + "Safety Accessory", + "Roll cage", + "Overalls", + "Helmet", + "Fire Extinguisher", + "Cut-off", + "Fuel", + "Manifold", + "Exhaust", + "Air Filter", + "Turbo", + "Pistons", + "Intercooler", + "Gasket", + "Fuel Pump", + "Engine Porting", + "Engine Cleaning", + "Computer", + "Camshaft", + "Pads" + ] + }, + "FactionApplicationStatusEnum": { + "type": "string", + "enum": [ + "accepted", + "declined", + "withdrawn" + ] + }, + "FactionRankedWarsCategoryEnum": { + "type": "string", + "enum": [ + "all", + "ongoing" + ] + }, + "FactionCrimeUserOutcome": { + "type": "string", + "enum": [ + "Successful", + "Failed", + "Jailed", + "Injured", + "Hospitalized" + ] + }, + "RaceId": { + "type": "integer", + "format": "int32" + }, + "RaceTrackId": { + "type": "integer", + "format": "int32" + }, + "RaceCarId": { + "type": "integer", + "format": "int32" + }, + "RaceCarUpgradeId": { + "type": "integer", + "format": "int32" + }, + "ItemId": { + "type": "integer", + "format": "int64" + }, + "ItemModId": { + "type": "integer", + "format": "int32" + }, + "AmmoId": { + "type": "integer", + "format": "int32" + }, + "RankedWarId": { + "type": "integer", + "format": "int32" + }, + "ItemUid": { + "type": "integer", + "format": "int64" + }, + "UserId": { + "type": "integer", + "format": "int32" + }, + "ReviveId": { + "type": "integer", + "format": "int32" + }, + "LogId": { + "type": "integer", + "format": "int32" + }, + "LogCategoryId": { + "type": "integer", + "format": "int32" + }, + "FactionId": { + "type": "integer", + "format": "int32" + }, + "FactionBranchId": { + "type": "integer", + "format": "int32" + }, + "FactionCrimeId": { + "type": "integer", + "format": "int64" + }, + "ChainId": { + "type": "integer", + "format": "int32" + }, + "AttackId": { + "type": "integer", + "format": "int32" + }, + "AttackCode": { + "type": "string" + }, + "ForumId": { + "type": "integer", + "format": "int32" + }, + "ForumThreadId": { + "type": "integer", + "format": "int32" + }, + "ForumPostId": { + "type": "integer", + "format": "int32" + }, + "RequestLinks": { + "required": [ + "next", + "prev" + ], + "properties": { + "next": { + "description": "Auto-generated link to get the next set of records.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "prev": { + "description": "Auto-generated link to get the prev set of records.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RequestMetadata": { + "required": [ + "links" + ], + "properties": { + "links": { + "$ref": "#/components/schemas/RequestLinks" + } + }, + "type": "object" + }, + "RequestMetadataWithLinks": { + "required": [ + "links" + ], + "properties": { + "links": { + "$ref": "#/components/schemas/RequestLinks" + } + }, + "type": "object" + }, + "ForumFeedTypeEnum": { + "description": "This represents the type of the activity. Values range from 1 to 8 where:\n * 1 = 'X posted on a thread',\n * 2 = 'X created a thread',\n * 3 = 'X liked your thread',\n * 4 = 'X disliked your thread',\n * 5 = 'X liked your post',\n * 6 = 'X disliked your post',\n * 7 = 'X quoted your post'.", + "type": "integer", + "format": "int32", + "enum": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + "ReviveSetting": { + "type": "string", + "enum": [ + "Everyone", + "Friends & faction", + "No one", + "Unknown" + ] + }, + "FactionCrimeStatusEnum": { + "type": "string", + "enum": [ + "Recruiting", + "Planning", + "Successful", + "Failure", + "Expired" + ] + }, + "AttackActionEnum": { + "type": "string", + "enum": [ + "attackerhosp", + "busy", + "critical hit", + "attackerjail", + "escapefail", + "hit", + "hosp", + "joinfight", + "leave", + "loot", + "lost", + "missed", + "mug", + "noAmmo", + "onItemUseEff", + "opponenthosp", + "opponentjail", + "paralyzed", + "reload", + "runaway", + "specialTemp", + "specialTempI", + "stalemate", + "startfight", + "stunned", + "suppressed", + "timeout", + "won" + ] + }, + "TornItemAmmoTypeEnum": { + "type": "string", + "enum": [ + "Standard", + "Hollow Point", + "Piercing", + "Tracer", + "Incendiary" + ] + }, + "TornItemWeaponTypeEnum": { + "type": "string", + "enum": [ + "Heavy artillery", + "Machine gun", + "Pistol", + "Rifle", + "Shotgun", + "SMG", + "Temporary", + "Clubbing", + "Piercing", + "Slashing", + "Mechanical" + ] + }, + "TornItemWeaponCategoryEnum": { + "type": "string", + "enum": [ + "Melee", + "Secondary", + "Primary", + "Temporary" + ] + }, + "TornItemTypeEnum": { + "type": "string", + "enum": [ + "Alcohol", + "Armor", + "Artifact", + "Book", + "Booster", + "Candy", + "Car", + "Clothing", + "Collectible", + "Drug", + "Energy Drink", + "Enhancer", + "Flower", + "Jewelry", + "Material", + "Medical", + "Other", + "Plushie", + "Special", + "Supply Pack", + "Tool", + "Unused", + "Weapon" + ] + }, + "TornItemCategory": { + "type": "string", + "enum": [ + "All", + "Alcohol", + "Armor", + "Artifact", + "Book", + "Booster", + "Candy", + "Car", + "Clothing", + "Collectible", + "Defensive", + "Drug", + "Energy Drink", + "Enhancer", + "Flower", + "Jewelry", + "Material", + "Medical", + "Melee", + "Other", + "Plushie", + "Primary", + "Secondary", + "Special", + "Supply Pack", + "Temporary", + "Tool", + "Unused", + "Weapon" + ] + }, + "TornItemArmorCoveragePartEnum": { + "type": "string", + "enum": [ + "Full Body", + "Heart", + "Stomach", + "Chest", + "Arm", + "Groin", + "Leg", + "Throat", + "Hand", + "Foot", + "Head" + ] + }, + "AttackPlayerFaction": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "AttackPlayer": { + "required": [ + "id", + "name", + "level", + "faction" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "faction": { + "oneOf": [ + { + "$ref": "#/components/schemas/AttackPlayerFaction" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "AttackPlayerSimplified": { + "required": [ + "id", + "faction_id" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "faction_id": { + "oneOf": [ + { + "$ref": "#/components/schemas/FactionId" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "AttackingFinishingHitEffects": { + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "$ref": "#/components/schemas/AttackFinishingHitEffect" + }, + "value": { + "description": "Advanced weapon bonus value in percentage.", + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "Attack": { + "required": [ + "id", + "code", + "started", + "ended", + "attacker", + "defender", + "result", + "respect_gain", + "respect_loss", + "chain", + "is_interrupted", + "is_stealthed", + "is_raid", + "is_ranked_war", + "finishing_hit_effects", + "modifiers" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/AttackId" + }, + "code": { + "$ref": "#/components/schemas/AttackCode" + }, + "started": { + "description": "Attack start timestamp.", + "type": "integer", + "format": "int32" + }, + "ended": { + "description": "Attack end timestamp.", + "type": "integer", + "format": "int32" + }, + "attacker": { + "oneOf": [ + { + "$ref": "#/components/schemas/AttackPlayer" + }, + { + "type": "null" + } + ] + }, + "defender": { + "$ref": "#/components/schemas/AttackPlayer" + }, + "result": { + "$ref": "#/components/schemas/FactionAttackResult" + }, + "respect_gain": { + "type": "number", + "format": "float" + }, + "respect_loss": { + "type": "number", + "format": "float" + }, + "chain": { + "type": "integer", + "format": "int32" + }, + "is_interrupted": { + "description": "This is an experimental flag which should help determine 'assist' attacks which have not contributed to the chain. For example, attacks such as where the opponent lost to someoene else before the attacker could finish the attack. This flag might not work entirely correctly, so use with caution.", + "type": "boolean" + }, + "is_stealthed": { + "type": "boolean" + }, + "is_raid": { + "type": "boolean" + }, + "is_ranked_war": { + "type": "boolean" + }, + "finishing_hit_effects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AttackingFinishingHitEffects" + } + }, + "modifiers": { + "required": [ + "fair_fight", + "war", + "retaliation", + "group", + "overseas", + "chain", + "warlord" + ], + "properties": { + "fair_fight": { + "type": "number", + "format": "float" + }, + "war": { + "type": "number", + "format": "float" + }, + "retaliation": { + "type": "number", + "format": "float" + }, + "group": { + "type": "number", + "format": "float" + }, + "overseas": { + "type": "number", + "format": "float" + }, + "chain": { + "type": "number", + "format": "float" + }, + "warlord": { + "type": "number", + "format": "float" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "AttackSimplified": { + "required": [ + "id", + "code", + "started", + "ended", + "attacker", + "defender", + "result", + "respect_gain", + "respect_loss" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/AttackId" + }, + "code": { + "$ref": "#/components/schemas/AttackCode" + }, + "started": { + "description": "Attack start timestamp.", + "type": "integer", + "format": "int32" + }, + "ended": { + "description": "Attack end timestamp.", + "type": "integer", + "format": "int32" + }, + "attacker": { + "oneOf": [ + { + "$ref": "#/components/schemas/AttackPlayerSimplified" + }, + { + "type": "null" + } + ] + }, + "defender": { + "$ref": "#/components/schemas/AttackPlayerSimplified" + }, + "result": { + "$ref": "#/components/schemas/FactionAttackResult" + }, + "respect_gain": { + "type": "number", + "format": "float" + }, + "respect_loss": { + "type": "number", + "format": "float" + } + }, + "type": "object" + }, + "ReviveSimplified": { + "required": [ + "id", + "reviver", + "target", + "success_chance", + "result", + "timestamp" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ReviveId" + }, + "reviver": { + "required": [ + "id", + "faction_id" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "faction_id": { + "oneOf": [ + { + "$ref": "#/components/schemas/FactionId" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "target": { + "required": [ + "id", + "faction_id", + "hospital_reason", + "early_discharge", + "last_action", + "online_status" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "faction_id": { + "oneOf": [ + { + "$ref": "#/components/schemas/FactionId" + }, + { + "type": "null" + } + ] + }, + "hospital_reason": { + "type": "string" + }, + "early_discharge": { + "type": "boolean" + }, + "last_action": { + "type": "integer", + "format": "int32" + }, + "online_status": { + "type": "string" + } + }, + "type": "object" + }, + "success_chance": { + "type": "number", + "format": "float" + }, + "result": { + "type": "string" + }, + "timestamp": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "Revive": { + "required": [ + "id", + "reviver", + "target", + "success_chance", + "result", + "timestamp" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ReviveId" + }, + "reviver": { + "required": [ + "id", + "name", + "faction" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + }, + "faction": { + "oneOf": [ + { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "target": { + "required": [ + "id", + "name", + "faction", + "hospital_reason", + "early_discharge", + "last_action", + "online_status" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + }, + "faction": { + "oneOf": [ + { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + }, + "hospital_reason": { + "type": "string" + }, + "early_discharge": { + "type": "boolean" + }, + "last_action": { + "type": "integer", + "format": "int32" + }, + "online_status": { + "type": "string" + } + }, + "type": "object" + }, + "success_chance": { + "type": "number", + "format": "float" + }, + "result": { + "type": "string" + }, + "timestamp": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "RevivesResponse": { + "required": [ + "revives", + "_metadata" + ], + "properties": { + "revives": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Revive" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "RevivesFullResponse": { + "required": [ + "revives", + "_metadata" + ], + "properties": { + "revives": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReviveSimplified" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "TimestampResponse": { + "required": [ + "timestamp" + ], + "properties": { + "timestamp": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionUpgradeDetails": { + "required": [ + "id", + "name", + "ability", + "level", + "cost", + "unlockedAt" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionBranchId" + }, + "name": { + "type": "string" + }, + "ability": { + "type": "string" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "cost": { + "type": "integer", + "format": "int32" + }, + "unlocked_at": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionBranchDetails": { + "required": [ + "name", + "order", + "multiplier", + "upgrades" + ], + "properties": { + "name": { + "type": "string" + }, + "order": { + "type": "integer", + "format": "int32" + }, + "multiplier": { + "description": "Respect cost multiplier.", + "type": "integer", + "format": "int32" + }, + "upgrades": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionUpgradeDetails" + } + } + }, + "type": "object" + }, + "FactionUpgrades": { + "required": [ + "core", + "peace", + "war" + ], + "properties": { + "core": { + "properties": { + "upgrades": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionUpgradeDetails" + } + } + }, + "type": "object" + }, + "peace": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionBranchDetails" + } + }, + "war": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionBranchDetails" + } + } + }, + "type": "object" + }, + "FactionUpgradesResponse": { + "required": [ + "upgrades", + "state" + ], + "properties": { + "upgrades": { + "$ref": "#/components/schemas/FactionUpgrades" + }, + "state": { + "$ref": "#/components/schemas/FactionBranchStateEnum" + } + }, + "type": "object" + }, + "FactionStat": { + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "$ref": "#/components/schemas/FactionStatEnum" + }, + "value": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + }, + "FactionStatsResponse": { + "required": [ + "stats" + ], + "properties": { + "stats": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionStat" + } + } + }, + "type": "object" + }, + "FactionContributor": { + "required": [ + "id", + "username", + "value", + "in_faction" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "username": { + "type": "string" + }, + "value": { + "type": "integer", + "format": "int64" + }, + "in_faction": { + "type": "boolean" + } + }, + "type": "object" + }, + "FactionContributorsResponse": { + "required": [ + "contributors" + ], + "properties": { + "contributors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionContributor" + } + } + }, + "type": "object" + }, + "FactionHofStats": { + "required": [ + "rank", + "respect", + "chain" + ], + "properties": { + "rank": { + "$ref": "#/components/schemas/HofValueString" + }, + "respect": { + "$ref": "#/components/schemas/HofValue" + }, + "chain": { + "$ref": "#/components/schemas/HofValue" + } + }, + "type": "object" + }, + "FactionHofResponse": { + "required": [ + "hof" + ], + "properties": { + "hof": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionHofStats" + } + } + }, + "type": "object" + }, + "FactionMember": { + "description": "Details about a faction member.", + "required": [ + "id", + "name", + "position", + "level", + "days_in_faction", + "is_revivable", + "is_on_wall", + "is_in_oc", + "has_early_discharge", + "last_action", + "status", + "revive_setting" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + }, + "position": { + "type": "string" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "days_in_faction": { + "type": "integer", + "format": "int32" + }, + "is_revivable": { + "type": "boolean" + }, + "is_on_wall": { + "description": "Shows if the member is currently defending territory wall.", + "type": "boolean" + }, + "is_in_oc": { + "description": "Shows if the member is currently participating in an organized crime. Show false for members of other factions.", + "type": "boolean" + }, + "has_early_discharge": { + "description": "Shows if the member is eligible for an early discharge from the hospital.", + "type": "boolean" + }, + "last_action": { + "$ref": "#/components/schemas/UserLastAction" + }, + "status": { + "$ref": "#/components/schemas/UserStatus" + }, + "life": { + "$ref": "#/components/schemas/UserLife" + }, + "revive_setting": { + "$ref": "#/components/schemas/ReviveSetting" + } + }, + "type": "object" + }, + "UserLastAction": { + "description": "Details about a user's last action.", + "required": [ + "status", + "timestamp", + "relative" + ], + "properties": { + "status": { + "type": "string" + }, + "timestamp": { + "type": "integer", + "format": "int32" + }, + "relative": { + "type": "string" + } + }, + "type": "object" + }, + "UserLife": { + "description": "Unfortunately, current life value is often shown incorrectly, and the calculation for each member is very resource heavy, so this field is deprecated and will be removed on 1st of May, 2025.", + "properties": { + "current": { + "type": "integer", + "format": "int32", + "deprecated": true + }, + "maximum": { + "type": "integer", + "format": "int32", + "deprecated": true + } + }, + "type": "object", + "deprecated": true + }, + "UserStatus": { + "description": "Details about a user's status.", + "required": [ + "description", + "details", + "state", + "until" + ], + "properties": { + "description": { + "type": "string" + }, + "details": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "state": { + "type": "string" + }, + "until": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "FactionMembersResponse": { + "required": [ + "members" + ], + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionMember" + } + } + }, + "type": "object" + }, + "FactionRank": { + "required": [ + "level", + "name", + "division", + "position", + "wins" + ], + "properties": { + "level": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + }, + "division": { + "type": "integer", + "format": "int32" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "wins": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionBasic": { + "required": [ + "id", + "name", + "tag", + "tag_image", + "leader_id", + "co-leader_id", + "respect", + "days_old", + "capacity", + "members", + "is_enlisted", + "rank", + "best_chain" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "tag_image": { + "type": "string" + }, + "leader_id": { + "$ref": "#/components/schemas/UserId" + }, + "co-leader_id": { + "$ref": "#/components/schemas/UserId" + }, + "respect": { + "type": "integer", + "format": "int32" + }, + "days_old": { + "type": "integer", + "format": "int32" + }, + "capacity": { + "type": "integer", + "format": "int32" + }, + "members": { + "type": "integer", + "format": "int32" + }, + "is_enlisted": { + "description": "Indicates if the faction is enlisted for ranked wars. Available only with faction AA permissions for your own faction.", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + }, + "rank": { + "$ref": "#/components/schemas/FactionRank" + }, + "best_chain": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionBasicResponse": { + "required": [ + "basic" + ], + "properties": { + "basic": { + "$ref": "#/components/schemas/FactionBasic" + } + }, + "type": "object" + }, + "FactionPact": { + "required": [ + "faction_id", + "faction_name", + "until" + ], + "properties": { + "faction_id": { + "$ref": "#/components/schemas/FactionId" + }, + "faction_name": { + "type": "string" + }, + "until": { + "description": "The duration until when is the non-aggression pact valid.", + "type": "string" + } + }, + "type": "object" + }, + "FactionRankedWarParticipant": { + "required": [ + "id", + "name", + "score", + "chain" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer", + "format": "int32" + }, + "chain": { + "description": "Faction's current chain.", + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionRankedWar": { + "required": [ + "war_id", + "start", + "end", + "target", + "winner", + "factions" + ], + "properties": { + "war_id": { + "type": "integer", + "format": "int32" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "target": { + "description": "The score target of the war.", + "type": "integer", + "format": "int32" + }, + "winner": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "factions": { + "description": "The factions involved in the ranked war.", + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionRankedWarParticipant" + } + } + }, + "type": "object" + }, + "FactionRaidWarParticipant": { + "required": [ + "id", + "name", + "score", + "chain", + "is_aggressor" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer", + "format": "int32" + }, + "chain": { + "description": "Faction's current chain.", + "type": "integer", + "format": "int32" + }, + "is_aggressor": { + "type": "boolean" + } + }, + "type": "object" + }, + "FactionRaidWar": { + "required": [ + "war_id", + "start", + "end", + "factions" + ], + "properties": { + "war_id": { + "type": "integer", + "format": "int32" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "factions": { + "description": "The factions involved in the raid war.", + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionRaidWarParticipant" + } + } + }, + "type": "object" + }, + "FactionTerritoryWarParticipant": { + "required": [ + "id", + "name", + "score", + "chain", + "is_aggressor", + "playerIds" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer", + "format": "int32" + }, + "chain": { + "description": "Faction's current chain.", + "type": "integer", + "format": "int32" + }, + "is_aggressor": { + "type": "boolean" + }, + "playerIds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserId" + } + } + }, + "type": "object" + }, + "FactionTerritoryWar": { + "required": [ + "war_id", + "territory", + "start", + "end", + "target", + "factions" + ], + "properties": { + "war_id": { + "type": "integer", + "format": "int32" + }, + "territory": { + "type": "string" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "target": { + "description": "The score target of the war.", + "type": "integer", + "format": "int32" + }, + "winner": { + "oneOf": [ + { + "$ref": "#/components/schemas/FactionId" + }, + { + "type": "null" + } + ] + }, + "factions": { + "description": "The factions involved in the territory war.", + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionTerritoryWarParticipant" + } + } + }, + "type": "object" + }, + "FactionWars": { + "required": [ + "ranked", + "raids", + "territory" + ], + "properties": { + "ranked": { + "oneOf": [ + { + "$ref": "#/components/schemas/FactionRankedWar" + }, + { + "type": "null" + } + ] + }, + "raids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionRaidWar" + } + }, + "territory": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionTerritoryWar" + } + } + }, + "type": "object" + }, + "FactionWarsResponse": { + "required": [ + "pacts", + "wars" + ], + "properties": { + "pacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionPact" + } + }, + "wars": { + "$ref": "#/components/schemas/FactionWars" + } + }, + "type": "object" + }, + "FactionNews": { + "required": [ + "id", + "text", + "timestamp" + ], + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "timestamp": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionNewsResponse": { + "required": [ + "news", + "_metadata" + ], + "properties": { + "news": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionNews" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "FactionAttacksResponse": { + "required": [ + "attacks", + "_metadata" + ], + "properties": { + "attacks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Attack" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "FactionAttacksFullResponse": { + "required": [ + "attacks", + "_metadata" + ], + "properties": { + "attacks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AttackSimplified" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "FactionApplication": { + "required": [ + "id", + "user", + "message", + "valid_until", + "status" + ], + "properties": { + "id": { + "description": "application id", + "type": "integer", + "format": "int32" + }, + "user": { + "required": [ + "id", + "name", + "level", + "stats" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + }, + "level": { + "type": "string" + }, + "stats": { + "required": [ + "strength", + "speed", + "dexterity", + "defense" + ], + "properties": { + "strength": { + "type": "integer", + "format": "int64" + }, + "speed": { + "type": "integer", + "format": "int64" + }, + "dexterity": { + "type": "integer", + "format": "int64" + }, + "defense": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "message": { + "type": "string" + }, + "valid_until": { + "type": "integer", + "format": "int32" + }, + "status": { + "$ref": "#/components/schemas/FactionApplicationStatusEnum" + } + }, + "type": "object" + }, + "FactionApplicationsResponse": { + "required": [ + "applications" + ], + "properties": { + "applications": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionApplication" + } + } + }, + "type": "object" + }, + "FactionOngoingChain": { + "required": [ + "id", + "current", + "max", + "timeout", + "modifier", + "cooldown", + "start", + "end" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ChainId" + }, + "current": { + "type": "integer", + "format": "int32" + }, + "max": { + "type": "integer", + "format": "int32" + }, + "timeout": { + "description": "Seconds until chain breaks.", + "type": "integer", + "format": "int32" + }, + "modifier": { + "type": "number", + "format": "float" + }, + "cooldown": { + "description": "Timestamp until when chain is on cooldown.", + "type": "integer", + "format": "int32" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionOngoingChainResponse": { + "required": [ + "chain" + ], + "properties": { + "chain": { + "$ref": "#/components/schemas/FactionOngoingChain" + } + }, + "type": "object" + }, + "FactionChain": { + "required": [ + "id", + "chain", + "respect", + "start", + "end" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ChainId" + }, + "chain": { + "type": "integer", + "format": "int32" + }, + "respect": { + "type": "number", + "format": "float" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionChainsResponse": { + "required": [ + "chains", + "_metadata" + ], + "properties": { + "chains": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionChain" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "FactionChainReportResponse": { + "required": [ + "chainreport" + ], + "properties": { + "chainreport": { + "$ref": "#/components/schemas/FactionChainReport" + } + }, + "type": "object" + }, + "FactionChainReport": { + "required": [ + "id", + "faction_id", + "start", + "end", + "details", + "bonuses", + "attackers", + "non-attackers" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ChainId" + }, + "faction_id": { + "$ref": "#/components/schemas/FactionId" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "type": "integer", + "format": "int32" + }, + "details": { + "$ref": "#/components/schemas/FactionChainReportDetails" + }, + "bonuses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionChainReportBonus" + } + }, + "attackers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionChainReportAttacker" + } + }, + "non-attackers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserId" + } + } + }, + "type": "object" + }, + "FactionChainReportDetails": { + "required": [ + "chain", + "respect", + "members", + "targets", + "war", + "best", + "leave", + "mug", + "hospitalize", + "assists", + "retaliations", + "overseas", + "draws", + "escapes", + "losses" + ], + "properties": { + "chain": { + "type": "integer", + "format": "int32" + }, + "respect": { + "type": "number", + "format": "float" + }, + "members": { + "type": "integer", + "format": "int32" + }, + "targets": { + "type": "integer", + "format": "int32" + }, + "war": { + "type": "integer", + "format": "int32" + }, + "best": { + "type": "number", + "format": "float" + }, + "leave": { + "type": "integer", + "format": "int32" + }, + "mug": { + "type": "integer", + "format": "int32" + }, + "hospitalize": { + "type": "integer", + "format": "int32" + }, + "assists": { + "type": "integer", + "format": "int32" + }, + "retaliations": { + "type": "integer", + "format": "int32" + }, + "overseas": { + "type": "integer", + "format": "int32" + }, + "draws": { + "type": "integer", + "format": "int32" + }, + "escapes": { + "type": "integer", + "format": "int32" + }, + "losses": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionChainReportBonus": { + "required": [ + "attacker_id", + "defender_id", + "chain", + "respect" + ], + "properties": { + "attacker_id": { + "$ref": "#/components/schemas/UserId" + }, + "defender_id": { + "$ref": "#/components/schemas/UserId" + }, + "chain": { + "type": "integer", + "format": "int32" + }, + "respect": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionChainReportAttacker": { + "required": [ + "id", + "respect", + "attacks" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "respect": { + "$ref": "#/components/schemas/FactionChainReportAttackerRespect" + }, + "attacks": { + "$ref": "#/components/schemas/FactionChainReportAttackerAttacks" + } + }, + "type": "object" + }, + "FactionChainReportAttackerRespect": { + "required": [ + "total", + "average", + "best" + ], + "properties": { + "total": { + "type": "number", + "format": "float" + }, + "average": { + "type": "number", + "format": "float" + }, + "best": { + "type": "number", + "format": "float" + } + }, + "type": "object" + }, + "FactionChainReportAttackerAttacks": { + "required": [ + "total", + "leave", + "mug", + "hospitalize", + "assists", + "retaliations", + "overseas", + "draws", + "escpaces", + "losses", + "war", + "bonuses" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "leave": { + "type": "integer", + "format": "int32" + }, + "mug": { + "type": "integer", + "format": "int32" + }, + "hospitalize": { + "type": "integer", + "format": "int32" + }, + "assists": { + "type": "integer", + "format": "int32" + }, + "retaliations": { + "type": "integer", + "format": "int32" + }, + "overseas": { + "type": "integer", + "format": "int32" + }, + "draws": { + "type": "integer", + "format": "int32" + }, + "escapes": { + "type": "integer", + "format": "int32" + }, + "losses": { + "type": "integer", + "format": "int32" + }, + "war": { + "type": "integer", + "format": "int32" + }, + "bonuses": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionCrimeUser": { + "required": [ + "id", + "outcome", + "joined_at", + "progress" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "outcome": { + "description": "This field will be null for old crimes.", + "oneOf": [ + { + "$ref": "#/components/schemas/FactionCrimeUserOutcome" + }, + { + "type": "null" + } + ] + }, + "joined_at": { + "description": "The timestamp at which the user joined the slot.", + "type": "integer", + "format": "int32" + }, + "progress": { + "description": "Current planning progress on the slot.", + "type": "number", + "format": "float" + } + }, + "type": "object" + }, + "FactionCrimeRewardItem": { + "required": [ + "id", + "quantity" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ItemId" + }, + "quantity": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionCrimeRewardPayout": { + "required": [ + "type", + "percentage", + "paid_by", + "paid_at" + ], + "properties": { + "type": { + "$ref": "#/components/schemas/FactionOrganizedCrimePayoutType" + }, + "percentage": { + "description": "Total percentage split between all participants.", + "type": "integer", + "format": "int32" + }, + "paid_by": { + "$ref": "#/components/schemas/UserId" + }, + "paid_at": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionCrimeReward": { + "required": [ + "money", + "items", + "respect", + "payout", + "scope" + ], + "properties": { + "money": { + "type": "integer", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionCrimeRewardItem" + } + }, + "respect": { + "type": "integer", + "format": "int32" + }, + "scope": { + "type": "integer", + "format": "int32" + }, + "payout": { + "description": "Details about the crime payouts. This field is null if the crime has not been paid via the automatic payouts system.", + "oneOf": [ + { + "$ref": "#/components/schemas/FactionCrimeRewardPayout" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "FactionCrimeSlot": { + "required": [ + "position", + "item_requirement", + "user", + "checkpoint_pass_rate" + ], + "properties": { + "position": { + "type": "string" + }, + "item_requirement": { + "description": "Details of item required for the slot, if applicable.", + "oneOf": [ + { + "required": [ + "id", + "is_reusable", + "is_available" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ItemId" + }, + "is_reusable": { + "description": "Shows if the item is reusable or consumed during the crime.", + "type": "boolean" + }, + "is_available": { + "description": "Shows if user has the required item.", + "type": "boolean" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + }, + "user_id": { + "description": "This field is replaced with the 'user.id' and will be removed on the 1st of May 2025.", + "deprecated": true, + "oneOf": [ + { + "$ref": "#/components/schemas/UserId" + }, + { + "type": "null" + } + ] + }, + "user": { + "description": "Details about the user joined the slot, if any.", + "oneOf": [ + { + "$ref": "#/components/schemas/FactionCrimeUser" + }, + { + "type": "null" + } + ] + }, + "success_chance": { + "description": "This field is replaced with 'checkpoint_pass_rate' field and will be removed on 1st of May 2025.", + "type": "integer", + "format": "int32", + "deprecated": true + }, + "crime_pass_rate": { + "description": "This field is replaced with 'checkpoint_pass_rate' field and will be removed on 1st of May 2025.", + "type": "integer", + "format": "int32", + "deprecated": true + }, + "checkpoint_pass_rate": { + "description": "Returns CPR for the player who joined the slot. If the slot is empty (availalbe), it shows your CPR for that slot. This value is 0 for expired crimes.", + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "FactionCrime": { + "required": [ + "id", + "name", + "difficulty", + "status", + "created_at", + "planning_at", + "ready_at", + "expired_at", + "executed_at", + "slots", + "rewards" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionCrimeId" + }, + "name": { + "type": "string" + }, + "difficulty": { + "type": "integer", + "format": "int32" + }, + "status": { + "$ref": "#/components/schemas/FactionCrimeStatusEnum" + }, + "created_at": { + "description": "The timestamp at which the crime was created.", + "type": "integer", + "format": "int32" + }, + "planning_at": { + "description": "The timestamp at which the planning phase for the crime has begun.", + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "ready_at": { + "description": "The timestamp at which the crime will be ready.", + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "expired_at": { + "description": "The timestamp at which the crime will expire.", + "type": "integer", + "format": "int32" + }, + "executed_at": { + "description": "The timestamp at which the crime was executed.
Note: this value is null for all crimes executed before January 15th, 2025.", + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "slots": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionCrimeSlot" + } + }, + "rewards": { + "description": "Details about the crime rewards. Available only for crimes with 'Successful' status.", + "oneOf": [ + { + "$ref": "#/components/schemas/FactionCrimeReward" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "FactionCrimesResponse": { + "required": [ + "crimes", + "_metadata" + ], + "properties": { + "crimes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionCrime" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "FactionCrimeResponse": { + "required": [ + "crime" + ], + "properties": { + "crime": { + "$ref": "#/components/schemas/FactionCrime" + } + }, + "type": "object" + }, + "FactionBalance": { + "required": [ + "faction", + "members" + ], + "properties": { + "faction": { + "required": [ + "money", + "points", + "scope" + ], + "properties": { + "money": { + "type": "integer", + "format": "int64" + }, + "points": { + "type": "integer", + "format": "int64" + }, + "scope": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "members": { + "type": "array", + "items": { + "required": [ + "id", + "username", + "money", + "points" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "username": { + "type": "string" + }, + "money": { + "type": "integer", + "format": "int64" + }, + "points": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "FactionBalanceResponse": { + "required": [ + "balance" + ], + "properties": { + "balance": { + "$ref": "#/components/schemas/FactionBalance" + } + }, + "type": "object" + }, + "FactionSelectionName": { + "description": "The following selections will fallback to API v1 and may change at any time: 'armor', 'boosters', 'caches', 'cesium', 'crimeexp', 'drugs', 'medical', 'positions', 'reports', 'temporary', 'weapons'.", + "type": "string", + "enum": [ + "applications", + "attacks", + "attacksfull", + "balance", + "basic", + "chain", + "chainreport", + "chains", + "contributors", + "crime", + "crimes", + "hof", + "lookup", + "members", + "news", + "rankedwars", + "rankedwarreport", + "revives", + "revivesfull", + "stats", + "timestamp", + "upgrades", + "wars", + "armor", + "boosters", + "caches", + "cesium", + "crimeexp", + "drugs", + "medical", + "positions", + "reports", + "temporary", + "territory", + "weapons" + ] + }, + "FactionLookupResponse": { + "required": [ + "selections" + ], + "properties": { + "selections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionSelectionName" + } + } + }, + "type": "object" + }, + "FactionRankedWarDetails": { + "required": [ + "id", + "start", + "end", + "target", + "winner", + "factions" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/RankedWarId" + }, + "start": { + "description": "Timestamp the war started at.", + "type": "integer", + "format": "int32" + }, + "end": { + "description": "Timestamp the war ended at.", + "type": "integer", + "format": "int32" + }, + "target": { + "type": "integer", + "format": "int32" + }, + "winner": { + "oneOf": [ + { + "$ref": "#/components/schemas/FactionId" + }, + { + "type": "null" + } + ] + }, + "factions": { + "type": "array", + "items": { + "required": [ + "id", + "name", + "score", + "chain" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer", + "format": "int32" + }, + "chain": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "FactionRankedWarResponse": { + "required": [ + "rankedwars", + "_metadata" + ], + "properties": { + "rankedwars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FactionRankedWarDetails" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "FactionRankedWarReportResponse": { + "required": [ + "rankedwarreport" + ], + "properties": { + "rankedwarreport": { + "required": [ + "id", + "start", + "end", + "winner", + "forfeit", + "factions" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/RankedWarId" + }, + "start": { + "description": "Timestamp the war started at.", + "type": "integer", + "format": "int32" + }, + "end": { + "description": "Timestamp the war ended at.", + "type": "integer", + "format": "int32" + }, + "winner": { + "$ref": "#/components/schemas/FactionId" + }, + "forfeit": { + "type": "boolean" + }, + "factions": { + "type": "array", + "items": { + "required": [ + "id", + "name", + "score", + "attacks", + "rank", + "rewards", + "members" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer", + "format": "int32" + }, + "attacks": { + "type": "integer", + "format": "int32" + }, + "rank": { + "required": [ + "before", + "after" + ], + "properties": { + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + }, + "type": "object" + }, + "rewards": { + "required": [ + "respect", + "points", + "items" + ], + "properties": { + "respect": { + "type": "integer", + "format": "int32" + }, + "points": { + "type": "integer", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "required": [ + "id", + "name", + "quantity" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ItemId" + }, + "name": { + "type": "string" + }, + "quantity": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "members": { + "type": "array", + "items": { + "required": [ + "id", + "name", + "level", + "attacks", + "score" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "attacks": { + "type": "integer", + "format": "int32" + }, + "score": { + "type": "number", + "format": "float" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "ForumCategoriesResponse": { + "required": [ + "categories" + ], + "properties": { + "categories": { + "type": "array", + "items": { + "required": [ + "id", + "title", + "acronym", + "threads" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ForumId" + }, + "title": { + "type": "string" + }, + "acronym": { + "type": "string" + }, + "threads": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "ForumThreadAuthor": { + "required": [ + "id", + "username", + "karma" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "username": { + "type": "string" + }, + "karma": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ForumPollVote": { + "required": [ + "answer", + "votes" + ], + "properties": { + "answer": { + "type": "string" + }, + "votes": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ForumPoll": { + "required": [ + "question", + "answers_count", + "answers" + ], + "properties": { + "question": { + "type": "string" + }, + "answers_count": { + "type": "integer", + "format": "int32" + }, + "answers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumPollVote" + } + } + }, + "type": "object" + }, + "ForumThreadBase": { + "required": [ + "id", + "title", + "forum_id", + "posts", + "rating", + "views", + "author", + "last_poster", + "first_post_time", + "last_post_time", + "has_poll", + "is_locked", + "is_sticky" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ForumThreadId" + }, + "title": { + "type": "string" + }, + "forum_id": { + "$ref": "#/components/schemas/ForumId" + }, + "posts": { + "type": "integer", + "format": "int32" + }, + "rating": { + "type": "integer", + "format": "int32" + }, + "views": { + "description": "Total number of times players have opened this thread.", + "type": "integer", + "format": "int32" + }, + "author": { + "$ref": "#/components/schemas/ForumThreadAuthor" + }, + "last_poster": { + "oneOf": [ + { + "$ref": "#/components/schemas/ForumThreadAuthor" + }, + { + "type": "null" + } + ] + }, + "first_post_time": { + "type": "integer", + "format": "int32" + }, + "last_post_time": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "has_poll": { + "type": "boolean" + }, + "is_locked": { + "type": "boolean" + }, + "is_sticky": { + "type": "boolean" + } + }, + "type": "object" + }, + "ForumThreadExtended": { + "allOf": [ + { + "required": [ + "content", + "content_raw", + "poll" + ], + "properties": { + "content": { + "type": "string" + }, + "content_raw": { + "type": "string" + }, + "poll": { + "description": "'poll' is null when 'has_poll' is false.", + "oneOf": [ + { + "$ref": "#/components/schemas/ForumPoll" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/ForumThreadBase" + } + ] + }, + "ForumPost": { + "required": [ + "id", + "thread_id", + "author", + "is_legacy", + "is_topic", + "is_edited", + "is_pinned", + "created_time", + "edited_by", + "has_quote", + "quoted_post_id", + "content", + "likes", + "dislikes" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ForumPostId" + }, + "thread_id": { + "$ref": "#/components/schemas/ForumThreadId" + }, + "author": { + "$ref": "#/components/schemas/ForumThreadAuthor" + }, + "is_legacy": { + "description": "Indicates whether post was made using the old formatting engine which doesn't use HTML.", + "type": "boolean" + }, + "is_topic": { + "type": "boolean" + }, + "is_edited": { + "type": "boolean" + }, + "is_pinned": { + "type": "boolean" + }, + "created_time": { + "type": "integer", + "format": "int32" + }, + "edited_by": { + "description": "'edited_by' is null when 'is_edited' is false.", + "oneOf": [ + { + "$ref": "#/components/schemas/UserId" + }, + { + "type": "null" + } + ] + }, + "has_quote": { + "type": "boolean" + }, + "quoted_post_id": { + "description": "'quoted_post_id' is null when 'has_quote' is false.", + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "content": { + "description": "depending on the input 'cat' parameter, this will either return raw value (with HTML) or plain text. Legacy posts are returned as is, they can't be stripped of tags.", + "type": "string" + }, + "likes": { + "type": "integer", + "format": "int32" + }, + "dislikes": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ForumThreadUserExtended": { + "allOf": [ + { + "required": [ + "new_posts" + ], + "properties": { + "new_posts": { + "description": "Available only when requesting data for yourself (no id or your id) with at least 'Minimal' access type key.", + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/ForumThreadBase" + } + ] + }, + "ForumSubscribedThreadPostsCount": { + "required": [ + "new", + "total" + ], + "properties": { + "new": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ForumSubscribedThread": { + "required": [ + "id", + "forum_id", + "author", + "title", + "posts" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ForumThreadId" + }, + "forum_id": { + "$ref": "#/components/schemas/ForumId" + }, + "author": { + "$ref": "#/components/schemas/ForumThreadAuthor" + }, + "title": { + "type": "string" + }, + "posts": { + "$ref": "#/components/schemas/ForumSubscribedThreadPostsCount" + } + }, + "type": "object" + }, + "ForumFeed": { + "required": [ + "thread_id", + "post_id", + "user", + "title", + "text", + "timestamp", + "is_seen", + "type" + ], + "properties": { + "thread_id": { + "$ref": "#/components/schemas/ForumThreadId" + }, + "post_id": { + "$ref": "#/components/schemas/ForumPostId" + }, + "user": { + "$ref": "#/components/schemas/ForumThreadAuthor" + }, + "title": { + "type": "string" + }, + "text": { + "type": "string" + }, + "timestamp": { + "type": "integer", + "format": "int32" + }, + "is_seen": { + "type": "boolean" + }, + "type": { + "$ref": "#/components/schemas/ForumFeedTypeEnum" + } + }, + "type": "object" + }, + "ForumThreadsResponse": { + "required": [ + "threads", + "_metadata" + ], + "properties": { + "threads": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumThreadBase" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "ForumThreadResponse": { + "required": [ + "thread" + ], + "properties": { + "thread": { + "$ref": "#/components/schemas/ForumThreadExtended" + } + }, + "type": "object" + }, + "ForumPostsResponse": { + "required": [ + "posts", + "_metadata" + ], + "properties": { + "posts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumPost" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "ForumSelectionName": { + "type": "string", + "enum": [ + "categories", + "lookup", + "posts", + "thread", + "threads", + "timestamp" + ] + }, + "ForumLookupResponse": { + "required": [ + "selections" + ], + "properties": { + "selections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumSelectionName" + } + } + }, + "type": "object" + }, + "ItemMarketListingItemBonus": { + "required": [ + "id", + "title", + "description", + "value" + ], + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ItemMarketListingItemStats": { + "required": [ + "damage", + "accuracy", + "armor", + "quality" + ], + "properties": { + "damage": { + "oneOf": [ + { + "type": "number", + "format": "float" + }, + { + "type": "null" + } + ] + }, + "accuracy": { + "oneOf": [ + { + "type": "number", + "format": "float" + }, + { + "type": "null" + } + ] + }, + "armor": { + "oneOf": [ + { + "type": "number", + "format": "float" + }, + { + "type": "null" + } + ] + }, + "quality": { + "type": "number", + "format": "float" + } + }, + "type": "object" + }, + "ItemMarketItem": { + "required": [ + "id", + "name", + "type", + "average_price" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ItemId" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "average_price": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + }, + "ItemMarketListingStackable": { + "required": [ + "price", + "amount" + ], + "properties": { + "price": { + "type": "integer", + "format": "int64" + }, + "amount": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ItemMarketListingItemDetails": { + "required": [ + "uid", + "stats", + "bonuses", + "rarity" + ], + "properties": { + "uid": { + "$ref": "#/components/schemas/ItemUid" + }, + "stats": { + "$ref": "#/components/schemas/ItemMarketListingItemStats" + }, + "bonuses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemMarketListingItemBonus" + } + }, + "rarity": { + "oneOf": [ + { + "type": "string", + "enum": [ + "yellow", + "orange", + "red" + ] + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ItemMarketListingNonstackable": { + "required": [ + "price", + "amount", + "item_details" + ], + "properties": { + "price": { + "type": "integer", + "format": "int64" + }, + "amount": { + "type": "integer", + "format": "int32" + }, + "itemDetails": { + "description": "This field used camelCase instead of snake_case by accident and is replaced with 'item_details' field. This will be removed on 1st of May 2025.", + "deprecated": true, + "allOf": [ + { + "$ref": "#/components/schemas/ItemMarketListingItemDetails" + } + ] + }, + "item_details": { + "$ref": "#/components/schemas/ItemMarketListingItemDetails" + } + }, + "type": "object" + }, + "ItemMarket": { + "required": [ + "item", + "listings" + ], + "properties": { + "item": { + "$ref": "#/components/schemas/ItemMarketItem" + }, + "listings": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ItemMarketListingNonstackable" + }, + { + "$ref": "#/components/schemas/ItemMarketListingStackable" + } + ] + } + } + }, + "type": "object" + }, + "MarketItemMarketResponse": { + "required": [ + "itemmarket", + "_metadata" + ], + "properties": { + "itemmarket": { + "$ref": "#/components/schemas/ItemMarket" + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "MarketSelectionName": { + "description": "The following selections will fallback to API v1 and may change at any time: 'pointsmarket'.", + "type": "string", + "enum": [ + "itemmarket", + "lookup", + "timestamp", + "pointsmarket" + ] + }, + "MarketLookupResponse": { + "required": [ + "selections" + ], + "properties": { + "selections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MarketSelectionName" + } + } + }, + "type": "object" + }, + "RacingCarsResponse": { + "required": [ + "cars" + ], + "properties": { + "cars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RaceCar" + } + } + }, + "type": "object" + }, + "RaceCar": { + "required": [ + "car_item_id", + "car_item_name", + "top_speed", + "acceleration", + "braking", + "dirt", + "handling", + "safety", + "tarmac", + "class" + ], + "properties": { + "car_item_id": { + "$ref": "#/components/schemas/ItemId" + }, + "car_item_name": { + "type": "string" + }, + "top_speed": { + "type": "integer", + "format": "int32" + }, + "acceleration": { + "type": "integer", + "format": "int32" + }, + "braking": { + "type": "integer", + "format": "int32" + }, + "dirt": { + "type": "integer", + "format": "int32" + }, + "handling": { + "type": "integer", + "format": "int32" + }, + "safety": { + "type": "integer", + "format": "int32" + }, + "tarmac": { + "type": "integer", + "format": "int32" + }, + "class": { + "$ref": "#/components/schemas/RaceClassEnum" + } + }, + "type": "object" + }, + "RacingTracksResponse": { + "required": [ + "tracks" + ], + "properties": { + "tracks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RaceTrack" + } + } + }, + "type": "object" + }, + "RaceTrack": { + "required": [ + "id", + "title", + "description" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/RaceTrackId" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "RacingCarUpgradesResponse": { + "required": [ + "carupgrades" + ], + "properties": { + "carupgrades": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RaceCarUpgrade" + } + } + }, + "type": "object" + }, + "RaceCarUpgrade": { + "required": [ + "id", + "class_required", + "name", + "description", + "category", + "subcategory", + "effects", + "cost" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/RaceCarUpgradeId" + }, + "class_required": { + "$ref": "#/components/schemas/RaceClassEnum" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "category": { + "$ref": "#/components/schemas/RaceCarUpgradeCategory" + }, + "subcategory": { + "$ref": "#/components/schemas/RaceCarUpgradeSubCategory" + }, + "effects": { + "required": [ + "top_speed", + "acceleration", + "braking", + "handling", + "safety", + "dirt", + "tarmac" + ], + "properties": { + "top_speed": { + "type": "integer", + "format": "int32" + }, + "acceleration": { + "type": "integer", + "format": "int32" + }, + "braking": { + "type": "integer", + "format": "int32" + }, + "handling": { + "type": "integer", + "format": "int32" + }, + "safety": { + "type": "integer", + "format": "int32" + }, + "dirt": { + "type": "integer", + "format": "int32" + }, + "tarmac": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "cost": { + "required": [ + "points", + "cash" + ], + "properties": { + "points": { + "type": "integer", + "format": "int32" + }, + "cash": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "RacingRacesResponse": { + "required": [ + "races", + "_metadata" + ], + "properties": { + "races": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Race" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "Race": { + "required": [ + "id", + "title", + "track_id", + "creator_id", + "status", + "laps", + "participants", + "schedule", + "requirements" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/RaceId" + }, + "title": { + "type": "string" + }, + "track_id": { + "$ref": "#/components/schemas/RaceTrackId" + }, + "creator_id": { + "$ref": "#/components/schemas/UserId" + }, + "status": { + "$ref": "#/components/schemas/RaceStatusEnum" + }, + "laps": { + "type": "integer", + "format": "int32" + }, + "participants": { + "required": [ + "minimum", + "maximum", + "current" + ], + "properties": { + "minimum": { + "type": "integer", + "format": "int32" + }, + "maximum": { + "type": "integer", + "format": "int32" + }, + "current": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "schedule": { + "required": [ + "join_from", + "join_until", + "start", + "end" + ], + "properties": { + "join_from": { + "type": "integer", + "format": "int32" + }, + "join_until": { + "type": "integer", + "format": "int32" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "requirements": { + "required": [ + "car_class", + "driver_class", + "car_item_id", + "requires_stock_car", + "requires_password", + "join_fee" + ], + "properties": { + "car_class": { + "oneOf": [ + { + "$ref": "#/components/schemas/RaceClassEnum" + }, + { + "type": "null" + } + ] + }, + "driver_class": { + "oneOf": [ + { + "$ref": "#/components/schemas/RaceClassEnum" + }, + { + "type": "null" + } + ] + }, + "car_item_id": { + "oneOf": [ + { + "$ref": "#/components/schemas/ItemId" + }, + { + "type": "null" + } + ] + }, + "requires_stock_car": { + "type": "boolean" + }, + "requires_password": { + "type": "boolean" + }, + "join_fee": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "RacingTrackRecordsResponse": { + "required": [ + "records" + ], + "properties": { + "records": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RaceRecord" + } + } + }, + "type": "object" + }, + "RaceRecord": { + "required": [ + "driver_id", + "driver_name", + "car_item_id", + "lap_time", + "car_item_name" + ], + "properties": { + "driver_id": { + "$ref": "#/components/schemas/UserId" + }, + "driver_name": { + "type": "string" + }, + "car_item_id": { + "$ref": "#/components/schemas/ItemId" + }, + "lap_time": { + "type": "number", + "format": "float" + }, + "car_item_name": { + "type": "string" + } + }, + "type": "object" + }, + "RacerDetails": { + "required": [ + "driver_id", + "position", + "car_id", + "car_item_id", + "car_item_name", + "car_class", + "has_crashed", + "best_lap_time", + "race_time", + "time_ended" + ], + "properties": { + "driver_id": { + "$ref": "#/components/schemas/UserId" + }, + "position": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "car_id": { + "$ref": "#/components/schemas/RaceCarId" + }, + "car_item_id": { + "$ref": "#/components/schemas/ItemId" + }, + "car_item_name": { + "type": "string" + }, + "car_class": { + "$ref": "#/components/schemas/RaceClassEnum" + }, + "has_crashed": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + }, + "best_lap_time": { + "oneOf": [ + { + "type": "number", + "format": "float" + }, + { + "type": "null" + } + ] + }, + "race_time": { + "oneOf": [ + { + "type": "number", + "format": "float" + }, + { + "type": "null" + } + ] + }, + "time_ended": { + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RacingRaceDetailsResponse": { + "allOf": [ + { + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RacerDetails" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/Race" + } + ] + }, + "RacingSelectionName": { + "type": "string", + "enum": [ + "cars", + "carupgrades", + "lookup", + "race", + "races", + "records", + "timestamp", + "tracks" + ] + }, + "RacingLookupResponse": { + "required": [ + "selections" + ], + "properties": { + "selections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RacingSelectionName" + } + } + }, + "type": "object" + }, + "TornSubcrimesResponse": { + "required": [ + "subcrimes" + ], + "properties": { + "subcrimes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornSubcrime" + } + } + }, + "type": "object" + }, + "TornSubcrime": { + "required": [ + "id", + "name", + "nerve_cost" + ], + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + }, + "nerve_cost": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "TornCrimesResponse": { + "required": [ + "crimes" + ], + "properties": { + "crimes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornCrime" + } + } + }, + "type": "object" + }, + "TornCrime": { + "required": [ + "id", + "name", + "category_id", + "enhancer_id", + "enhancer_name", + "unique_outcomes_count", + "unique_outcomes_ids", + "notes" + ], + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + }, + "category_id": { + "type": "integer", + "format": "int32" + }, + "category_name": { + "type": "string" + }, + "enhancer_id": { + "type": "integer", + "format": "int32" + }, + "enhancer_name": { + "type": "string" + }, + "unique_outcomes_count": { + "type": "integer", + "format": "int32" + }, + "unique_outcomes_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "notes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + }, + "TornCalendarActivity": { + "required": [ + "title", + "description", + "start", + "end" + ], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "start": { + "type": "integer", + "format": "int32" + }, + "end": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "TornCalendarResponse": { + "required": [ + "calendar" + ], + "properties": { + "calendar": { + "required": [ + "competitions", + "events" + ], + "properties": { + "competitions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornCalendarActivity" + } + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornCalendarActivity" + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "TornHof": { + "required": [ + "id", + "username", + "faction_id", + "level", + "last_action", + "rank_name", + "rank_number", + "position", + "signed_up", + "age_in_days", + "value", + "rank" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "username": { + "type": "string" + }, + "faction_id": { + "$ref": "#/components/schemas/FactionId" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "last_action": { + "type": "integer", + "format": "int32" + }, + "rank_name": { + "type": "string" + }, + "rank_number": { + "type": "integer", + "format": "int32" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "signed_up": { + "type": "integer", + "format": "int32" + }, + "age_in_days": { + "type": "integer", + "format": "int32" + }, + "value": { + "description": "Value representing the chosen category. Traveltime is shown in seconds. If the chosen category is 'rank', the value is of type string. If the chosen category is 'racingskill', the value is of type float. Otherwise it is an integer." + }, + "rank": { + "type": "string" + } + }, + "type": "object" + }, + "TornHofResponse": { + "required": [ + "hof", + "_metadata" + ], + "properties": { + "hof": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornHof" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "FactionHofValues": { + "required": [ + "chain", + "chain_duration", + "respect" + ], + "properties": { + "chain": { + "description": "Maximum chain achieved by the faction. Null if chosen category is 'rank' or 'respect'.", + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "chain_duration": { + "description": "The duration of the chain. Null if chosen category is 'rank' or 'respect'.", + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + }, + "respect": { + "description": "Null if chosen category is 'chain'.", + "oneOf": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "TornFactionHof": { + "required": [ + "faction_id", + "name", + "members", + "position", + "rank", + "values" + ], + "properties": { + "faction_id": { + "$ref": "#/components/schemas/FactionId" + }, + "name": { + "type": "string" + }, + "members": { + "type": "integer", + "format": "int32" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "rank": { + "description": "The full rank title & division of the faction.", + "type": "string" + }, + "values": { + "$ref": "#/components/schemas/FactionHofValues" + } + }, + "type": "object" + }, + "TornFactionHofResponse": { + "required": [ + "factionhof", + "_metadata" + ], + "properties": { + "factionhof": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornFactionHof" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "TornLog": { + "required": [ + "id", + "title" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/LogId" + }, + "title": { + "type": "string" + } + }, + "type": "object" + }, + "TornLogCategory": { + "required": [ + "id", + "title" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/LogCategoryId" + }, + "title": { + "type": "string" + } + }, + "type": "object" + }, + "TornLogTypesResponse": { + "required": [ + "logtypes" + ], + "properties": { + "logtypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornLog" + } + } + }, + "type": "object" + }, + "TornLogCategoriesResponse": { + "required": [ + "logcategories" + ], + "properties": { + "logcategories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornLogCategory" + } + } + }, + "type": "object" + }, + "Bounty": { + "required": [ + "target_id", + "target_name", + "target_level", + "lister_id", + "lister_name", + "reward", + "reason", + "quantity", + "is_anonymous", + "valid_until" + ], + "properties": { + "target_id": { + "$ref": "#/components/schemas/UserId" + }, + "target_name": { + "type": "string" + }, + "target_level": { + "type": "integer", + "format": "int32" + }, + "lister_id": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserId" + }, + { + "type": "null" + } + ] + }, + "lister_name": { + "description": "If the bounty is anonymous this field is null.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "reward": { + "type": "integer", + "format": "int64" + }, + "reason": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "is_anonymous": { + "type": "boolean" + }, + "valid_until": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "AttackLogSummary": { + "required": [ + "id", + "name", + "hits", + "misses", + "damage" + ], + "properties": { + "id": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserId" + }, + { + "type": "null" + } + ] + }, + "name": { + "oneOf": [ + { + "description": "Name of the participant, could be null in stealthed attacks.", + "type": "string" + }, + { + "type": "null" + } + ] + }, + "hits": { + "type": "integer", + "format": "int32" + }, + "misses": { + "type": "integer", + "format": "int32" + }, + "damage": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "AttackLog": { + "required": [ + "text", + "timestamp", + "action", + "icon", + "attacker", + "defender" + ], + "properties": { + "text": { + "type": "string" + }, + "timestamp": { + "type": "integer", + "format": "int32" + }, + "action": { + "$ref": "#/components/schemas/AttackActionEnum" + }, + "icon": { + "type": "string" + }, + "attacker": { + "oneOf": [ + { + "description": "This value could be null in stealthed attacks.", + "required": [ + "id", + "name", + "item" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + }, + "item": { + "oneOf": [ + { + "description": "This object could be null if there was no item being used in this turn or during this effect.", + "properties": { + "id": { + "$ref": "#/components/schemas/ItemId" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + }, + "defender": { + "oneOf": [ + { + "description": "This value could be null in stealthed attacks.", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "AttackLogResponse": { + "required": [ + "attacklog", + "_metadata" + ], + "properties": { + "attacklog": { + "required": [ + "log", + "summary" + ], + "properties": { + "log": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AttackLog" + } + }, + "summary": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AttackLogSummary" + } + } + }, + "type": "object" + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "TornBountiesResponse": { + "required": [ + "bounties", + "_metadata" + ], + "properties": { + "bounties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Bounty" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "TornItemAmmo": { + "required": [ + "id", + "name", + "price", + "types" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/AmmoId" + }, + "name": { + "type": "string" + }, + "price": { + "type": "integer", + "format": "int64" + }, + "types": { + "description": "Types of ammo", + "type": "array", + "items": { + "$ref": "#/components/schemas/TornItemAmmoTypeEnum" + } + } + }, + "type": "object" + }, + "TornItemAmmoResponse": { + "required": [ + "itemammo" + ], + "properties": { + "itemammo": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornItemAmmo" + } + } + }, + "type": "object" + }, + "TornItemMods": { + "required": [ + "id", + "name", + "description", + "dual_fit", + "weapons" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ItemModId" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dual_fit": { + "description": "Whether the upgrade fits on dual weapons.", + "type": "boolean" + }, + "weapons": { + "description": "The weapon types this upgrade can be attached to.", + "type": "array", + "items": { + "$ref": "#/components/schemas/TornItemWeaponTypeEnum" + } + } + }, + "type": "object" + }, + "TornItemModsResponse": { + "required": [ + "itemmods" + ], + "properties": { + "itemmods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornItemMods" + } + } + }, + "type": "object" + }, + "TornItemBaseStats": { + "required": [ + "damage", + "accuracy", + "armor" + ], + "properties": { + "damage": { + "type": "integer", + "format": "int32" + }, + "accuracy": { + "type": "integer", + "format": "int32" + }, + "armor": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "TornItemWeaponDetails": { + "required": [ + "stealth_level", + "base_stats", + "category", + "ammo", + "mods" + ], + "properties": { + "stealth_level": { + "type": "number", + "format": "float" + }, + "base_stats": { + "$ref": "#/components/schemas/TornItemBaseStats" + }, + "category": { + "$ref": "#/components/schemas/TornItemWeaponCategoryEnum" + }, + "ammo": { + "oneOf": [ + { + "required": [ + "id", + "name", + "magazine_rounds", + "rate_of_fire" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/AmmoId" + }, + "name": { + "type": "string" + }, + "magazine_rounds": { + "type": "integer", + "format": "int32" + }, + "rate_of_fire": { + "required": [ + "minimum", + "maximum" + ], + "properties": { + "minimum": { + "type": "integer", + "format": "int32" + }, + "maximum": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + }, + "mods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemModId" + } + } + }, + "type": "object" + }, + "TornItemArmorCoverage": { + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "$ref": "#/components/schemas/TornItemArmorCoveragePartEnum" + }, + "value": { + "type": "number", + "format": "float" + } + }, + "type": "object" + }, + "TornItemArmorDetails": { + "required": [ + "coverage", + "base_stats" + ], + "properties": { + "coverage": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornItemArmorCoverage" + } + }, + "base_stats": { + "$ref": "#/components/schemas/TornItemBaseStats" + } + }, + "type": "object" + }, + "TornItem": { + "required": [ + "id", + "name", + "description", + "effect", + "requirement", + "image", + "type", + "sub_type", + "is_masked", + "is_tradable", + "is_found_in_city", + "value", + "circulation", + "details" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/ItemId" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "effect": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "requirement": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "image": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/TornItemTypeEnum" + }, + "sub_type": { + "oneOf": [ + { + "$ref": "#/components/schemas/TornItemWeaponTypeEnum" + }, + { + "type": "null" + } + ] + }, + "is_masked": { + "type": "boolean" + }, + "is_tradable": { + "type": "boolean" + }, + "is_found_in_city": { + "type": "boolean" + }, + "value": { + "required": [ + "vendor", + "buy_price", + "sell_price", + "market_price" + ], + "properties": { + "vendor": { + "oneOf": [ + { + "required": [ + "country", + "name" + ], + "properties": { + "country": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + }, + "buy_price": { + "oneOf": [ + { + "type": "integer", + "format": "int64" + }, + { + "type": "null" + } + ] + }, + "sell_price": { + "oneOf": [ + { + "type": "integer", + "format": "int64" + }, + { + "type": "null" + } + ] + }, + "market_price": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + }, + "circulation": { + "type": "integer", + "format": "int64" + }, + "details": { + "description": "If the item 'type' is 'Armor' then TornItemArmorDetails is returned.
If the item 'type' is 'Weapon' then TornItemWeaponDetails is returned.
Otherwise, null is returned.", + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/TornItemWeaponDetails" + }, + { + "$ref": "#/components/schemas/TornItemArmorDetails" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "TornItemsResponse": { + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornItem" + } + } + }, + "type": "object" + }, + "TornFactionTreeBranch": { + "required": [ + "id", + "name", + "upgrades" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/FactionBranchId" + }, + "name": { + "type": "string" + }, + "upgrades": { + "type": "array", + "items": { + "required": [ + "name", + "level", + "ability", + "challenge", + "cost" + ], + "properties": { + "name": { + "type": "string" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "ability": { + "type": "string" + }, + "cost": { + "type": "integer", + "format": "int32" + }, + "challenge": { + "oneOf": [ + { + "required": [ + "description", + "amount_required", + "stat" + ], + "properties": { + "description": { + "type": "string" + }, + "amount_required": { + "type": "integer", + "format": "int64" + }, + "stat": { + "$ref": "#/components/schemas/FactionStatEnum" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "TornFactionTree": { + "required": [ + "name", + "branches" + ], + "properties": { + "name": { + "type": "string" + }, + "branches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornFactionTreeBranch" + } + } + }, + "type": "object" + }, + "TornFactionTreeResponse": { + "required": [ + "factionTree" + ], + "properties": { + "factionTree": { + "$ref": "#/components/schemas/TornFactionTree" + } + }, + "type": "object" + }, + "TornSelectionName": { + "description": "The following selections will fallback to API v1 and may change at any time: 'bank','cards','cityshops','companies','competition','dirtybombs','education','gyms','honors','itemdetails','itemstats','medals','organisedcrimes','pawnshop','pokertables','properties','rackets','raidreport','raids','rockpaperscissors','searchforcash','shoplifting','stats','stocks','territory','territorynames','territorywarreport','territorywars'.", + "type": "string", + "enum": [ + "attacklog", + "bounties", + "calendar", + "crimes", + "factionHof", + "factiontree", + "hof", + "itemammo", + "itemmods", + "items", + "logcategories", + "logtypes", + "lookup", + "subcrimes", + "timestamp", + "bank", + "cards", + "cityshops", + "companies", + "competition", + "dirtybombs", + "education", + "gyms", + "honors", + "itemdetails", + "itemstats", + "medals", + "organisedcrimes", + "pawnshop", + "pokertables", + "properties", + "rackets", + "raidreport", + "raids", + "rankedwarreport", + "rankedwars", + "rockpaperscissors", + "searchforcash", + "shoplifting", + "stats", + "stocks", + "territory", + "territorynames", + "territorywarreport", + "territorywars" + ] + }, + "TornLookupResponse": { + "required": [ + "selections" + ], + "properties": { + "selections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornSelectionName" + } + } + }, + "type": "object" + }, + "PersonalStatsOther": { + "required": [ + "other" + ], + "properties": { + "other": { + "required": [ + "activity", + "awards", + "merits_bought", + "refills", + "donator_days", + "ranked_war_wins" + ], + "properties": { + "activity": { + "required": [ + "time", + "streak" + ], + "properties": { + "time": { + "description": "Time played in seconds", + "type": "integer", + "format": "int32" + }, + "streak": { + "required": [ + "best", + "current" + ], + "properties": { + "best": { + "type": "integer", + "format": "int32" + }, + "current": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "awards": { + "type": "integer", + "format": "int32" + }, + "merits_bought": { + "type": "integer", + "format": "int32" + }, + "refills": { + "required": [ + "energy", + "nerve", + "token" + ], + "properties": { + "energy": { + "type": "integer", + "format": "int32" + }, + "nerve": { + "type": "integer", + "format": "int32" + }, + "token": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "donator_days": { + "type": "integer", + "format": "int32" + }, + "ranked_war_wins": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsOtherPopular": { + "required": [ + "other" + ], + "properties": { + "other": { + "required": [ + "activity", + "awards", + "merits_bought", + "refills", + "donator_days", + "ranked_war_wins" + ], + "properties": { + "activity": { + "required": [ + "time", + "streak" + ], + "properties": { + "time": { + "description": "Time played in seconds", + "type": "integer", + "format": "int32" + }, + "streak": { + "required": [ + "best", + "current" + ], + "properties": { + "best": { + "type": "integer", + "format": "int32" + }, + "current": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "awards": { + "type": "integer", + "format": "int32" + }, + "merits_bought": { + "type": "integer", + "format": "int32" + }, + "refills": { + "required": [ + "energy", + "nerve" + ], + "properties": { + "energy": { + "type": "integer", + "format": "int32" + }, + "nerve": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "donator_days": { + "type": "integer", + "format": "int32" + }, + "ranked_war_wins": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsNetworthExtended": { + "required": [ + "networth" + ], + "properties": { + "networth": { + "required": [ + "total", + "wallet", + "vaults", + "bank", + "overseas_bank", + "points", + "inventory", + "display_case", + "bazaar", + "item_market", + "property", + "stock_market", + "auction_house", + "bookie", + "company", + "enlisted_cars", + "piggy_bank", + "pending", + "loans", + "unpaid_fees" + ], + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "wallet": { + "type": "integer", + "format": "int64" + }, + "vaults": { + "type": "integer", + "format": "int64" + }, + "bank": { + "type": "integer", + "format": "int64" + }, + "overseas_bank": { + "type": "integer", + "format": "int64" + }, + "points": { + "type": "integer", + "format": "int64" + }, + "inventory": { + "type": "integer", + "format": "int64" + }, + "display_case": { + "type": "integer", + "format": "int64" + }, + "bazaar": { + "type": "integer", + "format": "int64" + }, + "item_market": { + "type": "integer", + "format": "int64" + }, + "property": { + "type": "integer", + "format": "int64" + }, + "stock_market": { + "type": "integer", + "format": "int64" + }, + "auction_house": { + "type": "integer", + "format": "int64" + }, + "bookie": { + "type": "integer", + "format": "int64" + }, + "company": { + "type": "integer", + "format": "int64" + }, + "enlisted_cars": { + "type": "integer", + "format": "int64" + }, + "piggy_bank": { + "type": "integer", + "format": "int64" + }, + "pending": { + "type": "integer", + "format": "int64" + }, + "loans": { + "type": "integer", + "format": "int64" + }, + "unpaid_fees": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsNetworthPublic": { + "required": [ + "networth" + ], + "properties": { + "networth": { + "required": [ + "total" + ], + "properties": { + "total": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsRacing": { + "required": [ + "racing" + ], + "properties": { + "racing": { + "required": [ + "skill", + "points", + "races" + ], + "properties": { + "skill": { + "type": "integer", + "format": "int32" + }, + "points": { + "type": "integer", + "format": "int32" + }, + "races": { + "required": [ + "entered", + "won" + ], + "properties": { + "entered": { + "type": "integer", + "format": "int32" + }, + "won": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsMissions": { + "required": [ + "missions" + ], + "properties": { + "missions": { + "required": [ + "missions", + "contracts", + "credits" + ], + "properties": { + "missions": { + "type": "integer", + "format": "int32" + }, + "contracts": { + "required": [ + "total", + "duke" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "duke": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "credits": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsDrugs": { + "required": [ + "drugs" + ], + "properties": { + "drugs": { + "required": [ + "cannabis", + "ecstasy", + "ketamine", + "lsd", + "opium", + "pcp", + "shrooms", + "speed", + "vicodin", + "xanax", + "total", + "overdoses", + "rehabilitations" + ], + "properties": { + "cannabis": { + "type": "integer", + "format": "int32" + }, + "ecstasy": { + "type": "integer", + "format": "int32" + }, + "ketamine": { + "type": "integer", + "format": "int32" + }, + "lsd": { + "type": "integer", + "format": "int32" + }, + "opium": { + "type": "integer", + "format": "int32" + }, + "pcp": { + "type": "integer", + "format": "int32" + }, + "shrooms": { + "type": "integer", + "format": "int32" + }, + "speed": { + "type": "integer", + "format": "int32" + }, + "vicodin": { + "type": "integer", + "format": "int32" + }, + "xanax": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + }, + "overdoses": { + "type": "integer", + "format": "int32" + }, + "rehabilitations": { + "required": [ + "amount", + "fees" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int32" + }, + "fees": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsTravel": { + "required": [ + "travel" + ], + "properties": { + "travel": { + "required": [ + "total", + "time_spent", + "items_bought", + "hunting", + "attacks_won", + "defends_lost", + "argentina", + "canada", + "cayman_islands", + "china", + "hawaii", + "japan", + "mexico", + "united_arab_emirates", + "united_kingdom", + "south_africa", + "switzerland" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "time_spent": { + "type": "integer", + "format": "int32" + }, + "items_bought": { + "type": "integer", + "format": "int32" + }, + "hunting": { + "required": [ + "skill" + ], + "properties": { + "skill": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "attacks_won": { + "type": "integer", + "format": "int32" + }, + "defends_lost": { + "type": "integer", + "format": "int32" + }, + "argentina": { + "type": "integer", + "format": "int32" + }, + "canada": { + "type": "integer", + "format": "int32" + }, + "cayman_islands": { + "type": "integer", + "format": "int32" + }, + "china": { + "type": "integer", + "format": "int32" + }, + "hawaii": { + "type": "integer", + "format": "int32" + }, + "japan": { + "type": "integer", + "format": "int32" + }, + "mexico": { + "type": "integer", + "format": "int32" + }, + "united_arab_emirates": { + "type": "integer", + "format": "int32" + }, + "united_kingdom": { + "type": "integer", + "format": "int32" + }, + "south_africa": { + "type": "integer", + "format": "int32" + }, + "switzerland": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsTravelPopular": { + "required": [ + "travel" + ], + "properties": { + "travel": { + "required": [ + "total", + "time_spent" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "time_spent": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsItems": { + "required": [ + "items" + ], + "properties": { + "items": { + "required": [ + "found", + "trashed", + "used", + "viruses_coded" + ], + "properties": { + "found": { + "required": [ + "city", + "dump", + "easter_eggs" + ], + "properties": { + "city": { + "type": "integer", + "format": "int32" + }, + "dump": { + "type": "integer", + "format": "int32" + }, + "easter_eggs": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "trashed": { + "type": "integer", + "format": "int32" + }, + "used": { + "required": [ + "books", + "boosters", + "consumables", + "candy", + "alcohol", + "energy_drinks", + "stat_enhancers", + "easter_eggs" + ], + "properties": { + "books": { + "type": "integer", + "format": "int32" + }, + "boosters": { + "type": "integer", + "format": "int32" + }, + "consumables": { + "type": "integer", + "format": "int32" + }, + "candy": { + "type": "integer", + "format": "int32" + }, + "alcohol": { + "type": "integer", + "format": "int32" + }, + "energy_drinks": { + "type": "integer", + "format": "int32" + }, + "stat_enhancers": { + "type": "integer", + "format": "int32" + }, + "easter_eggs": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "viruses_coded": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsItemsPopular": { + "required": [ + "items" + ], + "properties": { + "items": { + "required": [ + "found", + "used" + ], + "properties": { + "found": { + "required": [ + "dump" + ], + "properties": { + "dump": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "used": { + "required": [ + "books", + "boosters", + "consumables", + "candy", + "alcohol", + "energy_drinks", + "stat_enhancers", + "easter_eggs" + ], + "properties": { + "books": { + "type": "integer", + "format": "int32" + }, + "boosters": { + "type": "integer", + "format": "int32" + }, + "consumables": { + "type": "integer", + "format": "int32" + }, + "candy": { + "type": "integer", + "format": "int32" + }, + "alcohol": { + "type": "integer", + "format": "int32" + }, + "energy_drinks": { + "type": "integer", + "format": "int32" + }, + "stat_enhancers": { + "type": "integer", + "format": "int32" + }, + "easter_eggs": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsInvestments": { + "required": [ + "investments" + ], + "properties": { + "investments": { + "required": [ + "bank", + "stocks" + ], + "properties": { + "bank": { + "required": [ + "total", + "profit", + "current", + "time_remaining" + ], + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "profit": { + "type": "integer", + "format": "int64" + }, + "current": { + "type": "integer", + "format": "int64" + }, + "time_remaining": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "stocks": { + "required": [ + "profits", + "losses", + "fees", + "net_profits", + "payouts" + ], + "properties": { + "profits": { + "type": "integer", + "format": "int64" + }, + "losses": { + "type": "integer", + "format": "int64" + }, + "fees": { + "type": "integer", + "format": "int64" + }, + "net_profits": { + "type": "integer", + "format": "int64" + }, + "payouts": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsBounties": { + "required": [ + "bounties" + ], + "properties": { + "bounties": { + "required": [ + "placed", + "collected", + "received" + ], + "properties": { + "placed": { + "required": [ + "amount", + "value" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int32" + }, + "value": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + }, + "collected": { + "required": [ + "amount", + "value" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int32" + }, + "value": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + }, + "received": { + "required": [ + "amount", + "value" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int32" + }, + "value": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsCrimesV2": { + "required": [ + "offenses", + "skills", + "version" + ], + "properties": { + "offenses": { + "required": [ + "vandalism", + "fraud", + "theft", + "counterfeiting", + "illicit_services", + "cybercrime", + "extortion", + "illegal_production", + "organized_crimes", + "total" + ], + "properties": { + "vandalism": { + "type": "integer", + "format": "int32" + }, + "fraud": { + "type": "integer", + "format": "int32" + }, + "theft": { + "type": "integer", + "format": "int32" + }, + "counterfeiting": { + "type": "integer", + "format": "int32" + }, + "illicit_services": { + "type": "integer", + "format": "int32" + }, + "cybercrime": { + "type": "integer", + "format": "int32" + }, + "extortion": { + "type": "integer", + "format": "int32" + }, + "illegal_production": { + "type": "integer", + "format": "int32" + }, + "organized_crimes": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "skills": { + "required": [ + "search_for_cash", + "bootlegging", + "graffiti", + "shoplifting", + "pickpocketing", + "card_skimming", + "burglary", + "hustling", + "disposal", + "cracking", + "forgery", + "scamming" + ], + "properties": { + "search_for_cash": { + "type": "integer", + "format": "int32" + }, + "bootlegging": { + "type": "integer", + "format": "int32" + }, + "graffiti": { + "type": "integer", + "format": "int32" + }, + "shoplifting": { + "type": "integer", + "format": "int32" + }, + "pickpocketing": { + "type": "integer", + "format": "int32" + }, + "card_skimming": { + "type": "integer", + "format": "int32" + }, + "burglary": { + "type": "integer", + "format": "int32" + }, + "hustling": { + "type": "integer", + "format": "int32" + }, + "disposal": { + "type": "integer", + "format": "int32" + }, + "cracking": { + "type": "integer", + "format": "int32" + }, + "forgery": { + "type": "integer", + "format": "int32" + }, + "scamming": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "PersonalStatsCrimesV1": { + "required": [ + "total", + "sell_illegal_goods", + "theft", + "auto_theft", + "drug_deals", + "computer", + "fraud", + "murder", + "other", + "organized_crimes", + "version" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "sell_illegal_goods": { + "type": "integer", + "format": "int32" + }, + "theft": { + "type": "integer", + "format": "int32" + }, + "auto_theft": { + "type": "integer", + "format": "int32" + }, + "drug_deals": { + "type": "integer", + "format": "int32" + }, + "computer": { + "type": "integer", + "format": "int32" + }, + "fraud": { + "type": "integer", + "format": "int32" + }, + "murder": { + "type": "integer", + "format": "int32" + }, + "other": { + "type": "integer", + "format": "int32" + }, + "organized_crimes": { + "type": "integer", + "format": "int32" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "PersonalStatsCrimesPopular": { + "required": [ + "crimes" + ], + "properties": { + "crimes": { + "required": [ + "total", + "version" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "version": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsCommunication": { + "required": [ + "communication" + ], + "properties": { + "communication": { + "required": [ + "mails_sent", + "classified_ads", + "personals" + ], + "properties": { + "mails_sent": { + "required": [ + "total", + "friends", + "faction", + "colleagues", + "spouse" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "friends": { + "type": "integer", + "format": "int32" + }, + "faction": { + "type": "integer", + "format": "int32" + }, + "colleagues": { + "type": "integer", + "format": "int32" + }, + "spouse": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "classified_ads": { + "type": "integer", + "format": "int32" + }, + "personals": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsFinishingHits": { + "required": [ + "finishing_hits" + ], + "properties": { + "finishing_hits": { + "required": [ + "heavy_artillery", + "machine_guns", + "rifles", + "sub_machine_guns", + "shotguns", + "pistols", + "temporary", + "piercing", + "slashing", + "clubbing", + "mechanical", + "hand_to_hand" + ], + "properties": { + "heavy_artillery": { + "type": "integer", + "format": "int32" + }, + "machine_guns": { + "type": "integer", + "format": "int32" + }, + "rifles": { + "type": "integer", + "format": "int32" + }, + "sub_machine_guns": { + "type": "integer", + "format": "int32" + }, + "shotguns": { + "type": "integer", + "format": "int32" + }, + "pistols": { + "type": "integer", + "format": "int32" + }, + "temporary": { + "type": "integer", + "format": "int32" + }, + "piercing": { + "type": "integer", + "format": "int32" + }, + "slashing": { + "type": "integer", + "format": "int32" + }, + "clubbing": { + "type": "integer", + "format": "int32" + }, + "mechanical": { + "type": "integer", + "format": "int32" + }, + "hand_to_hand": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsHospital": { + "required": [ + "hospital" + ], + "properties": { + "hospital": { + "required": [ + "times_hospitalized", + "medical_items_used", + "blood_withdrawn", + "reviving" + ], + "properties": { + "times_hospitalized": { + "type": "integer", + "format": "int32" + }, + "medical_items_used": { + "type": "integer", + "format": "int32" + }, + "blood_withdrawn": { + "type": "integer", + "format": "int32" + }, + "reviving": { + "required": [ + "skill", + "revives", + "revives_received" + ], + "properties": { + "skill": { + "type": "integer", + "format": "int32" + }, + "revives": { + "type": "integer", + "format": "int32" + }, + "revives_received": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsHospitalPopular": { + "required": [ + "hospital" + ], + "properties": { + "hospital": { + "required": [ + "medical_items_used", + "reviving" + ], + "properties": { + "medical_items_used": { + "type": "integer", + "format": "int32" + }, + "reviving": { + "required": [ + "skill", + "revives" + ], + "properties": { + "skill": { + "type": "integer", + "format": "int32" + }, + "revives": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsJail": { + "required": [ + "jail" + ], + "properties": { + "jail": { + "required": [ + "times_jailed", + "busts", + "bails" + ], + "properties": { + "times_jailed": { + "type": "integer", + "format": "int32" + }, + "busts": { + "required": [ + "success", + "fails" + ], + "properties": { + "success": { + "type": "integer", + "format": "int32" + }, + "fails": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "bails": { + "required": [ + "amount", + "fees" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int32" + }, + "fees": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsTrading": { + "required": [ + "trading" + ], + "properties": { + "trading": { + "required": [ + "items", + "trades", + "points", + "bazaar" + ], + "properties": { + "items": { + "required": [ + "bought", + "sent", + "auctions" + ], + "properties": { + "bought": { + "required": [ + "market", + "shops" + ], + "properties": { + "market": { + "type": "integer", + "format": "int32" + }, + "shops": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "auctions": { + "required": [ + "won", + "sold" + ], + "properties": { + "won": { + "type": "integer", + "format": "int32" + }, + "sold": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "sent": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "trades": { + "type": "integer", + "format": "int32" + }, + "points": { + "required": [ + "bought", + "sold" + ], + "properties": { + "bought": { + "type": "integer", + "format": "int32" + }, + "sold": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "bazaar": { + "required": [ + "customers", + "sales", + "profit" + ], + "properties": { + "customers": { + "type": "integer", + "format": "int32" + }, + "sales": { + "type": "integer", + "format": "int32" + }, + "profit": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsJobsPublic": { + "required": [ + "jobs" + ], + "properties": { + "jobs": { + "required": [ + "job_points_used", + "trains_received" + ], + "properties": { + "job_points_used": { + "type": "integer", + "format": "int32" + }, + "trains_received": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsJobsExtended": { + "required": [ + "jobs" + ], + "properties": { + "jobs": { + "required": [ + "job_points_used", + "trains_received", + "stats" + ], + "properties": { + "job_points_used": { + "type": "integer", + "format": "int32" + }, + "trains_received": { + "type": "integer", + "format": "int32" + }, + "stats": { + "required": [ + "manual", + "intelligence", + "endurance", + "total" + ], + "properties": { + "manual": { + "type": "integer", + "format": "int32" + }, + "intelligence": { + "type": "integer", + "format": "int32" + }, + "endurance": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsBattleStats": { + "required": [ + "battle_stats" + ], + "properties": { + "battle_stats": { + "required": [ + "strength", + "defense", + "speed", + "dexterity", + "total" + ], + "properties": { + "strength": { + "type": "integer", + "format": "int64" + }, + "defense": { + "type": "integer", + "format": "int64" + }, + "speed": { + "type": "integer", + "format": "int64" + }, + "dexterity": { + "type": "integer", + "format": "int64" + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsAttackingPublic": { + "required": [ + "attacking" + ], + "properties": { + "attacking": { + "required": [ + "attacks", + "defends", + "elo", + "unarmored_wins", + "highest_level_beaten", + "escpaes", + "killstreak", + "hits", + "damage", + "networth", + "ammunition", + "faction" + ], + "properties": { + "attacks": { + "required": [ + "won", + "lost", + "stalemate", + "assist", + "stealth" + ], + "properties": { + "won": { + "type": "integer", + "format": "int32" + }, + "lost": { + "type": "integer", + "format": "int32" + }, + "stalemate": { + "type": "integer", + "format": "int32" + }, + "assist": { + "type": "integer", + "format": "int32" + }, + "stealth": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "defends": { + "required": [ + "won", + "lost", + "stalemate", + "total" + ], + "properties": { + "won": { + "type": "integer", + "format": "int32" + }, + "lost": { + "type": "integer", + "format": "int32" + }, + "stalemate": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "elo": { + "type": "integer", + "format": "int32" + }, + "unarmored_wins": { + "type": "integer", + "format": "int32" + }, + "highest_level_beaten": { + "type": "integer", + "format": "int32" + }, + "escapes": { + "required": [ + "player", + "foes" + ], + "properties": { + "player": { + "type": "integer", + "format": "int32" + }, + "foes": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "killstreak": { + "required": [ + "best" + ], + "properties": { + "best": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "hits": { + "required": [ + "success", + "miss", + "critical", + "one_hit_kills" + ], + "properties": { + "success": { + "type": "integer", + "format": "int32" + }, + "miss": { + "type": "integer", + "format": "int32" + }, + "critical": { + "type": "integer", + "format": "int32" + }, + "one_hit_kills": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "damage": { + "required": [ + "total", + "best" + ], + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "best": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "networth": { + "required": [ + "money_mugged", + "largest_mug", + "items_looted" + ], + "properties": { + "money_mugged": { + "type": "integer", + "format": "int64" + }, + "largest_mug": { + "type": "integer", + "format": "int64" + }, + "items_looted": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ammunition": { + "required": [ + "total", + "special", + "hollow_point", + "tracer", + "piercing", + "incendiary" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "special": { + "type": "integer", + "format": "int32" + }, + "hollow_point": { + "type": "integer", + "format": "int32" + }, + "tracer": { + "type": "integer", + "format": "int32" + }, + "piercing": { + "type": "integer", + "format": "int32" + }, + "incendiary": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "faction": { + "required": [ + "respect", + "retaliations", + "ranked_war_hits", + "raid_hits", + "territory" + ], + "properties": { + "respect": { + "type": "integer", + "format": "int32" + }, + "retaliations": { + "type": "integer", + "format": "int32" + }, + "ranked_war_hits": { + "type": "integer", + "format": "int32" + }, + "raid_hits": { + "type": "integer", + "format": "int32" + }, + "territory": { + "required": [ + "wall_joins", + "wall_clears", + "wall_time" + ], + "properties": { + "wall_joins": { + "type": "integer", + "format": "int32" + }, + "wall_clears": { + "type": "integer", + "format": "int32" + }, + "wall_time": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsAttackingExtended": { + "required": [ + "attacking" + ], + "properties": { + "attacking": { + "required": [ + "attacks", + "defends", + "elo", + "unarmored_wins", + "highest_level_beaten", + "escapes", + "killstreak", + "hits", + "damage", + "networth", + "ammunition", + "faction" + ], + "properties": { + "attacks": { + "required": [ + "won", + "lost", + "stalemate", + "assist", + "stealth" + ], + "properties": { + "won": { + "type": "integer", + "format": "int32" + }, + "lost": { + "type": "integer", + "format": "int32" + }, + "stalemate": { + "type": "integer", + "format": "int32" + }, + "assist": { + "type": "integer", + "format": "int32" + }, + "stealth": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "defends": { + "required": [ + "won", + "lost", + "stalemate", + "total" + ], + "properties": { + "won": { + "type": "integer", + "format": "int32" + }, + "lost": { + "type": "integer", + "format": "int32" + }, + "stalemate": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "elo": { + "type": "integer", + "format": "int32" + }, + "unarmored_wins": { + "type": "integer", + "format": "int32" + }, + "highest_level_beaten": { + "type": "integer", + "format": "int32" + }, + "escapes": { + "required": [ + "player", + "foes" + ], + "properties": { + "player": { + "type": "integer", + "format": "int32" + }, + "foes": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "killstreak": { + "required": [ + "best", + "current" + ], + "properties": { + "best": { + "type": "integer", + "format": "int32" + }, + "current": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "hits": { + "required": [ + "success", + "miss", + "critical", + "one_hit_kills" + ], + "properties": { + "success": { + "type": "integer", + "format": "int32" + }, + "miss": { + "type": "integer", + "format": "int32" + }, + "critical": { + "type": "integer", + "format": "int32" + }, + "one_hit_kills": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "damage": { + "required": [ + "total", + "best" + ], + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "best": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "networth": { + "required": [ + "money_mugged", + "largest_mug", + "items_looted" + ], + "properties": { + "money_mugged": { + "type": "integer", + "format": "int64" + }, + "largest_mug": { + "type": "integer", + "format": "int64" + }, + "items_looted": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ammunition": { + "required": [ + "total", + "special", + "hollow_point", + "tracer", + "piercing", + "incendiary" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "special": { + "type": "integer", + "format": "int32" + }, + "hollow_point": { + "type": "integer", + "format": "int32" + }, + "tracer": { + "type": "integer", + "format": "int32" + }, + "piercing": { + "type": "integer", + "format": "int32" + }, + "incendiary": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "faction": { + "required": [ + "respect", + "retaliations", + "ranked_war_hits", + "raid_hits", + "territory" + ], + "properties": { + "respect": { + "type": "integer", + "format": "int32" + }, + "retaliations": { + "type": "integer", + "format": "int32" + }, + "ranked_war_hits": { + "type": "integer", + "format": "int32" + }, + "raid_hits": { + "type": "integer", + "format": "int32" + }, + "territory": { + "required": [ + "wall_joins", + "wall_clears", + "wall_time" + ], + "properties": { + "wall_joins": { + "type": "integer", + "format": "int32" + }, + "wall_clears": { + "type": "integer", + "format": "int32" + }, + "wall_time": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsAttackingPopular": { + "required": [ + "attacking" + ], + "properties": { + "attacking": { + "required": [ + "attacks", + "defends", + "elo", + "escapes", + "killstreak", + "hits", + "damage", + "networth", + "ammunition", + "faction" + ], + "properties": { + "attacks": { + "required": [ + "won", + "lost", + "stalemate", + "assist" + ], + "properties": { + "won": { + "type": "integer", + "format": "int32" + }, + "lost": { + "type": "integer", + "format": "int32" + }, + "stalemate": { + "type": "integer", + "format": "int32" + }, + "assist": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "defends": { + "required": [ + "won", + "lost", + "stalemate" + ], + "properties": { + "won": { + "type": "integer", + "format": "int32" + }, + "lost": { + "type": "integer", + "format": "int32" + }, + "stalemate": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "elo": { + "type": "integer", + "format": "int32" + }, + "escapes": { + "required": [ + "player", + "foes" + ], + "properties": { + "player": { + "type": "integer", + "format": "int32" + }, + "foes": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "killstreak": { + "required": [ + "best" + ], + "properties": { + "best": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "hits": { + "required": [ + "success", + "miss", + "critical", + "one_hit_kills" + ], + "properties": { + "success": { + "type": "integer", + "format": "int32" + }, + "miss": { + "type": "integer", + "format": "int32" + }, + "critical": { + "type": "integer", + "format": "int32" + }, + "one_hit_kills": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "damage": { + "required": [ + "best", + "total" + ], + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "best": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "networth": { + "required": [ + "money_mugged", + "largest_mug", + "items_looted" + ], + "properties": { + "money_mugged": { + "type": "integer", + "format": "int64" + }, + "largest_mug": { + "type": "integer", + "format": "int64" + }, + "items_looted": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "ammunition": { + "required": [ + "total", + "special", + "hollow_point", + "tracer", + "piercing", + "incendiary" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "special": { + "type": "integer", + "format": "int32" + }, + "hollow_point": { + "type": "integer", + "format": "int32" + }, + "tracer": { + "type": "integer", + "format": "int32" + }, + "piercing": { + "type": "integer", + "format": "int32" + }, + "incendiary": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "faction": { + "required": [ + "respect", + "ranked_war_hits" + ], + "properties": { + "respect": { + "type": "integer", + "format": "int32" + }, + "ranked_war_hits": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "PersonalStatsHistoricStat": { + "required": [ + "name", + "value", + "timestamp" + ], + "properties": { + "name": { + "description": "Requested stat name", + "type": "string" + }, + "value": { + "type": "integer", + "format": "int64" + }, + "timestamp": { + "description": "Timestamp when the stat was last updated", + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserPersonalStatsHistoric": { + "required": [ + "personalstats" + ], + "properties": { + "personalstats": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PersonalStatsHistoricStat" + } + } + }, + "type": "object" + }, + "PersonalStatsCrimes": { + "description": "Response for PersonalStatsCrimes depends on which crime version user is currently.", + "required": [ + "crimes" + ], + "properties": { + "crimes": { + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/PersonalStatsCrimesV1" + }, + { + "$ref": "#/components/schemas/PersonalStatsCrimesV2" + } + ] + } + }, + "type": "object" + }, + "UserPersonalStatsPopular": { + "required": [ + "personalstats" + ], + "properties": { + "personalstats": { + "allOf": [ + { + "$ref": "#/components/schemas/PersonalStatsAttackingPopular" + }, + { + "$ref": "#/components/schemas/PersonalStatsJobsPublic" + }, + { + "$ref": "#/components/schemas/PersonalStatsHospitalPopular" + }, + { + "$ref": "#/components/schemas/PersonalStatsCrimesPopular" + }, + { + "$ref": "#/components/schemas/PersonalStatsItemsPopular" + }, + { + "$ref": "#/components/schemas/PersonalStatsTravelPopular" + }, + { + "$ref": "#/components/schemas/PersonalStatsDrugs" + }, + { + "$ref": "#/components/schemas/PersonalStatsNetworthPublic" + }, + { + "$ref": "#/components/schemas/PersonalStatsOtherPopular" + } + ] + } + }, + "type": "object" + }, + "UserPersonalStatsCategory": { + "description": "Schema name corresponds to the requested category", + "required": [ + "personalstats" + ], + "properties": { + "personalstats": { + "oneOf": [ + { + "$ref": "#/components/schemas/PersonalStatsAttackingPublic" + }, + { + "$ref": "#/components/schemas/PersonalStatsJobsPublic" + }, + { + "$ref": "#/components/schemas/PersonalStatsTrading" + }, + { + "$ref": "#/components/schemas/PersonalStatsJail" + }, + { + "$ref": "#/components/schemas/PersonalStatsHospital" + }, + { + "$ref": "#/components/schemas/PersonalStatsFinishingHits" + }, + { + "$ref": "#/components/schemas/PersonalStatsCommunication" + }, + { + "$ref": "#/components/schemas/PersonalStatsCrimes" + }, + { + "$ref": "#/components/schemas/PersonalStatsBounties" + }, + { + "$ref": "#/components/schemas/PersonalStatsItems" + }, + { + "$ref": "#/components/schemas/PersonalStatsTravel" + }, + { + "$ref": "#/components/schemas/PersonalStatsDrugs" + }, + { + "$ref": "#/components/schemas/PersonalStatsMissions" + }, + { + "$ref": "#/components/schemas/PersonalStatsRacing" + }, + { + "$ref": "#/components/schemas/PersonalStatsNetworthPublic" + }, + { + "$ref": "#/components/schemas/PersonalStatsOther" + } + ] + } + }, + "type": "object" + }, + "UserPersonalStatsFull": { + "required": [ + "personalstats" + ], + "properties": { + "personalstats": { + "allOf": [ + { + "$ref": "#/components/schemas/PersonalStatsAttackingExtended" + }, + { + "$ref": "#/components/schemas/PersonalStatsBattleStats" + }, + { + "$ref": "#/components/schemas/PersonalStatsJobsExtended" + }, + { + "$ref": "#/components/schemas/PersonalStatsTrading" + }, + { + "$ref": "#/components/schemas/PersonalStatsJail" + }, + { + "$ref": "#/components/schemas/PersonalStatsHospital" + }, + { + "$ref": "#/components/schemas/PersonalStatsFinishingHits" + }, + { + "$ref": "#/components/schemas/PersonalStatsCommunication" + }, + { + "$ref": "#/components/schemas/PersonalStatsCrimes" + }, + { + "$ref": "#/components/schemas/PersonalStatsBounties" + }, + { + "$ref": "#/components/schemas/PersonalStatsInvestments" + }, + { + "$ref": "#/components/schemas/PersonalStatsItems" + }, + { + "$ref": "#/components/schemas/PersonalStatsTravel" + }, + { + "$ref": "#/components/schemas/PersonalStatsDrugs" + }, + { + "$ref": "#/components/schemas/PersonalStatsMissions" + }, + { + "$ref": "#/components/schemas/PersonalStatsRacing" + }, + { + "$ref": "#/components/schemas/PersonalStatsNetworthExtended" + }, + { + "$ref": "#/components/schemas/PersonalStatsOther" + } + ] + } + }, + "type": "object" + }, + "UserPersonalStatsFullPublic": { + "required": [ + "personalstats" + ], + "properties": { + "personalstats": { + "allOf": [ + { + "$ref": "#/components/schemas/PersonalStatsAttackingPublic" + }, + { + "$ref": "#/components/schemas/PersonalStatsJobsPublic" + }, + { + "$ref": "#/components/schemas/PersonalStatsTrading" + }, + { + "$ref": "#/components/schemas/PersonalStatsJail" + }, + { + "$ref": "#/components/schemas/PersonalStatsHospital" + }, + { + "$ref": "#/components/schemas/PersonalStatsFinishingHits" + }, + { + "$ref": "#/components/schemas/PersonalStatsCommunication" + }, + { + "$ref": "#/components/schemas/PersonalStatsCrimes" + }, + { + "$ref": "#/components/schemas/PersonalStatsBounties" + }, + { + "$ref": "#/components/schemas/PersonalStatsItems" + }, + { + "$ref": "#/components/schemas/PersonalStatsTravel" + }, + { + "$ref": "#/components/schemas/PersonalStatsDrugs" + }, + { + "$ref": "#/components/schemas/PersonalStatsMissions" + }, + { + "$ref": "#/components/schemas/PersonalStatsRacing" + }, + { + "$ref": "#/components/schemas/PersonalStatsNetworthPublic" + }, + { + "$ref": "#/components/schemas/PersonalStatsOther" + } + ] + } + }, + "type": "object" + }, + "UserPersonalStatsResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserPersonalStatsFull" + }, + { + "$ref": "#/components/schemas/UserPersonalStatsFullPublic" + }, + { + "$ref": "#/components/schemas/UserPersonalStatsCategory" + }, + { + "$ref": "#/components/schemas/UserPersonalStatsPopular" + }, + { + "$ref": "#/components/schemas/UserPersonalStatsHistoric" + } + ] + }, + "PersonalStatsCategoryEnum": { + "type": "string", + "enum": [ + "all", + "popular", + "attacking", + "battle_stats", + "jobs", + "trading", + "jail", + "hospital", + "finishing_hits", + "communication", + "crimes", + "bounties", + "investments", + "items", + "travel", + "drugs", + "missions", + "racing", + "networth", + "other" + ] + }, + "PersonalStatsStatName": { + "type": "string", + "enum": [ + "attackswon", + "attackslost", + "attacksdraw", + "attacksassisted", + "defendswon", + "defendslost", + "defendsstalemated", + "elo", + "yourunaway", + "theyrunaway", + "unarmoredwon", + "bestkillstreak", + "attackhits", + "attackmisses", + "attackdamage", + "bestdamage", + "onehitkills", + "attackcriticalhits", + "roundsfired", + "specialammoused", + "hollowammoused", + "tracerammoused", + "piercingammoused", + "incendiaryammoused", + "attacksstealthed", + "retals", + "moneymugged", + "largestmug", + "itemslooted", + "highestbeaten", + "respectforfaction", + "rankedwarhits", + "raidhits", + "territoryjoins", + "territoryclears", + "territorytime", + "jobpointsused", + "trainsreceived", + "marketitemsbought", + "auctionswon", + "auctionsells", + "itemssent", + "trades", + "cityitemsbought", + "pointsbought", + "pointssold", + "bazaarcustomers", + "bazaarsales", + "bazaarprofit", + "jailed", + "peoplebusted", + "failedbusts", + "peoplebought", + "peopleboughtspent", + "hospital", + "medicalitemsused", + "bloodwithdrawn", + "reviveskill", + "revives", + "revivesreceived", + "heavyhits", + "machinehits", + "riflehits", + "smghits", + "shotgunhits", + "pistolhits", + "temphits", + "piercinghits", + "slashinghits", + "clubbinghits", + "mechanicalhits", + "h2hhits", + "mailssent", + "friendmailssent", + "factionmailssent", + "companymailssent", + "spousemailssent", + "classifiedadsplaced", + "personalsplaced", + "criminaloffensesold", + "sellillegalgoods", + "theftold", + "autotheftcrime", + "drugdealscrime", + "computercrime", + "fraudold", + "murdercrime", + "othercrime", + "organizedcrimes", + "bountiesplaced", + "totalbountyspent", + "bountiescollected", + "totalbountyreward", + "bountiesreceived", + "receivedbountyvalue", + "cityfinds", + "dumpfinds", + "itemsdumped", + "booksread", + "boostersused", + "consumablesused", + "candyused", + "alcoholused", + "energydrinkused", + "statenhancersused", + "eastereggsfound", + "eastereggsused", + "virusescoded", + "traveltimes", + "timespenttraveling", + "itemsboughtabroad", + "attackswonabroad", + "defendslostabroad", + "argtravel", + "mextravel", + "uaetravel", + "hawtravel", + "japtravel", + "uktravel", + "satravel", + "switravel", + "chitravel", + "cantravel", + "caytravel", + "drugsused", + "overdosed", + "rehabs", + "rehabcost", + "cantaken", + "exttaken", + "kettaken", + "lsdtaken", + "opitaken", + "pcptaken", + "shrtaken", + "spetaken", + "victaken", + "xantaken", + "missionscompleted", + "contractscompleted", + "dukecontractscompleted", + "missioncreditsearned", + "racingskill", + "racingpointsearned", + "racesentered", + "raceswon", + "networth", + "timeplayed", + "activestreak", + "bestactivestreak", + "awards", + "refills", + "nerverefills", + "tokenrefills", + "meritsbought", + "daysbeendonator", + "criminaloffenses", + "vandalism", + "theft", + "counterfeiting", + "fraud", + "illicitservices", + "cybercrime", + "extortion", + "illegalproduction", + "currentkillstreak", + "strength", + "defense", + "speed", + "dexterity", + "totalstats", + "manuallabor", + "intelligence", + "endurance", + "totalworkingstats", + "moneyinvested", + "investedprofit", + "investamount", + "banktimeleft", + "stockprofits", + "stocklosses", + "stockfees", + "stocknetprofits", + "stockpayouts", + "networthwallet", + "networthvault", + "networthbank", + "networthcayman", + "networthpoints", + "networthitems", + "networthdisplaycase", + "networthbazaar", + "networthitemmarket", + "networthproperties", + "networthstockmarket", + "networthauctionhouse", + "networthbookie", + "networthcompany", + "networthenlistedcars", + "networthpiggybank", + "networthpending", + "networthloan", + "networthunpaidfees", + "huntingskill", + "searchforcashskill", + "bootleggingskill", + "graffitiskill", + "shopliftingskill", + "pickpocketingskill", + "cardskimmingskill", + "burglaryskill", + "hustlingskill", + "disposalskill", + "crackingskill", + "forgeryskill", + "scammingskill" + ] + }, + "UserCrimeDetailsBootlegging": { + "required": [ + "online_store", + "dvd_sales", + "dvd_copies" + ], + "properties": { + "online_store": { + "description": "Online store statistics.", + "required": [ + "earnings", + "visits", + "customers", + "sales" + ], + "properties": { + "earnings": { + "type": "integer", + "format": "int32" + }, + "visits": { + "type": "integer", + "format": "int32" + }, + "customers": { + "type": "integer", + "format": "int32" + }, + "sales": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "dvd_sales": { + "description": "DVD sales statistics.", + "required": [ + "action", + "comedy", + "drama", + "fantasy", + "horror", + "romance", + "thriller", + "sci-fi", + "total", + "earnings" + ], + "properties": { + "action": { + "type": "integer", + "format": "int32" + }, + "comedy": { + "type": "integer", + "format": "int32" + }, + "drama": { + "type": "integer", + "format": "int32" + }, + "fantasy": { + "type": "integer", + "format": "int32" + }, + "horror": { + "type": "integer", + "format": "int32" + }, + "romance": { + "type": "integer", + "format": "int32" + }, + "thriller": { + "type": "integer", + "format": "int32" + }, + "sci-fi": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + }, + "earnings": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "dvds_copied": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeDetailsGraffiti": { + "required": [ + "cans_used", + "most_graffiti_in_one_area", + "most_graffiti_simultaneously", + "graffiti_removed", + "cost_to_city" + ], + "properties": { + "cans_used": { + "type": "integer", + "format": "int32" + }, + "most_graffiti_in_one_area": { + "type": "integer", + "format": "int32" + }, + "most_graffiti_simultaneously": { + "type": "integer", + "format": "int32" + }, + "graffiti_removed": { + "type": "integer", + "format": "int32" + }, + "cost_to_city": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeDetailsShoplifting": { + "required": [ + "average_notoriety" + ], + "properties": { + "average_notoriety": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeDetailsCardSkimming": { + "required": [ + "card_details", + "skimmers" + ], + "properties": { + "card_details": { + "required": [ + "recoverable", + "recovered", + "sold", + "lost", + "areas" + ], + "properties": { + "recoverable": { + "type": "integer", + "format": "int32" + }, + "recovered": { + "type": "integer", + "format": "int32" + }, + "sold": { + "type": "integer", + "format": "int32" + }, + "lost": { + "type": "integer", + "format": "int32" + }, + "areas": { + "type": "array", + "items": { + "required": [ + "id", + "amount" + ], + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "amount": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "skimmers": { + "required": [ + "active", + "most_lucrative", + "oldest_recovered", + "lost" + ], + "properties": { + "active": { + "type": "integer", + "format": "int32" + }, + "most_lucrative": { + "type": "integer", + "format": "int32" + }, + "oldest_recovered": { + "type": "integer", + "format": "int32" + }, + "lost": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "UserCrimeDetailsHustling": { + "required": [ + "total_audience_gathered", + "biggest_money_won", + "shill_money_collected", + "pickpocket_money_collected" + ], + "properties": { + "total_audience_gathered": { + "type": "integer", + "format": "int32" + }, + "biggest_money_won": { + "type": "integer", + "format": "int32" + }, + "shill_money_collected": { + "type": "integer", + "format": "int32" + }, + "pickpocket_money_collected": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeDetailsCracking": { + "required": [ + "brute_force_cycles", + "encryption_layers_broken", + "highest_mips", + "chars_guessed", + "chars_guessed_total" + ], + "properties": { + "brute_force_cycles": { + "type": "integer", + "format": "int32" + }, + "encryption_layers_broken": { + "type": "integer", + "format": "int32" + }, + "highest_mips": { + "type": "integer", + "format": "int32" + }, + "chars_guessed": { + "type": "integer", + "format": "int32" + }, + "chars_guessed_total": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeDetailsScamming": { + "required": [ + "most_responses", + "zones", + "concerns", + "payouts", + "emails" + ], + "properties": { + "most_responses": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "zones": { + "required": [ + "red", + "neutral", + "concern", + "sensitivity", + "temptation", + "hesitation", + "low_reward", + "medium_reward", + "high_reward" + ], + "properties": { + "red": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "neutral": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "concern": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "sensitivity": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "temptation": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "hesitation": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "low_reward": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "medium_reward": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "high_reward": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + "type": "object" + }, + "concerns": { + "required": [ + "attempts", + "resolved" + ], + "properties": { + "attempts": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "resolved": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + "type": "object" + }, + "payouts": { + "required": [ + "low", + "medium", + "high" + ], + "properties": { + "low": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "medium": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "high": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + "type": "object" + }, + "emails": { + "required": [ + "scraper", + "phisher" + ], + "properties": { + "scraper": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "phisher": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + "type": "object" + } + }, + "type": "object" + }, + "UserSubcrime": { + "required": [ + "id", + "total", + "success", + "fail" + ], + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + }, + "success": { + "type": "integer", + "format": "int32" + }, + "fail": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeRewardAmmo": { + "required": [ + "standard", + "special" + ], + "properties": { + "standard": { + "type": "integer", + "format": "int32" + }, + "special": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeRewardItem": { + "required": [ + "id", + "amount" + ], + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "amount": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeRewards": { + "required": [ + "money", + "ammo", + "items" + ], + "properties": { + "money": { + "type": "integer", + "format": "int64" + }, + "ammo": { + "$ref": "#/components/schemas/UserCrimeRewardAmmo" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserCrimeRewardItem" + } + } + }, + "type": "object" + }, + "UserCrimeAttempts": { + "required": [ + "total", + "success", + "fail", + "critical_fail", + "subcrimes" + ], + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "success": { + "type": "integer", + "format": "int32" + }, + "fail": { + "type": "integer", + "format": "int32" + }, + "critical_fail": { + "type": "integer", + "format": "int32" + }, + "subcrimes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserSubcrime" + } + } + }, + "type": "object" + }, + "UserCrimeUniquesRewardMoney": { + "required": [ + "min", + "max" + ], + "properties": { + "min": { + "type": "integer", + "format": "int32" + }, + "max": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserCrimeUniquesRewardAmmo": { + "required": [ + "amount", + "type" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int32" + }, + "type": { + "$ref": "#/components/schemas/UserCrimeUniquesRewardAmmoEnum" + } + }, + "type": "object" + }, + "UserCrimeUniquesReward": { + "required": [ + "items", + "money", + "ammo" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserCrimeRewardItem" + } + }, + "money": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserCrimeUniquesRewardMoney" + }, + { + "type": "null" + } + ] + }, + "ammo": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserCrimeUniquesRewardAmmo" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "UserCrimeUniques": { + "required": [ + "id", + "rewards" + ], + "properties": { + "id": { + "description": "Unique result id.", + "type": "integer", + "format": "int64" + }, + "rewards": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserCrimeUniquesReward" + } + } + }, + "type": "object" + }, + "UserCrimesResponse": { + "required": [ + "crimes" + ], + "properties": { + "crimes": { + "$ref": "#/components/schemas/UserCrime" + } + }, + "type": "object" + }, + "UserCrime": { + "required": [ + "nerve_spent", + "skill", + "progression_bonus", + "rewards", + "attempts", + "uniques", + "miscellaneous" + ], + "properties": { + "nerve_spent": { + "type": "integer", + "format": "int32" + }, + "skill": { + "type": "integer", + "format": "int32" + }, + "progression_bonus": { + "type": "integer", + "format": "int32" + }, + "rewards": { + "$ref": "#/components/schemas/UserCrimeRewards" + }, + "attempts": { + "$ref": "#/components/schemas/UserCrimeAttempts" + }, + "uniques": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserCrimeUniques" + } + }, + "miscellaneous": { + "description": " Miscellaneous stats for specific crime. Results differ based on the cat id.", + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/UserCrimeDetailsBootlegging" + }, + { + "$ref": "#/components/schemas/UserCrimeDetailsGraffiti" + }, + { + "$ref": "#/components/schemas/UserCrimeDetailsShoplifting" + }, + { + "$ref": "#/components/schemas/UserCrimeDetailsCardSkimming" + }, + { + "$ref": "#/components/schemas/UserCrimeDetailsHustling" + }, + { + "$ref": "#/components/schemas/UserCrimeDetailsCracking" + }, + { + "$ref": "#/components/schemas/UserCrimeDetailsScamming" + } + ] + } + }, + "type": "object" + }, + "UserRacesResponse": { + "required": [ + "races", + "_metadata" + ], + "properties": { + "races": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RacingRaceDetailsResponse" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "UserRaceCarDetails": { + "allOf": [ + { + "required": [ + "id", + "name", + "worth", + "points_spent", + "races_entered", + "races_won", + "is_removed", + "parts" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/RaceCarId" + }, + "name": { + "type": "string" + }, + "worth": { + "type": "integer", + "format": "int64" + }, + "points_spent": { + "type": "integer", + "format": "int32" + }, + "races_entered": { + "type": "integer", + "format": "int32" + }, + "races_won": { + "type": "integer", + "format": "int32" + }, + "is_removed": { + "type": "boolean" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RaceCarUpgradeId" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/RaceCar" + } + ] + }, + "UserEnlistedCarsResponse": { + "required": [ + "enlistedcars" + ], + "properties": { + "enlistedcars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserRaceCarDetails" + } + } + }, + "type": "object" + }, + "UserForumPostsResponse": { + "required": [ + "forumPosts", + "_metadata" + ], + "properties": { + "forumPosts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumPost" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "UserForumThreadsResponse": { + "required": [ + "forumThreads", + "_metadata" + ], + "properties": { + "forumThreads": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumThreadUserExtended" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "UserForumSubscribedThreadsResponse": { + "required": [ + "forumSbuscribedThreads" + ], + "properties": { + "forumSubscribedThreads": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumSubscribedThread" + } + } + }, + "type": "object" + }, + "UserForumFeedResponse": { + "required": [ + "forumFeed" + ], + "properties": { + "forumFeed": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumFeed" + } + } + }, + "type": "object" + }, + "UserForumFriendsResponse": { + "required": [ + "forumFriends" + ], + "properties": { + "forumFriends": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForumFeed" + } + } + }, + "type": "object" + }, + "HofValue": { + "required": [ + "value", + "rank" + ], + "properties": { + "value": { + "type": "integer", + "format": "int32" + }, + "rank": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "HofValueString": { + "required": [ + "value", + "rank" + ], + "properties": { + "value": { + "type": "string" + }, + "rank": { + "type": "integer", + "format": "int32" + } + }, + "type": "object" + }, + "UserHofStats": { + "required": [ + "attacks", + "busts", + "defends", + "networth", + "offences", + "revives", + "level", + "rank", + "awards", + "racing_skill", + "racing_points", + "racing_wins", + "travel_time", + "working_stats", + "battle_stats" + ], + "properties": { + "attacks": { + "$ref": "#/components/schemas/HofValue" + }, + "busts": { + "$ref": "#/components/schemas/HofValue" + }, + "defends": { + "$ref": "#/components/schemas/HofValue" + }, + "networth": { + "$ref": "#/components/schemas/HofValue" + }, + "offences": { + "$ref": "#/components/schemas/HofValue" + }, + "revives": { + "$ref": "#/components/schemas/HofValue" + }, + "level": { + "$ref": "#/components/schemas/HofValue" + }, + "rank": { + "$ref": "#/components/schemas/HofValue" + }, + "awards": { + "$ref": "#/components/schemas/HofValue" + }, + "racing_skill": { + "$ref": "#/components/schemas/HofValue" + }, + "racing_points": { + "$ref": "#/components/schemas/HofValue" + }, + "racing_wins": { + "$ref": "#/components/schemas/HofValue" + }, + "travel_time": { + "$ref": "#/components/schemas/HofValue" + }, + "working_stats": { + "$ref": "#/components/schemas/HofValue" + }, + "battle_stats": { + "oneOf": [ + { + "$ref": "#/components/schemas/HofValue" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "UserHofResponse": { + "required": [ + "hof" + ], + "properties": { + "hof": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserHofStats" + } + } + }, + "type": "object" + }, + "UserCalendar": { + "required": [ + "start_time" + ], + "properties": { + "start_time": { + "description": "Event start time displayed in TCT.", + "type": "string" + } + }, + "type": "object" + }, + "UserCalendarResponse": { + "required": [ + "calendar" + ], + "properties": { + "calendar": { + "$ref": "#/components/schemas/UserCalendar" + } + }, + "type": "object" + }, + "UserBountiesResponse": { + "required": [ + "bounties" + ], + "properties": { + "bounties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Bounty" + } + } + }, + "type": "object" + }, + "UserJobRanks": { + "required": [ + "army", + "grocer", + "casino", + "medical", + "law", + "education" + ], + "properties": { + "army": { + "$ref": "#/components/schemas/JobPositionArmyEnum" + }, + "grocer": { + "$ref": "#/components/schemas/JobPositionGrocerEnum" + }, + "casino": { + "$ref": "#/components/schemas/JobPositionCasinoEnum" + }, + "medical": { + "$ref": "#/components/schemas/JobPositionMedicalEnum" + }, + "law": { + "$ref": "#/components/schemas/JobPositionLawEnum" + }, + "education": { + "$ref": "#/components/schemas/JobPositionEducationEnum" + } + }, + "type": "object" + }, + "UserJobRanksResponse": { + "required": [ + "jobranks" + ], + "properties": { + "jobranks": { + "$ref": "#/components/schemas/UserJobRanks" + } + }, + "type": "object" + }, + "UserItemMarkeListingItemDetails": { + "required": [ + "id", + "name", + "type", + "rarity", + "uid", + "stats", + "bonuses" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "rarity": { + "oneOf": [ + { + "type": "string", + "enum": [ + "yellow", + "orange", + "red" + ] + }, + { + "type": "null" + } + ] + }, + "uid": { + "oneOf": [ + { + "$ref": "#/components/schemas/ItemUid" + }, + { + "type": "null" + } + ] + }, + "stats": { + "oneOf": [ + { + "$ref": "#/components/schemas/ItemMarketListingItemStats" + }, + { + "type": "null" + } + ] + }, + "bonuses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemMarketListingItemBonus" + } + } + }, + "type": "object" + }, + "UserItemMarketListing": { + "required": [ + "id", + "price", + "average_price", + "amount", + "is_anonymous", + "available", + "item" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "price": { + "type": "integer", + "format": "int64" + }, + "average_price": { + "type": "integer", + "format": "int64" + }, + "amount": { + "type": "integer", + "format": "int32" + }, + "is_anonymous": { + "type": "boolean" + }, + "available": { + "description": "Amount remaining in the inventory.", + "type": "integer", + "format": "int32" + }, + "item": { + "$ref": "#/components/schemas/UserItemMarkeListingItemDetails" + } + }, + "type": "object" + }, + "UserItemMarketResponse": { + "required": [ + "itemmarket", + "_metadata" + ], + "properties": { + "itemmarket": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserItemMarketListing" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "UserFactionBalance": { + "required": [ + "money", + "points" + ], + "properties": { + "money": { + "type": "integer", + "format": "int64" + }, + "points": { + "type": "integer", + "format": "int64" + } + }, + "type": "object" + }, + "UserFactionBalanceResponse": { + "required": [ + "factionBalance" + ], + "properties": { + "factionBalance": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserFactionBalance" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "UserOrganizedCrimeResponse": { + "required": [ + "organizedCrime" + ], + "properties": { + "organizedCrime": { + "oneOf": [ + { + "$ref": "#/components/schemas/FactionCrime" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "UserList": { + "required": [ + "id", + "name", + "level", + "faction_id", + "last_action", + "status" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/UserId" + }, + "name": { + "type": "string" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "faction_id": { + "oneOf": [ + { + "$ref": "#/components/schemas/FactionId" + }, + { + "type": "null" + } + ] + }, + "last_action": { + "$ref": "#/components/schemas/UserLastAction" + }, + "status": { + "$ref": "#/components/schemas/UserStatus" + } + }, + "type": "object" + }, + "UserListResponse": { + "required": [ + "list", + "_metadata" + ], + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserList" + } + }, + "_metadata": { + "$ref": "#/components/schemas/RequestMetadataWithLinks" + } + }, + "type": "object" + }, + "UserSelectionName": { + "description": "The following selections will fallback to API v1 and may change at any time: 'ammo','bars','basic','battlestats','bazaar','cooldowns','criminalrecord','discord','display','education','equipment','events','gym','honors','icons','inventory','jobpoints','log','medals','merits','messages','missions','money','networth','newevents','newmessages','notifications','perks','profile','properties','refills','reports','skills','stocks','travel','weaponexp','workstats'.", + "type": "string", + "enum": [ + "attacks", + "attacksfull", + "bounties", + "calendar", + "crimes", + "enlistedcars", + "factionbalance", + "forumfeed", + "forumfriends", + "forumposts", + "forumsubscribedthreads", + "forumthreads", + "hof", + "itemmarket", + "jobranks", + "list", + "lookup", + "organizedcrime", + "personalstats", + "races", + "revives", + "revivesfull", + "timestamp", + "ammo", + "bars", + "basic", + "battlestats", + "bazaar", + "cooldowns", + "criminalrecord", + "discord", + "display", + "education", + "equipment", + "events", + "gym", + "honors", + "icons", + "inventory", + "jobpoints", + "log", + "medals", + "merits", + "messages", + "missions", + "money", + "networth", + "newevents", + "newmessages", + "notifications", + "perks", + "profile", + "properties", + "refills", + "reports", + "skills", + "stocks", + "travel", + "weaponexp", + "workstats" + ] + }, + "UserLookupResponse": { + "required": [ + "selections" + ], + "properties": { + "selections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserSelectionName" + } + } + }, + "type": "object" + } + }, + "parameters": { + "ApiKeyPublic": { + "name": "key", + "in": "query", + "description": "API key (Public)", + "required": true, + "schema": { + "type": "string" + } + }, + "ApiKeyMinimal": { + "name": "key", + "in": "query", + "description": "API key (Minimal)", + "required": true, + "schema": { + "type": "string" + } + }, + "ApiKeyLimited": { + "name": "key", + "in": "query", + "description": "API key (Limited)", + "required": true, + "schema": { + "type": "string" + } + }, + "ApiLimit20": { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 20, + "maximum": 20, + "minimum": 1 + } + }, + "ApiLimit100Default20": { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 20, + "maximum": 100, + "minimum": 1 + } + }, + "ApiLimit1000Default20": { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 20, + "maximum": 1000, + "minimum": 1 + } + }, + "ApiLimit50": { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50, + "maximum": 50, + "minimum": 1 + } + }, + "ApiLimit100": { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 100, + "maximum": 100, + "minimum": 1 + } + }, + "ApiLimit1000": { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1000, + "maximum": 1000, + "minimum": 1 + } + }, + "ApiLimit": { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "minimum": 1 + } + }, + "ApiSort": { + "name": "sort", + "in": "query", + "description": "Sorted by the greatest timestamps", + "required": false, + "schema": { + "type": "string", + "enum": [ + "DESC", + "ASC" + ] + } + }, + "ApiSortDesc": { + "name": "sort", + "in": "query", + "description": "Sorted by the greatest timestamps", + "required": false, + "schema": { + "type": "string", + "default": "DESC", + "enum": [ + "DESC", + "ASC" + ] + } + }, + "ApiSortAsc": { + "name": "sort", + "in": "query", + "description": "Sort rows from newest to oldest
Default ordering is ascending", + "required": false, + "schema": { + "type": "string", + "default": "ASC", + "enum": [ + "DESC", + "ASC" + ] + } + }, + "ApiTo": { + "name": "to", + "in": "query", + "description": "Timestamp that sets the upper limit for the data returned. Data returned will be up to and including this time", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "ApiFrom": { + "name": "from", + "in": "query", + "description": "Timestamp that sets the lower limit for the data returned. Data returned will be after this time", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "ApiOffset": { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 0, + "minimum": 0 + } + }, + "ApiOffsetNoDefault": { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + }, + "ApiStripTagsTrue": { + "name": "striptags", + "in": "query", + "description": "Determines if fields include HTML or not ('Hospitalized by user' vs 'Hospitalized by user').", + "required": false, + "schema": { + "type": "string", + "default": "true", + "enum": [ + "true", + "false" + ] + } + }, + "ApiStripTagsFalse": { + "name": "striptags", + "in": "query", + "description": "Determines if fields include HTML or not ('Hospitalized by user' vs 'Hospitalized by user').", + "required": false, + "schema": { + "type": "string", + "default": "false", + "enum": [ + "true", + "false" + ] + } + }, + "ApiStripTags": { + "name": "striptags", + "in": "query", + "description": "Determines if fields include HTML or not ('Hospitalized by user' vs 'Hospitalized by user').", + "required": false, + "schema": { + "type": "string", + "enum": [ + "true", + "false" + ] + } + } + }, + "securitySchemes": { + "api_key": { + "type": "apiKey", + "description": "Pass your API key as the value.
Example header:
Authorization: ApiKey rnavT95qnTCTAbdK
The 'ApiKey' prefix is automatically added in Swagger.", + "name": "Authorization", + "in": "header" + } + } + }, + "tags": [ + { + "name": "User", + "description": "Part of User section" + }, + { + "name": "Faction", + "description": "Part of Faction section" + }, + { + "name": "Market", + "description": "Part of Market section" + }, + { + "name": "Racing", + "description": "Part of Racing section" + }, + { + "name": "Forum", + "description": "Part of Forum section" + }, + { + "name": "Torn", + "description": "Part of Torn section" + } + ] +} \ No newline at end of file diff --git a/torn-api-codegen/overrides.toml b/torn-api-codegen/overrides.toml new file mode 100644 index 0000000..e69de29 diff --git a/torn-api-codegen/src/lib.rs b/torn-api-codegen/src/lib.rs new file mode 100644 index 0000000..b3df4dd --- /dev/null +++ b/torn-api-codegen/src/lib.rs @@ -0,0 +1,2 @@ +pub mod model; +pub mod openapi; diff --git a/torn-api-codegen/src/model/enum.rs b/torn-api-codegen/src/model/enum.rs new file mode 100644 index 0000000..ca8302f --- /dev/null +++ b/torn-api-codegen/src/model/enum.rs @@ -0,0 +1,310 @@ +use heck::ToUpperCamelCase; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::openapi::{ + parameter::OpenApiParameterSchema, + r#type::{OpenApiType, OpenApiVariants}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EnumRepr { + U8, + U32, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EnumVariantTupleValue { + Ref(String), +} + +impl EnumVariantTupleValue { + pub fn from_schema(schema: &OpenApiType) -> Option { + if let OpenApiType { + ref_path: Some(path), + .. + } = schema + { + Some(Self::Ref((*path).to_owned())) + } else { + None + } + } + + pub fn name(&self) -> Option<&str> { + let Self::Ref(path) = self; + + path.strip_prefix("#/components/schemas/") + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EnumVariantValue { + Repr(u32), + String { rename: Option }, + Tuple(Vec), +} + +impl Default for EnumVariantValue { + fn default() -> Self { + Self::String { rename: None } + } +} + +impl EnumVariantValue { + pub fn codegen_display(&self, name: &str) -> Option { + match self { + Self::Repr(i) => Some(quote! { write!(f, "{}", #i) }), + Self::String { rename } => { + let name = rename.as_deref().unwrap_or(name); + Some(quote! { write!(f, #name) }) + } + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct EnumVariant { + pub name: String, + pub description: Option, + pub value: EnumVariantValue, +} + +impl EnumVariant { + pub fn codegen(&self) -> Option { + let doc = self.description.as_ref().map(|d| { + quote! { + #[doc = #d] + } + }); + + let name = format_ident!("{}", self.name); + + match &self.value { + EnumVariantValue::Repr(repr) => Some(quote! { + #doc + #name = #repr + }), + EnumVariantValue::String { rename } => { + let serde_attr = rename.as_ref().map(|r| { + quote! { + #[serde(rename = #r)] + } + }); + + Some(quote! { + #doc + #serde_attr + #name + }) + } + EnumVariantValue::Tuple(values) => { + let mut val_tys = Vec::with_capacity(values.len()); + + for value in values { + let ty_name = value.name()?; + let ty_name = format_ident!("{ty_name}"); + + val_tys.push(quote! { + crate::models::#ty_name + }); + } + + Some(quote! { + #name(#(#val_tys),*) + }) + } + } + } + + pub fn codegen_display(&self) -> Option { + let rhs = self.value.codegen_display(&self.name)?; + let name = format_ident!("{}", self.name); + + Some(quote! { + Self::#name => #rhs + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Enum { + pub name: String, + pub description: Option, + pub repr: Option, + pub copy: bool, + pub display: bool, + pub untagged: bool, + pub variants: Vec, +} + +impl Enum { + pub fn from_schema(name: &str, schema: &OpenApiType) -> Option { + let mut result = Enum { + name: name.to_owned(), + description: schema.description.as_deref().map(ToOwned::to_owned), + copy: true, + ..Default::default() + }; + + match &schema.r#enum { + Some(OpenApiVariants::Int(int_variants)) => { + result.repr = Some(EnumRepr::U32); + result.display = true; + result.variants = int_variants + .iter() + .copied() + .map(|i| EnumVariant { + name: format!("Variant{i}"), + value: EnumVariantValue::Repr(i as u32), + ..Default::default() + }) + .collect(); + } + Some(OpenApiVariants::Str(str_variants)) => { + result.display = true; + result.variants = str_variants + .iter() + .copied() + .map(|s| { + let transformed = s.replace('&', "And").to_upper_camel_case(); + EnumVariant { + value: EnumVariantValue::String { + rename: (transformed != s).then(|| s.to_owned()), + }, + name: transformed, + ..Default::default() + } + }) + .collect(); + } + None => return None, + } + + Some(result) + } + + pub fn from_parameter_schema(name: &str, schema: &OpenApiParameterSchema) -> Option { + let mut result = Self { + name: name.to_owned(), + copy: true, + display: true, + ..Default::default() + }; + + for var in schema.r#enum.as_ref()? { + let transformed = var.to_upper_camel_case(); + result.variants.push(EnumVariant { + value: EnumVariantValue::String { + rename: (transformed != *var).then(|| transformed.clone()), + }, + name: transformed, + ..Default::default() + }); + } + + Some(result) + } + + pub fn from_one_of(name: &str, schemas: &[OpenApiType]) -> Option { + let mut result = Self { + name: name.to_owned(), + untagged: true, + ..Default::default() + }; + + for schema in schemas { + let value = EnumVariantTupleValue::from_schema(schema)?; + let name = value.name()?.to_owned(); + + result.variants.push(EnumVariant { + name, + value: EnumVariantValue::Tuple(vec![value]), + ..Default::default() + }); + } + + Some(result) + } + + pub fn codegen(&self) -> Option { + let repr = self.repr.map(|r| match r { + EnumRepr::U8 => quote! { #[repr(u8)]}, + EnumRepr::U32 => quote! { #[repr(u32)]}, + }); + let name = format_ident!("{}", self.name); + let desc = self.description.as_ref().map(|d| { + quote! { + #repr + #[doc = #d] + } + }); + + let mut display = Vec::with_capacity(self.variants.len()); + let mut variants = Vec::with_capacity(self.variants.len()); + for variant in &self.variants { + variants.push(variant.codegen()?); + + if self.display { + display.push(variant.codegen_display()?); + } + } + + let mut derives = vec![]; + + if self.copy { + derives.extend_from_slice(&["Copy", "Hash"]); + } + + let derives = derives.into_iter().map(|d| format_ident!("{d}")); + + let serde_attr = self.untagged.then(|| { + quote! { + #[serde(untagged)] + } + }); + + let display = self.display.then(|| { + quote! { + impl std::fmt::Display for #name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + #(#display),* + } + } + } + } + }); + + Some(quote! { + #desc + #[derive(Debug, Clone, PartialEq, serde::Deserialize, #(#derives),*)] + #serde_attr + pub enum #name { + #(#variants),* + } + #display + }) + } +} + +#[cfg(test)] +mod test { + use crate::openapi::schema::OpenApiSchema; + + use super::*; + + #[test] + fn codegen() { + let schema = OpenApiSchema::read().unwrap(); + + let revive_setting = schema.components.schemas.get("ReviveSetting").unwrap(); + + let r#enum = Enum::from_schema("ReviveSetting", revive_setting).unwrap(); + + let code = r#enum.codegen().unwrap(); + + panic!("{code}"); + } +} diff --git a/torn-api-codegen/src/model/mod.rs b/torn-api-codegen/src/model/mod.rs new file mode 100644 index 0000000..ed0ae5f --- /dev/null +++ b/torn-api-codegen/src/model/mod.rs @@ -0,0 +1,189 @@ +use r#enum::Enum; +use indexmap::IndexMap; +use newtype::Newtype; +use object::Object; +use proc_macro2::TokenStream; + +use crate::openapi::r#type::OpenApiType; + +pub mod r#enum; +pub mod newtype; +pub mod object; +pub mod parameter; +pub mod path; +pub mod scope; +pub mod union; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Model { + Newtype(Newtype), + Enum(Enum), + Object(Object), + Unresolved, +} + +pub fn resolve(r#type: &OpenApiType, name: &str, schemas: &IndexMap<&str, OpenApiType>) -> Model { + match r#type { + OpenApiType { + r#enum: Some(_), .. + } => Enum::from_schema(name, r#type).map_or(Model::Unresolved, Model::Enum), + OpenApiType { + r#type: Some("object"), + .. + } => Object::from_schema_object(name, r#type, schemas) + .map_or(Model::Unresolved, Model::Object), + OpenApiType { + r#type: Some(_), .. + } => Newtype::from_schema(name, r#type).map_or(Model::Unresolved, Model::Newtype), + OpenApiType { + one_of: Some(types), + .. + } => Enum::from_one_of(name, types).map_or(Model::Unresolved, Model::Enum), + OpenApiType { + all_of: Some(types), + .. + } => Object::from_all_of(name, types, schemas).map_or(Model::Unresolved, Model::Object), + _ => Model::Unresolved, + } +} + +impl Model { + pub fn codegen(&self) -> Option { + match self { + Self::Newtype(newtype) => newtype.codegen(), + Self::Enum(r#enum) => r#enum.codegen(), + Self::Object(object) => object.codegen(), + Self::Unresolved => None, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + model::r#enum::{EnumRepr, EnumVariant}, + openapi::schema::OpenApiSchema, + }; + + #[test] + fn resolve_newtypes() { + let schema = OpenApiSchema::read().unwrap(); + + let user_id_schema = schema.components.schemas.get("UserId").unwrap(); + + let user_id = resolve(user_id_schema, "UserId", &schema.components.schemas); + + assert_eq!( + user_id, + Model::Newtype(Newtype { + name: "UserId".to_owned(), + description: None, + inner: newtype::NewtypeInner::I32, + copy: true, + ord: true + }) + ); + + let attack_code_schema = schema.components.schemas.get("AttackCode").unwrap(); + + let attack_code = resolve(attack_code_schema, "AttackCode", &schema.components.schemas); + + assert_eq!( + attack_code, + Model::Newtype(Newtype { + name: "AttackCode".to_owned(), + description: None, + inner: newtype::NewtypeInner::Str, + copy: false, + ord: false + }) + ); + } + + #[test] + fn resolve_enums() { + let schema = OpenApiSchema::read().unwrap(); + + let forum_feed_type_schema = schema.components.schemas.get("ForumFeedTypeEnum").unwrap(); + + let forum_feed_type = resolve( + forum_feed_type_schema, + "ForumFeedTypeEnum", + &schema.components.schemas, + ); + + assert_eq!(forum_feed_type, Model::Enum(Enum { + name: "ForumFeedType".to_owned(), + description: Some("This represents the type of the activity. Values range from 1 to 8 where:\n * 1 = 'X posted on a thread',\n * 2 = 'X created a thread',\n * 3 = 'X liked your thread',\n * 4 = 'X disliked your thread',\n * 5 = 'X liked your post',\n * 6 = 'X disliked your post',\n * 7 = 'X quoted your post'.".to_owned()), + repr: Some(EnumRepr::U32), + copy: true, + untagged: false, + display: true, + variants: vec![ + EnumVariant { + name: "Variant1".to_owned(), + value: r#enum::EnumVariantValue::Repr(1), + ..Default::default() + }, + EnumVariant { + name: "Variant2".to_owned(), + value: r#enum::EnumVariantValue::Repr(2), + ..Default::default() + }, + EnumVariant { + name: "Variant3".to_owned(), + value: r#enum::EnumVariantValue::Repr(3), + ..Default::default() + }, + EnumVariant { + name: "Variant4".to_owned(), + value: r#enum::EnumVariantValue::Repr(4), + ..Default::default() + }, + EnumVariant { + name: "Variant5".to_owned(), + value: r#enum::EnumVariantValue::Repr(5), + ..Default::default() + }, + EnumVariant { + name: "Variant6".to_owned(), + value: r#enum::EnumVariantValue::Repr(6), + ..Default::default() + }, + EnumVariant { + name: "Variant7".to_owned(), + value: r#enum::EnumVariantValue::Repr(7), + ..Default::default() + }, + ] + })) + } + + #[test] + fn resolve_all() { + let schema = OpenApiSchema::read().unwrap(); + + let mut unresolved = vec![]; + let total = schema.components.schemas.len(); + + for (name, desc) in &schema.components.schemas { + if resolve(desc, name, &schema.components.schemas) == Model::Unresolved { + unresolved.push(name); + } + } + + if !unresolved.is_empty() { + panic!( + "Failed to resolve {}/{} types. Could not resolve [{}]", + unresolved.len(), + total, + unresolved + .into_iter() + .map(|u| format!("`{u}`")) + .collect::>() + .join(", ") + ) + } + } +} diff --git a/torn-api-codegen/src/model/newtype.rs b/torn-api-codegen/src/model/newtype.rs new file mode 100644 index 0000000..372c637 --- /dev/null +++ b/torn-api-codegen/src/model/newtype.rs @@ -0,0 +1,144 @@ +use quote::{format_ident, quote}; + +use crate::openapi::r#type::OpenApiType; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NewtypeInner { + Str, + I32, + I64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Newtype { + pub name: String, + pub description: Option, + pub inner: NewtypeInner, + pub copy: bool, + pub ord: bool, +} + +impl Newtype { + pub fn from_schema(name: &str, schema: &OpenApiType) -> Option { + let name = name.to_owned(); + let description = schema.description.as_deref().map(ToOwned::to_owned); + + match schema { + OpenApiType { + r#type: Some("string"), + .. + } => Some(Self { + name, + description, + inner: NewtypeInner::Str, + copy: false, + ord: false, + }), + OpenApiType { + r#type: Some("integer"), + format: Some("int32"), + .. + } => Some(Self { + name, + description, + inner: NewtypeInner::I32, + copy: true, + ord: true, + }), + OpenApiType { + r#type: Some("integer"), + format: Some("int64"), + .. + } => Some(Self { + name, + description, + inner: NewtypeInner::I64, + copy: true, + ord: true, + }), + _ => None, + } + } + + pub fn codegen(&self) -> Option { + let mut derives = vec![quote! { Debug, Clone, PartialEq, Eq, Hash, serde::Deserialize }]; + + if self.copy { + derives.push(quote! { Copy }); + } + + if self.ord { + derives.push(quote! { PartialOrd, Ord }); + } + + let name = format_ident!("{}", self.name); + let inner = match self.inner { + NewtypeInner::Str => format_ident!("String"), + NewtypeInner::I32 => format_ident!("i32"), + NewtypeInner::I64 => format_ident!("i64"), + }; + + let doc = self.description.as_ref().map(|d| { + quote! { + #[doc = #d] + } + }); + + let body = quote! { + #doc + #[derive(#(#derives),*)] + pub struct #name(pub #inner); + + impl #name { + pub fn new(inner: #inner) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> #inner { + self.0 + } + } + + impl From<#inner> for #name { + fn from(inner: #inner) -> Self { + Self(inner) + } + } + + impl From<#name> for #inner { + fn from(outer: #name) -> Self { + outer.0 + } + } + + impl std::fmt::Display for #name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + }; + + Some(body) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::openapi::schema::OpenApiSchema; + + #[test] + fn codegen() { + let schema = OpenApiSchema::read().unwrap(); + + let user_id = schema.components.schemas.get("UserId").unwrap(); + + let mut newtype = Newtype::from_schema("UserId", user_id).unwrap(); + + newtype.description = Some("Description goes here".to_owned()); + + let code = newtype.codegen().unwrap().to_string(); + + panic!("{code}"); + } +} diff --git a/torn-api-codegen/src/model/object.rs b/torn-api-codegen/src/model/object.rs new file mode 100644 index 0000000..1ae23fa --- /dev/null +++ b/torn-api-codegen/src/model/object.rs @@ -0,0 +1,448 @@ +use heck::{ToSnakeCase, ToUpperCamelCase}; +use indexmap::IndexMap; +use proc_macro2::TokenStream; +use quote::{ToTokens, format_ident, quote}; +use syn::Ident; + +use crate::openapi::r#type::OpenApiType; + +use super::r#enum::Enum; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PrimitiveType { + Bool, + I32, + I64, + String, + Float, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PropertyType { + Primitive(PrimitiveType), + Ref(String), + Enum(Enum), + Nested(Box), + Array(Box), +} + +impl PropertyType { + pub fn codegen(&self, namespace: &mut ObjectNamespace) -> Option { + match self { + Self::Primitive(PrimitiveType::Bool) => Some(format_ident!("bool").into_token_stream()), + Self::Primitive(PrimitiveType::I32) => Some(format_ident!("i32").into_token_stream()), + Self::Primitive(PrimitiveType::I64) => Some(format_ident!("i64").into_token_stream()), + Self::Primitive(PrimitiveType::String) => { + Some(format_ident!("String").into_token_stream()) + } + Self::Primitive(PrimitiveType::Float) => Some(format_ident!("f64").into_token_stream()), + Self::Ref(path) => { + let name = path.strip_prefix("#/components/schemas/")?; + let name = format_ident!("{name}"); + + Some(quote! { crate::models::#name }) + } + Self::Enum(r#enum) => { + let code = r#enum.codegen()?; + namespace.push_element(code); + + let ns = namespace.get_ident(); + let name = format_ident!("{}", r#enum.name); + + Some(quote! { + #ns::#name + }) + } + Self::Array(array) => { + let inner_ty = array.codegen(namespace)?; + + Some(quote! { + Vec<#inner_ty> + }) + } + Self::Nested(nested) => { + let code = nested.codegen()?; + namespace.push_element(code); + + let ns = namespace.get_ident(); + let name = format_ident!("{}", nested.name); + + Some(quote! { + #ns::#name + }) + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Property { + pub name: String, + pub description: Option, + pub required: bool, + pub nullable: bool, + pub r#type: PropertyType, +} + +impl Property { + pub fn from_schema( + name: &str, + required: bool, + schema: &OpenApiType, + schemas: &IndexMap<&str, OpenApiType>, + ) -> Option { + let name = name.to_owned(); + let description = schema.description.as_deref().map(ToOwned::to_owned); + + match schema { + OpenApiType { + r#enum: Some(_), .. + } => Some(Self { + r#type: PropertyType::Enum(Enum::from_schema( + &name.clone().to_upper_camel_case(), + schema, + )?), + name, + description, + required, + nullable: false, + }), + OpenApiType { + one_of: Some(types), + .. + } => match types.as_slice() { + [ + left, + OpenApiType { + r#type: Some("null"), + .. + }, + ] => { + let mut inner = Self::from_schema(&name, required, left, schemas)?; + inner.nullable = true; + Some(inner) + } + [ + left @ .., + OpenApiType { + r#type: Some("null"), + .. + }, + ] => { + let rest = OpenApiType { + one_of: Some(left.to_owned()), + ..schema.clone() + }; + let mut inner = Self::from_schema(&name, required, &rest, schemas)?; + inner.nullable = true; + Some(inner) + } + cases => { + let r#enum = Enum::from_one_of(&name.to_upper_camel_case(), cases)?; + Some(Self { + name, + description: None, + required, + nullable: false, + r#type: PropertyType::Enum(r#enum), + }) + } + }, + OpenApiType { + all_of: Some(types), + .. + } => { + let composite = Object::from_all_of(&name.to_upper_camel_case(), types, schemas)?; + Some(Self { + name, + description: None, + required, + nullable: false, + r#type: PropertyType::Nested(Box::new(composite)), + }) + } + OpenApiType { + r#type: Some("object"), + .. + } => Some(Self { + r#type: PropertyType::Nested(Box::new(Object::from_schema_object( + &name.clone().to_upper_camel_case(), + schema, + schemas, + )?)), + name, + description, + required, + nullable: false, + }), + OpenApiType { + ref_path: Some(path), + .. + } => Some(Self { + name, + description, + r#type: PropertyType::Ref((*path).to_owned()), + required, + nullable: false, + }), + OpenApiType { + r#type: Some("array"), + items: Some(items), + .. + } => { + let inner = Self::from_schema(&name, required, items, schemas)?; + + Some(Self { + name, + description, + required, + nullable: false, + r#type: PropertyType::Array(Box::new(inner.r#type)), + }) + } + OpenApiType { + r#type: Some(_), .. + } => { + let prim = match (schema.r#type, schema.format) { + (Some("integer"), Some("int32")) => PrimitiveType::I32, + (Some("integer"), Some("int64")) => PrimitiveType::I64, + (Some("number"), Some("float")) => PrimitiveType::Float, + (Some("string"), None) => PrimitiveType::String, + (Some("boolean"), None) => PrimitiveType::Bool, + _ => return None, + }; + + Some(Self { + name, + description, + required, + nullable: false, + r#type: PropertyType::Primitive(prim), + }) + } + _ => None, + } + } + + pub fn codegen(&self, namespace: &mut ObjectNamespace) -> Option { + let desc = self.description.as_ref().map(|d| quote! { #[doc = #d]}); + + let name = &self.name; + let (name, serde_attr) = match name.as_str() { + "type" => (format_ident!("r#type"), None), + name if name != name.to_snake_case() => ( + format_ident!("{}", name.to_snake_case()), + Some(quote! { #[serde(rename = #name)]}), + ), + _ => (format_ident!("{name}"), None), + }; + + let ty_inner = self.r#type.codegen(namespace)?; + + let ty = if !self.required || self.nullable { + quote! { Option<#ty_inner> } + } else { + ty_inner + }; + + Some(quote! { + #desc + #serde_attr + pub #name: #ty + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Object { + pub name: String, + pub description: Option, + pub properties: Vec, +} + +impl Object { + pub fn from_schema_object( + name: &str, + schema: &OpenApiType, + schemas: &IndexMap<&str, OpenApiType>, + ) -> Option { + let mut result = Object { + name: name.to_owned(), + description: schema.description.as_deref().map(ToOwned::to_owned), + ..Default::default() + }; + + let Some(props) = &schema.properties else { + return None; + }; + + let required = schema.required.clone().unwrap_or_default(); + + for (prop_name, prop) in props { + // HACK: This will cause a duplicate key otherwise + if *prop_name == "itemDetails" { + continue; + } + + // TODO: implement custom enum for this (depends on overrides being added) + if *prop_name == "value" && name == "TornHof" { + continue; + } + + result.properties.push(Property::from_schema( + prop_name, + required.contains(prop_name), + prop, + schemas, + )?); + } + + Some(result) + } + + pub fn from_all_of( + name: &str, + types: &[OpenApiType], + schemas: &IndexMap<&str, OpenApiType>, + ) -> Option { + let mut result = Self { + name: name.to_owned(), + ..Default::default() + }; + + for r#type in types { + let r#type = if let OpenApiType { + ref_path: Some(path), + .. + } = r#type + { + let name = path.strip_prefix("#/components/schemas/")?; + schemas.get(name)? + } else { + r#type + }; + let obj = Self::from_schema_object(name, r#type, schemas)?; + + result.description = result.description.or(obj.description); + result.properties.extend(obj.properties); + } + + Some(result) + } + + pub fn codegen(&self) -> Option { + let doc = self.description.as_ref().map(|d| { + quote! { + #[doc = #d] + } + }); + + let mut namespace = ObjectNamespace { + object: self, + ident: None, + elements: Vec::default(), + }; + + let mut props = Vec::with_capacity(self.properties.len()); + for prop in &self.properties { + props.push(prop.codegen(&mut namespace)?); + } + + let name = format_ident!("{}", self.name); + let ns = namespace.codegen(); + + Some(quote! { + #ns + + #doc + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + pub struct #name { + #(#props),* + } + }) + } +} + +pub struct ObjectNamespace<'o> { + object: &'o Object, + ident: Option, + elements: Vec, +} + +impl ObjectNamespace<'_> { + pub fn get_ident(&mut self) -> Ident { + self.ident + .get_or_insert_with(|| { + let name = self.object.name.to_snake_case(); + format_ident!("{name}") + }) + .clone() + } + + pub fn push_element(&mut self, el: TokenStream) { + self.elements.push(el); + } + + pub fn codegen(mut self) -> Option { + if self.elements.is_empty() { + None + } else { + let ident = self.get_ident(); + let elements = self.elements; + Some(quote! { + pub mod #ident { + #(#elements)* + } + }) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::openapi::schema::OpenApiSchema; + + #[test] + fn resolve_object() { + let schema = OpenApiSchema::read().unwrap(); + + let attack = schema.components.schemas.get("FactionUpgrades").unwrap(); + + let resolved = + Object::from_schema_object("FactionUpgrades", attack, &schema.components.schemas) + .unwrap(); + let _code = resolved.codegen().unwrap(); + } + + #[test] + fn resolve_objects() { + let schema = OpenApiSchema::read().unwrap(); + + let mut objects = 0; + let mut unresolved = vec![]; + + for (name, desc) in &schema.components.schemas { + if desc.r#type == Some("object") { + objects += 1; + if Object::from_schema_object(name, desc, &schema.components.schemas).is_none() { + unresolved.push(name); + } + } + } + + if !unresolved.is_empty() { + panic!( + "Failed to resolve {}/{} objects. Could not resolve [{}]", + unresolved.len(), + objects, + unresolved + .into_iter() + .map(|u| format!("`{u}`")) + .collect::>() + .join(", ") + ) + } + } +} diff --git a/torn-api-codegen/src/model/parameter.rs b/torn-api-codegen/src/model/parameter.rs new file mode 100644 index 0000000..efa076e --- /dev/null +++ b/torn-api-codegen/src/model/parameter.rs @@ -0,0 +1,431 @@ +use std::fmt::Write; + +use heck::ToUpperCamelCase; +use proc_macro2::TokenStream; +use quote::{ToTokens, format_ident, quote}; + +use crate::openapi::parameter::{ + OpenApiParameter, OpenApiParameterDefault, OpenApiParameterSchema, + ParameterLocation as SchemaLocation, +}; + +use super::r#enum::Enum; + +#[derive(Debug, Clone)] +pub struct ParameterOptions

{ + pub default: Option

, + pub minimum: Option

, + pub maximum: Option

, +} + +#[derive(Debug, Clone)] +pub enum ParameterType { + I32 { + options: ParameterOptions, + }, + String, + Boolean, + Enum { + options: ParameterOptions, + r#type: Enum, + }, + Schema { + type_name: String, + }, + Array { + items: Box, + }, +} + +impl ParameterType { + pub fn from_schema(name: &str, schema: &OpenApiParameterSchema) -> Option { + match schema { + OpenApiParameterSchema { + r#type: Some("integer"), + // BUG: missing for some types in the spec + + // format: Some("int32"), + .. + } => { + let default = match schema.default { + Some(OpenApiParameterDefault::Int(d)) => Some(d), + None => None, + _ => return None, + }; + + Some(Self::I32 { + options: ParameterOptions { + default, + minimum: schema.minimum, + maximum: schema.maximum, + }, + }) + } + OpenApiParameterSchema { + r#type: Some("string"), + r#enum: Some(variants), + .. + } if variants.as_slice() == ["true", "false"] + || variants.as_slice() == ["false", "true "] => + { + Some(ParameterType::Boolean) + } + OpenApiParameterSchema { + r#type: Some("string"), + r#enum: Some(_), + .. + } => { + let default = match schema.default { + Some(OpenApiParameterDefault::Str(d)) => Some(d.to_owned()), + None => None, + _ => return None, + }; + + Some(ParameterType::Enum { + options: ParameterOptions { + default, + minimum: None, + maximum: None, + }, + r#type: Enum::from_parameter_schema(name, schema)?, + }) + } + OpenApiParameterSchema { + r#type: Some("string"), + .. + } => Some(ParameterType::String), + OpenApiParameterSchema { + ref_path: Some(path), + .. + } => { + let type_name = path.strip_prefix("#/components/schemas/")?.to_owned(); + + Some(ParameterType::Schema { type_name }) + } + OpenApiParameterSchema { + r#type: Some("array"), + items: Some(items), + .. + } => Some(Self::Array { + items: Box::new(Self::from_schema(name, items)?), + }), + _ => None, + } + } + + pub fn codegen_type_name(&self, name: &str) -> TokenStream { + match self { + Self::I32 { .. } | Self::String | Self::Enum { .. } | Self::Array { .. } => { + format_ident!("{name}").into_token_stream() + } + Self::Boolean => quote! { bool }, + Self::Schema { type_name } => { + let type_name = format_ident!("{type_name}",); + quote! { crate::models::#type_name } + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ParameterLocation { + Query, + Path, +} + +#[derive(Debug, Clone)] +pub struct Parameter { + pub name: String, + pub value: String, + pub description: Option, + pub r#type: ParameterType, + pub required: bool, + pub location: ParameterLocation, +} + +impl Parameter { + pub fn from_schema(name: &str, schema: &OpenApiParameter) -> Option { + let name = match name { + "From" => "FromTimestamp".to_owned(), + "To" => "ToTimestamp".to_owned(), + name => name.to_owned(), + }; + let value = schema.name.to_owned(); + let description = schema.description.as_deref().map(ToOwned::to_owned); + + let location = match &schema.r#in { + SchemaLocation::Query => ParameterLocation::Query, + SchemaLocation::Path => ParameterLocation::Path, + }; + + let r#type = ParameterType::from_schema(&name, &schema.schema)?; + + Some(Self { + name, + value, + description, + r#type, + required: schema.required, + location, + }) + } + + pub fn codegen(&self) -> Option { + match &self.r#type { + ParameterType::I32 { options } => { + let name = format_ident!("{}", self.name); + + let mut desc = self.description.as_deref().unwrap_or_default().to_owned(); + + if options.default.is_some() + || options.minimum.is_some() + || options.maximum.is_some() + { + _ = writeln!(desc, "\n # Notes"); + } + + let constructor = if let (Some(min), Some(max)) = (options.minimum, options.maximum) + { + _ = write!(desc, "Values have to lie between {min} and {max}. "); + let name_raw = &self.name; + quote! { + impl #name { + pub fn new(inner: i32) -> Result { + if inner > #max || inner < #min { + Err(crate::ParameterError::OutOfRange { value: inner, name: #name_raw }) + } else { + Ok(Self(inner)) + } + } + } + + impl TryFrom for #name { + type Error = crate::ParameterError; + fn try_from(inner: i32) -> Result { + if inner > #max || inner < #min { + Err(crate::ParameterError::OutOfRange { value: inner, name: #name_raw }) + } else { + Ok(Self(inner)) + } + } + } + } + } else { + quote! { + impl #name { + pub fn new(inner: i32) -> Self { + Self(inner) + } + } + } + }; + + if let Some(default) = options.default { + _ = write!(desc, "The default value is {default}."); + } + + let doc = quote! { + #[doc = #desc] + }; + + Some(quote! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #doc + pub struct #name(i32); + + #constructor + + impl From<#name> for i32 { + fn from(value: #name) -> Self { + value.0 + } + } + + impl #name { + pub fn into_inner(self) -> i32 { + self.0 + } + } + + impl std::fmt::Display for #name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + }) + } + ParameterType::Enum { options, r#type } => { + let mut desc = self.description.as_deref().unwrap_or_default().to_owned(); + if let Some(default) = &options.default { + let default = default.to_upper_camel_case(); + _ = write!( + desc, + r#" +# Notes +The default value [Self::{}](self::{}#variant.{})"#, + default, self.name, default + ); + } + + let doc = quote! { #[doc = #desc]}; + let inner = r#type.codegen()?; + + Some(quote! { + #doc + #inner + }) + } + ParameterType::Array { items } => { + let (inner_name, outer_name) = match items.as_ref() { + ParameterType::I32 { .. } + | ParameterType::String + | ParameterType::Array { .. } + | ParameterType::Enum { .. } => self.name.strip_suffix('s').map_or_else( + || (self.name.to_owned(), format!("{}s", self.name)), + |s| (s.to_owned(), self.name.to_owned()), + ), + ParameterType::Boolean => ("bool".to_owned(), self.name.clone()), + ParameterType::Schema { type_name } => (type_name.clone(), self.name.clone()), + }; + + let inner = Self { + r#type: *items.clone(), + name: inner_name.clone(), + ..self.clone() + }; + + let mut code = inner.codegen().unwrap_or_default(); + + let name = format_ident!("{}", outer_name); + let inner_ty = items.codegen_type_name(&inner_name); + + code.extend(quote! { + #[derive(Debug, Clone)] + pub struct #name(pub Vec<#inner_ty>); + + impl std::fmt::Display for #name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let mut first = true; + for el in &self.0 { + if first { + first = false; + write!(f, "{el}")?; + } else { + write!(f, ",{el}")?; + } + } + Ok(()) + } + } + }); + + Some(code) + } + _ => None, + } + } +} + +#[cfg(test)] +mod test { + use crate::openapi::{path::OpenApiPathParameter, schema::OpenApiSchema}; + + use super::*; + + #[test] + fn resolve_components() { + let schema = OpenApiSchema::read().unwrap(); + + let mut parameters = 0; + let mut unresolved = vec![]; + + for (name, desc) in &schema.components.parameters { + parameters += 1; + if Parameter::from_schema(name, desc).is_none() { + unresolved.push(name); + } + } + + if !unresolved.is_empty() { + panic!( + "Failed to resolve {}/{} params. Could not resolve [{}]", + unresolved.len(), + parameters, + unresolved + .into_iter() + .map(|u| format!("`{u}`")) + .collect::>() + .join(", ") + ) + } + } + + #[test] + fn resolve_inline() { + let schema = OpenApiSchema::read().unwrap(); + + let mut params = 0; + let mut unresolved = Vec::new(); + + for (path, body) in &schema.paths { + for param in &body.get.parameters { + if let OpenApiPathParameter::Inline(inline) = param { + params += 1; + if Parameter::from_schema(inline.name, inline).is_none() { + unresolved.push(format!("`{}.{}`", path, inline.name)); + } + } + } + } + + if !unresolved.is_empty() { + panic!( + "Failed to resolve {}/{} inline params. Could not resolve [{}]", + unresolved.len(), + params, + unresolved.join(", ") + ) + } + } + + #[test] + fn codegen_inline() { + let schema = OpenApiSchema::read().unwrap(); + + let mut params = 0; + let mut unresolved = Vec::new(); + + for (path, body) in &schema.paths { + for param in &body.get.parameters { + if let OpenApiPathParameter::Inline(inline) = param { + if inline.r#in == SchemaLocation::Query { + let Some(param) = Parameter::from_schema(inline.name, inline) else { + continue; + }; + if matches!( + param.r#type, + ParameterType::Schema { .. } + | ParameterType::Boolean + | ParameterType::String + ) { + continue; + } + params += 1; + if param.codegen().is_none() { + unresolved.push(format!("`{}.{}`", path, inline.name)); + } + } + } + } + } + + if !unresolved.is_empty() { + panic!( + "Failed to codegen {}/{} inline params. Could not codegen [{}]", + unresolved.len(), + params, + unresolved.join(", ") + ) + } + } +} diff --git a/torn-api-codegen/src/model/path.rs b/torn-api-codegen/src/model/path.rs new file mode 100644 index 0000000..59f499e --- /dev/null +++ b/torn-api-codegen/src/model/path.rs @@ -0,0 +1,482 @@ +use std::{fmt::Write, ops::Deref}; + +use heck::{ToSnakeCase, ToUpperCamelCase}; +use indexmap::IndexMap; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::Ident; + +use crate::openapi::{ + parameter::OpenApiParameter, + path::{OpenApiPath, OpenApiPathParameter, OpenApiResponseBody}, +}; + +use super::{ + parameter::{Parameter, ParameterLocation, ParameterType}, + union::Union, +}; + +#[derive(Debug, Clone)] +pub enum PathSegment { + Constant(String), + Parameter { name: String }, +} + +#[derive(Debug, Clone)] +pub enum PathParameter { + Inline(Parameter), + Component(Parameter), +} + +#[derive(Debug, Clone)] +pub enum PathResponse { + Component { name: String }, + // TODO: needs to be implemented + ArbitraryUnion(Union), +} + +#[derive(Debug, Clone)] +pub struct Path { + pub segments: Vec, + pub name: String, + pub summary: Option, + pub description: String, + pub parameters: Vec, + pub response: PathResponse, +} + +impl Path { + pub fn from_schema( + path: &str, + schema: &OpenApiPath, + parameters: &IndexMap<&str, OpenApiParameter>, + ) -> Option { + let mut segments = Vec::new(); + for segment in path.strip_prefix('/')?.split('/') { + if segment.starts_with('{') && segment.ends_with('}') { + segments.push(PathSegment::Parameter { + name: segment[1..(segment.len() - 1)].to_owned(), + }); + } else { + segments.push(PathSegment::Constant(segment.to_owned())); + } + } + + let summary = schema.get.summary.as_deref().map(ToOwned::to_owned); + let description = schema.get.description.deref().to_owned(); + + let mut params = Vec::with_capacity(schema.get.parameters.len()); + for parameter in &schema.get.parameters { + match ¶meter { + OpenApiPathParameter::Link { ref_path } => { + let name = ref_path + .strip_prefix("#/components/parameters/")? + .to_owned(); + let param = parameters.get(&name.as_str())?; + params.push(PathParameter::Component(Parameter::from_schema( + &name, param, + )?)); + } + OpenApiPathParameter::Inline(schema) => { + let name = schema.name.to_upper_camel_case(); + let parameter = Parameter::from_schema(&name, schema)?; + params.push(PathParameter::Inline(parameter)); + } + }; + } + + let mut suffixes = vec![]; + let mut name = String::new(); + + for seg in &segments { + match seg { + PathSegment::Constant(val) => { + name.push_str(&val.to_upper_camel_case()); + } + PathSegment::Parameter { name } => { + suffixes.push(format!("For{}", name.to_upper_camel_case())); + } + } + } + + for suffix in suffixes { + name.push_str(&suffix); + } + + let response = match &schema.get.response_content { + OpenApiResponseBody::Schema(link) => PathResponse::Component { + name: link + .ref_path + .strip_prefix("#/components/schemas/")? + .to_owned(), + }, + OpenApiResponseBody::Union { any_of: _ } => PathResponse::ArbitraryUnion( + Union::from_schema("Response", &schema.get.response_content)?, + ), + }; + + Some(Self { + segments, + name, + summary, + description, + parameters: params, + response, + }) + } + + pub fn codegen_request(&self) -> Option { + let name = if self.segments.len() == 1 { + let Some(PathSegment::Constant(first)) = self.segments.first() else { + return None; + }; + format_ident!("{}Request", first.to_upper_camel_case()) + } else { + format_ident!("{}Request", self.name) + }; + + let mut ns = PathNamespace { + path: self, + ident: None, + elements: Vec::new(), + }; + + let mut fields = Vec::with_capacity(self.parameters.len()); + let mut convert_field = Vec::with_capacity(self.parameters.len()); + let mut start_fields = Vec::new(); + let mut discriminant = Vec::new(); + let mut discriminant_val = Vec::new(); + let mut fmt_val = Vec::new(); + + for param in &self.parameters { + let (is_inline, param) = match ¶m { + PathParameter::Inline(param) => (true, param), + PathParameter::Component(param) => (false, param), + }; + + let ty = match ¶m.r#type { + ParameterType::I32 { .. } | ParameterType::Enum { .. } => { + let ty_name = format_ident!("{}", param.name); + + if is_inline { + ns.push_element(param.codegen()?); + let path = ns.get_ident(); + + quote! { + crate::request::models::#path::#ty_name + } + } else { + quote! { + crate::parameters::#ty_name + } + } + } + ParameterType::String => quote! { String }, + ParameterType::Boolean => quote! { bool }, + ParameterType::Schema { type_name } => { + let ty_name = format_ident!("{}", type_name); + + quote! { + crate::models::#ty_name + } + } + ParameterType::Array { .. } => { + ns.push_element(param.codegen()?); + let ty_name = param.r#type.codegen_type_name(¶m.name); + let path = ns.get_ident(); + quote! { + crate::request::models::#path::#ty_name + } + } + }; + + let name = format_ident!("{}", param.name.to_snake_case()); + let query_val = ¶m.value; + + if param.location == ParameterLocation::Path { + discriminant.push(ty.clone()); + discriminant_val.push(quote! { self.#name }); + let path_name = format_ident!("{}", param.value); + start_fields.push(quote! { + #[builder(start_fn)] + pub #name: #ty + }); + fmt_val.push(quote! { + #path_name=self.#name + }); + } else { + let ty = if param.required { + convert_field.push(quote! { + .chain(std::iter::once(&self.#name).map(|v| (#query_val, v.to_string()))) + }); + ty + } else { + convert_field.push(quote! { + .chain(self.#name.as_ref().into_iter().map(|v| (#query_val, v.to_string()))) + }); + quote! { Option<#ty>} + }; + + fields.push(quote! { + pub #name: #ty + }); + } + } + + let response_ty = match &self.response { + PathResponse::Component { name } => { + let name = format_ident!("{name}"); + quote! { + crate::models::#name + } + } + PathResponse::ArbitraryUnion(union) => { + let path = ns.get_ident(); + let ty_name = format_ident!("{}", union.name); + + quote! { + crate::request::models::#path::#ty_name + } + } + }; + + let mut path_fmt_str = String::new(); + for seg in &self.segments { + match seg { + PathSegment::Constant(val) => _ = write!(path_fmt_str, "/{}", val), + PathSegment::Parameter { name } => _ = write!(path_fmt_str, "/{{{}}}", name), + } + } + + if let PathResponse::ArbitraryUnion(union) = &self.response { + ns.push_element(union.codegen()?); + } + + let ns = ns.codegen(); + + start_fields.extend(fields); + + Some(quote! { + #ns + + #[derive(Debug, Clone, bon::Builder)] + #[builder(state_mod(vis = "pub(crate)"))] + pub struct #name { + #(#start_fields),* + } + + impl crate::request::IntoRequest for #name { + #[allow(unused_parens)] + type Discriminant = (#(#discriminant),*); + type Response = #response_ty; + fn into_request(self) -> crate::request::ApiRequest { + #[allow(unused_parens)] + crate::request::ApiRequest { + path: format!(#path_fmt_str, #(#fmt_val),*), + parameters: std::iter::empty() + #(#convert_field)* + .collect(), + disriminant: (#(#discriminant_val),*), + } + } + } + }) + } + + pub fn codegen_scope_call(&self) -> Option { + let mut extra_args = Vec::new(); + let mut disc = Vec::new(); + + let snake_name = self.name.to_snake_case(); + + let request_name = format_ident!("{}Request", self.name); + let builder_name = format_ident!("{}RequestBuilder", self.name); + let builder_mod_name = format_ident!("{}_request_builder", snake_name); + let request_mod_name = format_ident!("{snake_name}"); + + let request_path = quote! { crate::request::models::#request_name }; + let builder_path = quote! { crate::request::models::#builder_name }; + let builder_mod_path = quote! { crate::request::models::#builder_mod_name }; + + let tail = snake_name + .split_once('_') + .map_or_else(|| "for_selections".to_owned(), |(_, tail)| tail.to_owned()); + + let fn_name = format_ident!("{tail}"); + + for param in &self.parameters { + let (param, is_inline) = match param { + PathParameter::Inline(param) => (param, true), + PathParameter::Component(param) => (param, false), + }; + + if param.location == ParameterLocation::Path { + let ty = match ¶m.r#type { + ParameterType::I32 { .. } | ParameterType::Enum { .. } => { + let ty_name = format_ident!("{}", param.name); + + if is_inline { + quote! { + crate::request::models::#request_mod_name::#ty_name + } + } else { + quote! { + crate::parameters::#ty_name + } + } + } + ParameterType::String => quote! { String }, + ParameterType::Boolean => quote! { bool }, + ParameterType::Schema { type_name } => { + let ty_name = format_ident!("{}", type_name); + + quote! { + crate::models::#ty_name + } + } + ParameterType::Array { .. } => param.r#type.codegen_type_name(¶m.name), + }; + + let arg_name = format_ident!("{}", param.value.to_snake_case()); + + extra_args.push(quote! { #arg_name: #ty, }); + disc.push(arg_name); + } + } + + let response_ty = match &self.response { + PathResponse::Component { name } => { + let name = format_ident!("{name}"); + quote! { + crate::models::#name + } + } + PathResponse::ArbitraryUnion(union) => { + let name = format_ident!("{}", union.name); + quote! { + crate::request::models::#request_mod_name::#name + } + } + }; + + Some(quote! { + pub async fn #fn_name( + &self, + #(#extra_args)* + builder: impl FnOnce( + #builder_path<#builder_mod_path::Empty> + ) -> #builder_path, + ) -> Result<#response_ty, E::Error> + where + S: #builder_mod_path::IsComplete, + { + let r = builder(#request_path::builder(#(#disc),*)).build(); + + self.0.fetch(r).await + } + }) + } +} + +pub struct PathNamespace<'r> { + path: &'r Path, + ident: Option, + elements: Vec, +} + +impl PathNamespace<'_> { + pub fn get_ident(&mut self) -> Ident { + self.ident + .get_or_insert_with(|| { + let name = self.path.name.to_snake_case(); + format_ident!("{name}") + }) + .clone() + } + + pub fn push_element(&mut self, el: TokenStream) { + self.elements.push(el); + } + + pub fn codegen(mut self) -> Option { + if self.elements.is_empty() { + None + } else { + let ident = self.get_ident(); + let elements = self.elements; + Some(quote! { + pub mod #ident { + #(#elements)* + } + }) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::openapi::schema::OpenApiSchema; + + #[test] + fn resolve_paths() { + let schema = OpenApiSchema::read().unwrap(); + + let mut paths = 0; + let mut unresolved = vec![]; + + for (name, desc) in &schema.paths { + paths += 1; + if Path::from_schema(name, desc, &schema.components.parameters).is_none() { + unresolved.push(name); + } + } + + if !unresolved.is_empty() { + panic!( + "Failed to resolve {}/{} paths. Could not resolve [{}]", + unresolved.len(), + paths, + unresolved + .into_iter() + .map(|u| format!("`{u}`")) + .collect::>() + .join(", ") + ) + } + } + + #[test] + fn codegen_paths() { + let schema = OpenApiSchema::read().unwrap(); + + let mut paths = 0; + let mut unresolved = vec![]; + + for (name, desc) in &schema.paths { + paths += 1; + let Some(path) = Path::from_schema(name, desc, &schema.components.parameters) else { + unresolved.push(name); + continue; + }; + + if path.codegen_scope_call().is_none() || path.codegen_request().is_none() { + unresolved.push(name); + } + } + + if !unresolved.is_empty() { + panic!( + "Failed to codegen {}/{} paths. Could not resolve [{}]", + unresolved.len(), + paths, + unresolved + .into_iter() + .map(|u| format!("`{u}`")) + .collect::>() + .join(", ") + ) + } + } +} diff --git a/torn-api-codegen/src/model/scope.rs b/torn-api-codegen/src/model/scope.rs new file mode 100644 index 0000000..2aa57bc --- /dev/null +++ b/torn-api-codegen/src/model/scope.rs @@ -0,0 +1,64 @@ +use heck::ToUpperCamelCase; +use indexmap::IndexMap; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use super::path::{Path, PathSegment}; + +pub struct Scope { + pub name: String, + pub mod_name: String, + pub members: Vec, +} + +impl Scope { + pub fn from_paths(paths: Vec) -> Vec { + let mut map = IndexMap::new(); + + for path in paths { + let Some(PathSegment::Constant(first_seg)) = path.segments.first() else { + continue; + }; + + map.entry(first_seg.to_owned()) + .or_insert_with(|| Scope { + name: format!("{}Scope", first_seg.to_upper_camel_case()), + mod_name: first_seg.clone(), + members: Vec::new(), + }) + .members + .push(path); + } + + map.into_values().collect() + } + + pub fn codegen(&self) -> Option { + let name = format_ident!("{}", self.name); + + let mut functions = Vec::with_capacity(self.members.len()); + + for member in &self.members { + if let Some(code) = member.codegen_scope_call() { + functions.push(code); + } + } + + Some(quote! { + pub struct #name<'e, E>(&'e E) + where + E: crate::executor::Executor; + + impl<'e, E> #name<'e, E> + where + E: crate::executor::Executor + { + pub fn new(executor: &'e E) -> Self { + Self(executor) + } + + #(#functions)* + } + }) + } +} diff --git a/torn-api-codegen/src/model/union.rs b/torn-api-codegen/src/model/union.rs new file mode 100644 index 0000000..4e2575a --- /dev/null +++ b/torn-api-codegen/src/model/union.rs @@ -0,0 +1,50 @@ +use heck::ToSnakeCase; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::openapi::path::OpenApiResponseBody; + +#[derive(Debug, Clone)] +pub struct Union { + pub name: String, + pub members: Vec, +} + +impl Union { + pub fn from_schema(name: &str, schema: &OpenApiResponseBody) -> Option { + let members = match schema { + OpenApiResponseBody::Union { any_of } => { + any_of.iter().map(|l| l.ref_path.to_owned()).collect() + } + _ => return None, + }; + let name = name.to_owned(); + + Some(Self { name, members }) + } + + pub fn codegen(&self) -> Option { + let name = format_ident!("{}", self.name); + let mut variants = Vec::new(); + + for member in &self.members { + let variant_name = member.strip_prefix("#/components/schemas/")?; + let accessor_name = format_ident!("{}", variant_name.to_snake_case()); + let ty_name = format_ident!("{}", variant_name); + variants.push(quote! { + pub fn #accessor_name(&self) -> Result { + ::deserialize(&self.0) + } + }); + } + + Some(quote! { + #[derive(Debug, Clone, serde::Deserialize)] + pub struct #name(serde_json::Value); + + impl #name { + #(#variants)* + } + }) + } +} diff --git a/torn-api-codegen/src/openapi/mod.rs b/torn-api-codegen/src/openapi/mod.rs new file mode 100644 index 0000000..c1ab891 --- /dev/null +++ b/torn-api-codegen/src/openapi/mod.rs @@ -0,0 +1,4 @@ +pub mod parameter; +pub mod path; +pub mod schema; +pub mod r#type; diff --git a/torn-api-codegen/src/openapi/parameter.rs b/torn-api-codegen/src/openapi/parameter.rs new file mode 100644 index 0000000..0b341d2 --- /dev/null +++ b/torn-api-codegen/src/openapi/parameter.rs @@ -0,0 +1,40 @@ +use std::borrow::Cow; + +use serde::Deserialize; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ParameterLocation { + Query, + Path, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum OpenApiParameterDefault<'a> { + Int(i32), + Str(&'a str), +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OpenApiParameterSchema<'a> { + #[serde(rename = "$ref")] + pub ref_path: Option<&'a str>, + pub r#type: Option<&'a str>, + pub r#enum: Option>, + pub format: Option<&'a str>, + pub default: Option>, + pub maximum: Option, + pub minimum: Option, + pub items: Option>>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OpenApiParameter<'a> { + pub name: &'a str, + pub description: Option>, + pub r#in: ParameterLocation, + pub required: bool, + #[serde(borrow)] + pub schema: OpenApiParameterSchema<'a>, +} diff --git a/torn-api-codegen/src/openapi/path.rs b/torn-api-codegen/src/openapi/path.rs new file mode 100644 index 0000000..23c5767 --- /dev/null +++ b/torn-api-codegen/src/openapi/path.rs @@ -0,0 +1,81 @@ +use std::borrow::Cow; + +use serde::{Deserialize, Deserializer}; + +use super::parameter::OpenApiParameter; + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum OpenApiPathParameter<'a> { + Link { + #[serde(rename = "$ref")] + ref_path: &'a str, + }, + Inline(OpenApiParameter<'a>), +} + +#[derive(Debug, Clone, Deserialize)] +pub struct SchemaLink<'a> { + #[serde(rename = "$ref")] + pub ref_path: &'a str, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum OpenApiResponseBody<'a> { + Schema(SchemaLink<'a>), + Union { + #[serde(borrow, rename = "anyOf")] + any_of: Vec>, + }, +} + +fn deserialize_response_body<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Json<'a> { + #[serde(borrow)] + schema: OpenApiResponseBody<'a>, + } + #[derive(Deserialize)] + struct Content<'a> { + #[serde(borrow, rename = "application/json")] + json: Json<'a>, + } + #[derive(Deserialize)] + struct StatusOk<'a> { + #[serde(borrow)] + content: Content<'a>, + } + #[derive(Deserialize)] + struct Responses<'a> { + #[serde(borrow, rename = "200")] + ok: StatusOk<'a>, + } + + let responses = Responses::deserialize(deserializer)?; + + Ok(responses.ok.content.json.schema) +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OpenApiPathBody<'a> { + pub summary: Option>, + pub description: Cow<'a, str>, + #[serde(borrow, default)] + pub parameters: Vec>, + #[serde( + borrow, + rename = "responses", + deserialize_with = "deserialize_response_body" + )] + pub response_content: OpenApiResponseBody<'a>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OpenApiPath<'a> { + #[serde(borrow)] + pub get: OpenApiPathBody<'a>, +} diff --git a/torn-api-codegen/src/openapi/schema.rs b/torn-api-codegen/src/openapi/schema.rs new file mode 100644 index 0000000..2450f01 --- /dev/null +++ b/torn-api-codegen/src/openapi/schema.rs @@ -0,0 +1,38 @@ +use indexmap::IndexMap; +use serde::Deserialize; + +use super::{parameter::OpenApiParameter, path::OpenApiPath, r#type::OpenApiType}; + +#[derive(Debug, Clone, Deserialize)] +pub struct Components<'a> { + #[serde(borrow)] + pub schemas: IndexMap<&'a str, OpenApiType<'a>>, + #[serde(borrow)] + pub parameters: IndexMap<&'a str, OpenApiParameter<'a>>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OpenApiSchema<'a> { + #[serde(borrow)] + pub paths: IndexMap<&'a str, OpenApiPath<'a>>, + #[serde(borrow)] + pub components: Components<'a>, +} + +impl OpenApiSchema<'_> { + pub fn read() -> Result { + let s = include_str!("../../openapi.json"); + + serde_json::from_str(s) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn read() { + OpenApiSchema::read().unwrap(); + } +} diff --git a/torn-api-codegen/src/openapi/type.rs b/torn-api-codegen/src/openapi/type.rs new file mode 100644 index 0000000..44f31e9 --- /dev/null +++ b/torn-api-codegen/src/openapi/type.rs @@ -0,0 +1,98 @@ +use std::borrow::Cow; + +use indexmap::IndexMap; +use serde::Deserialize; + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(untagged)] +pub enum OpenApiVariants<'a> { + Int(Vec), + #[serde(borrow)] + Str(Vec<&'a str>), +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OpenApiType<'a> { + #[serde(default)] + pub deprecated: bool, + pub description: Option>, + + pub r#type: Option<&'a str>, + pub format: Option<&'a str>, + + #[serde(rename = "$ref")] + pub ref_path: Option<&'a str>, + + pub one_of: Option>>, + pub all_of: Option>>, + + pub required: Option>, + #[serde(borrow)] + pub properties: Option>>, + + pub items: Option>>, + pub r#enum: Option>, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn object() { + let json = r##" + { + "required": [ + "name", + "branches" + ], + "properties": { + "name": { + "type": "string" + }, + "branches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TornFactionTreeBranch" + } + } + }, + "type": "object" + } + "##; + + let obj: OpenApiType = serde_json::from_str(json).unwrap(); + + assert_eq!(obj.r#type, Some("object")); + + let props = obj.properties.unwrap(); + + assert!(props.contains_key("name")); + + let branches = props.get("branches").unwrap(); + assert_eq!(branches.r#type, Some("array")); + + let items = branches.items.as_ref().unwrap(); + assert!(items.ref_path.is_some()); + } + + #[test] + fn enum_variants() { + let int_json = r#" + [1, 2, 3, 4] + "#; + + let de: OpenApiVariants = serde_json::from_str(int_json).unwrap(); + + assert_eq!(de, OpenApiVariants::Int(vec![1, 2, 3, 4])); + + let str_json = r#" + ["foo", "bar", "baz"] + "#; + + let de: OpenApiVariants = serde_json::from_str(str_json).unwrap(); + + assert_eq!(de, OpenApiVariants::Str(vec!["foo", "bar", "baz"])); + } +} diff --git a/torn-api/Cargo.toml b/torn-api/Cargo.toml index 9bfe952..c48651b 100644 --- a/torn-api/Cargo.toml +++ b/torn-api/Cargo.toml @@ -1,51 +1,27 @@ [package] name = "torn-api" -version = "0.7.5" -edition = "2021" -rust-version = "1.75.0" -authors = ["Pyrit [2111649]"] -license = "MIT" -repository = "https://github.com/TotallyNot/torn-api.rs.git" -homepage = "https://github.com/TotallyNot/torn-api.rs.git" -description = "Torn API bindings for rust" - -[[bench]] -name = "deserialisation_benchmark" -harness = false - -[features] -default = [ "reqwest", "user", "faction", "torn", "key", "market" ] -reqwest = [ "dep:reqwest" ] -awc = [ "dep:awc" ] -decimal = [ "dep:rust_decimal" ] - -user = [ "__common" ] -faction = [ "__common" ] -torn = [ "__common" ] -market = [ "__common" ] -key = [] - -__common = [] +version = "1.0.0" +edition = "2024" [dependencies] -serde = { version = "1", features = [ "derive" ] } -serde_json = "1" -chrono = { version = "0.4.31", features = [ "serde" ], default-features = false } -async-trait = "0.1" -thiserror = "1" -futures = "0.3" - -reqwest = { version = "0.12", default-features = false, features = [ "json" ], optional = true } -awc = { version = "3", default-features = false, optional = true } -rust_decimal = { version = "1", default-features = false, optional = true, features = [ "serde" ] } - -torn-api-macros = { path = "../torn-api-macros", version = "0.3.1" } +serde = { workspace = true, features = ["derive"] } +serde_repr = "0.1" +serde_json = { workspace = true } +bon = "3.6" +bytes = "1" +http = "1" +reqwest = { version = "0.12", default-features = false, features = [ + "rustls-tls", + "json", + "brotli", +] } +thiserror = "2" [dev-dependencies] -actix-rt = { version = "2.7.0" } -dotenv = "0.15.0" -tokio = { version = "1.20.1", features = ["test-util", "rt", "macros"] } -tokio-test = "0.4.2" -reqwest = { version = "0.12", default-features = true } -awc = { version = "3", features = [ "rustls" ] } -criterion = "0.5" +tokio = { version = "1", features = ["full"] } + +[build-dependencies] +torn-api-codegen = { path = "../torn-api-codegen" } +syn = { workspace = true, features = ["parsing"] } +proc-macro2 = { workspace = true } +prettyplease = "0.2" diff --git a/torn-api/benches/deserialisation_benchmark.rs b/torn-api/benches/deserialisation_benchmark.rs deleted file mode 100644 index 0eb52b0..0000000 --- a/torn-api/benches/deserialisation_benchmark.rs +++ /dev/null @@ -1,90 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use torn_api::{faction, send::ApiClient, user}; - -pub fn user_benchmark(c: &mut Criterion) { - dotenv::dotenv().unwrap(); - let rt = tokio::runtime::Builder::new_current_thread() - .enable_io() - .enable_time() - .build() - .unwrap(); - let response = rt.block_on(async { - let key = std::env::var("APIKEY").expect("api key"); - let client = reqwest::Client::default(); - - client - .torn_api(key) - .user(|b| { - b.selections([ - user::Selection::Basic, - user::Selection::Discord, - user::Selection::Profile, - user::Selection::PersonalStats, - ]) - }) - .await - .unwrap() - }); - - c.bench_function("user deserialize", |b| { - b.iter(|| { - response.basic().unwrap(); - response.discord().unwrap(); - response.profile().unwrap(); - response.personal_stats().unwrap(); - }) - }); -} - -pub fn faction_benchmark(c: &mut Criterion) { - dotenv::dotenv().unwrap(); - let rt = tokio::runtime::Builder::new_current_thread() - .enable_io() - .enable_time() - .build() - .unwrap(); - let response = rt.block_on(async { - let key = std::env::var("APIKEY").expect("api key"); - let client = reqwest::Client::default(); - - client - .torn_api(key) - .faction(|b| b.selections([faction::Selection::Basic])) - .await - .unwrap() - }); - - c.bench_function("faction deserialize", |b| { - b.iter(|| { - response.basic().unwrap(); - }) - }); -} - -pub fn attacks_full(c: &mut Criterion) { - dotenv::dotenv().unwrap(); - let rt = tokio::runtime::Builder::new_current_thread() - .enable_io() - .enable_time() - .build() - .unwrap(); - let response = rt.block_on(async { - let key = std::env::var("APIKEY").expect("api key"); - let client = reqwest::Client::default(); - - client - .torn_api(key) - .faction(|b| b.selections([faction::Selection::AttacksFull])) - .await - .unwrap() - }); - - c.bench_function("attacksfull deserialize", |b| { - b.iter(|| { - response.attacks_full().unwrap(); - }) - }); -} - -criterion_group!(benches, user_benchmark, faction_benchmark, attacks_full); -criterion_main!(benches); diff --git a/torn-api/build.rs b/torn-api/build.rs new file mode 100644 index 0000000..136a95e --- /dev/null +++ b/torn-api/build.rs @@ -0,0 +1,75 @@ +use std::{env, fs, path::Path}; + +use proc_macro2::TokenStream; +use torn_api_codegen::{ + model::{parameter::Parameter, path::Path as ApiPath, resolve, scope::Scope}, + openapi::schema::OpenApiSchema, +}; + +const DENY_LIST: &[&str] = &[]; + +fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let model_dest = Path::new(&out_dir).join("models.rs"); + let params_dest = Path::new(&out_dir).join("parameters.rs"); + let requests_dest = Path::new(&out_dir).join("requests.rs"); + let scopes_dest = Path::new(&out_dir).join("scopes.rs"); + + let schema = OpenApiSchema::read().unwrap(); + + let mut models_code = TokenStream::new(); + + for (name, model) in &schema.components.schemas { + if DENY_LIST.contains(name) { + continue; + } + let model = resolve(model, name, &schema.components.schemas); + if let Some(new_code) = model.codegen() { + models_code.extend(new_code); + } + } + + let models_file = syn::parse2(models_code).unwrap(); + let models_pretty = prettyplease::unparse(&models_file); + fs::write(&model_dest, models_pretty).unwrap(); + + let mut params_code = TokenStream::new(); + + for (name, param) in &schema.components.parameters { + if let Some(code) = Parameter::from_schema(name, param).unwrap().codegen() { + params_code.extend(code); + } + } + + let params_file = syn::parse2(params_code).unwrap(); + let params_pretty = prettyplease::unparse(¶ms_file); + fs::write(¶ms_dest, params_pretty).unwrap(); + + let mut requests_code = TokenStream::new(); + let mut paths = Vec::new(); + for (name, path) in &schema.paths { + let Some(path) = ApiPath::from_schema(name, path, &schema.components.parameters) else { + continue; + }; + if let Some(code) = path.codegen_request() { + requests_code.extend(code); + } + paths.push(path); + } + + let requests_file = syn::parse2(requests_code).unwrap(); + let requests_pretty = prettyplease::unparse(&requests_file); + fs::write(&requests_dest, requests_pretty).unwrap(); + + let mut scope_code = TokenStream::new(); + let scopes = Scope::from_paths(paths); + for scope in scopes { + if let Some(code) = scope.codegen() { + scope_code.extend(code); + } + } + + let scopes_file = syn::parse2(scope_code).unwrap(); + let scopes_pretty = prettyplease::unparse(&scopes_file); + fs::write(&scopes_dest, scopes_pretty).unwrap(); +} diff --git a/torn-api/src/awc.rs b/torn-api/src/awc.rs deleted file mode 100644 index 7e16904..0000000 --- a/torn-api/src/awc.rs +++ /dev/null @@ -1,22 +0,0 @@ -use async_trait::async_trait; -use thiserror::Error; - -use crate::local::ApiClient; - -#[derive(Error, Debug)] -pub enum AwcApiClientError { - #[error(transparent)] - Client(#[from] awc::error::SendRequestError), - - #[error(transparent)] - Payload(#[from] awc::error::JsonPayloadError), -} - -#[async_trait(?Send)] -impl ApiClient for awc::Client { - type Error = AwcApiClientError; - - async fn request(&self, url: String) -> Result { - self.get(url).send().await?.json().await.map_err(Into::into) - } -} diff --git a/torn-api/src/common.rs b/torn-api/src/common.rs deleted file mode 100644 index e461dd7..0000000 --- a/torn-api/src/common.rs +++ /dev/null @@ -1,172 +0,0 @@ -use chrono::{serde::ts_seconds, DateTime, Utc}; -use serde::Deserialize; -use torn_api_macros::IntoOwned; - -use crate::de_util; - -#[derive(Debug, Clone, Deserialize)] -pub enum OnlineStatus { - Online, - Offline, - Idle, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct LastAction { - #[serde(with = "ts_seconds")] - pub timestamp: DateTime, - pub status: OnlineStatus, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] -pub enum State { - Okay, - Traveling, - Hospital, - Abroad, - Jail, - Federal, - Fallen, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum StateColour { - Green, - Red, - Blue, -} - -#[derive(Debug, IntoOwned, Deserialize)] -pub struct Status<'a> { - pub description: &'a str, - #[serde(deserialize_with = "de_util::empty_string_is_none")] - pub details: Option<&'a str>, - #[serde(rename = "color")] - pub colour: StateColour, - pub state: State, - #[serde(deserialize_with = "de_util::zero_date_is_none")] - pub until: Option>, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Territory { - pub sector: i16, - pub size: i16, - pub density: i16, - pub daily_respect: i16, - pub faction: i32, - - #[cfg(feature = "decimal")] - #[serde(deserialize_with = "de_util::string_or_decimal")] - pub coordinate_x: rust_decimal::Decimal, - - #[cfg(feature = "decimal")] - #[serde(deserialize_with = "de_util::string_or_decimal")] - pub coordinate_y: rust_decimal::Decimal, -} - -#[derive(Debug, Clone, Copy, Deserialize)] -pub enum AttackResult { - Attacked, - Mugged, - Hospitalized, - Lost, - Arrested, - Escape, - Interrupted, - Assist, - Timeout, - Stalemate, - Special, - Looted, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Attack<'a> { - pub code: &'a str, - #[serde(with = "ts_seconds")] - pub timestamp_started: DateTime, - #[serde(with = "ts_seconds")] - pub timestamp_ended: DateTime, - - #[serde(deserialize_with = "de_util::empty_string_int_option")] - pub attacker_id: Option, - #[serde(deserialize_with = "de_util::empty_string_int_option")] - pub attacker_faction: Option, - pub defender_id: i32, - #[serde(deserialize_with = "de_util::empty_string_int_option")] - pub defender_faction: Option, - pub result: AttackResult, - - #[serde(deserialize_with = "de_util::int_is_bool")] - pub stealthed: bool, - - #[cfg(feature = "decimal")] - pub respect: rust_decimal::Decimal, - - #[cfg(not(feature = "decimal"))] - pub respect: f32, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct RespectModifiers { - pub fair_fight: f32, - pub war: f32, - pub retaliation: f32, - pub group_attack: f32, - pub overseas: f32, - pub chain_bonus: f32, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct AttackFull<'a> { - pub code: &'a str, - #[serde(with = "ts_seconds")] - pub timestamp_started: DateTime, - #[serde(with = "ts_seconds")] - pub timestamp_ended: DateTime, - - #[serde(deserialize_with = "de_util::empty_string_int_option")] - pub attacker_id: Option, - #[serde(deserialize_with = "de_util::empty_string_is_none")] - pub attacker_name: Option<&'a str>, - #[serde(deserialize_with = "de_util::empty_string_int_option")] - pub attacker_faction: Option, - #[serde( - deserialize_with = "de_util::empty_string_is_none", - rename = "attacker_factionname" - )] - pub attacker_faction_name: Option<&'a str>, - - pub defender_id: i32, - pub defender_name: &'a str, - #[serde(deserialize_with = "de_util::empty_string_int_option")] - pub defender_faction: Option, - #[serde( - deserialize_with = "de_util::empty_string_is_none", - rename = "defender_factionname" - )] - pub defender_faction_name: Option<&'a str>, - - pub result: AttackResult, - - #[serde(deserialize_with = "de_util::int_is_bool")] - pub stealthed: bool, - #[serde(deserialize_with = "de_util::int_is_bool")] - pub raid: bool, - #[serde(deserialize_with = "de_util::int_is_bool")] - pub ranked_war: bool, - - #[cfg(feature = "decimal")] - pub respect: rust_decimal::Decimal, - #[cfg(feature = "decimal")] - pub respect_loss: rust_decimal::Decimal, - - #[cfg(not(feature = "decimal"))] - pub respect: f32, - #[cfg(not(feature = "decimal"))] - pub respect_loss: f32, - - pub modifiers: RespectModifiers, -} diff --git a/torn-api/src/de_util.rs b/torn-api/src/de_util.rs deleted file mode 100644 index 939cfa6..0000000 --- a/torn-api/src/de_util.rs +++ /dev/null @@ -1,245 +0,0 @@ -#![allow(unused)] - -use std::collections::{BTreeMap, HashMap}; - -use chrono::{serde::ts_nanoseconds::deserialize, DateTime, NaiveDateTime, Utc}; -use serde::de::{Deserialize, Deserializer, Error, Unexpected, Visitor}; - -pub(crate) fn empty_string_is_none<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let s: &str = Deserialize::deserialize(deserializer)?; - if s.is_empty() { - Ok(None) - } else { - Ok(Some(s)) - } -} - -pub(crate) fn string_is_long<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - if s.is_empty() { - Ok(None) - } else { - s.parse() - .map(Some) - .map_err(|_e| Error::invalid_type(Unexpected::Str(&s), &"i64")) - } -} - -pub(crate) fn zero_date_is_none<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let i = i64::deserialize(deserializer)?; - if i == 0 { - Ok(None) - } else { - Ok(DateTime::from_timestamp(i, 0)) - } -} - -pub(crate) fn int_is_bool<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let i = i64::deserialize(deserializer)?; - - match i { - 0 => Ok(false), - 1 => Ok(true), - x => Err(Error::invalid_value(Unexpected::Signed(x), &"0 or 1")), - } -} - -pub(crate) fn empty_string_int_option<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct DumbVisitor; - - impl<'de> Visitor<'de> for DumbVisitor { - type Value = Option; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "Empty string or integer") - } - - // serde_json will treat all unsigned integers as u64 - fn visit_u64(self, v: u64) -> Result - where - E: Error, - { - Ok(Some(v as i32)) - } - - fn visit_borrowed_str(self, v: &'de str) -> Result - where - E: Error, - { - if v.is_empty() { - Ok(None) - } else { - Err(E::invalid_value(Unexpected::Str(v), &self)) - } - } - } - - deserializer.deserialize_any(DumbVisitor) -} - -pub(crate) fn datetime_map<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(serde::Deserialize)] - struct UnixTimestamp( - #[serde(with = "chrono::serde::ts_seconds")] chrono::DateTime, - ); - - struct MapVisitor; - - impl<'de> Visitor<'de> for MapVisitor { - type Value = BTreeMap>; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "map of unix timestamps") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - let mut result = BTreeMap::new(); - while let Some(key) = map.next_key::<&'de str>()? { - let id = key - .parse() - .map_err(|_e| A::Error::invalid_value(Unexpected::Str(key), &"integer"))?; - - let ts: UnixTimestamp = map.next_value()?; - result.insert(id, ts.0); - } - - Ok(result) - } - } - - deserializer.deserialize_map(MapVisitor) -} - -pub(crate) fn empty_dict_is_empty_array<'de, D, T>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, - T: Deserialize<'de>, -{ - struct ArrayVisitor(std::marker::PhantomData); - - impl<'de, T> Visitor<'de> for ArrayVisitor - where - T: Deserialize<'de>, - { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "vec or empty object") - } - - fn visit_map(self, map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - match map.size_hint() { - Some(0) | None => Ok(Vec::default()), - Some(len) => Err(A::Error::invalid_length(len, &"empty dict")), - } - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut result = match seq.size_hint() { - Some(len) => Vec::with_capacity(len), - None => Vec::default(), - }; - - while let Some(element) = seq.next_element()? { - result.push(element); - } - - Ok(result) - } - } - - deserializer.deserialize_any(ArrayVisitor(std::marker::PhantomData)) -} - -pub(crate) fn zero_is_none<'de, D, I>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, - I: TryFrom, -{ - let num = i64::deserialize(deserializer)?; - - if num == 0 { - Ok(None) - } else { - Ok(Some(num.try_into().map_err(|_| { - D::Error::invalid_value(Unexpected::Signed(num), &std::any::type_name::()) - })?)) - } -} - -pub(crate) fn null_is_empty_dict<'de, D, K, V>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, - K: std::hash::Hash + std::cmp::Eq + Deserialize<'de>, - V: Deserialize<'de>, -{ - Ok(Option::deserialize(deserializer)?.unwrap_or_default()) -} - -#[cfg(feature = "decimal")] -pub(crate) fn string_or_decimal<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct DumbVisitor; - - impl<'de> Visitor<'de> for DumbVisitor { - type Value = rust_decimal::Decimal; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "integer or float as string") - } - - fn visit_u64(self, v: u64) -> Result - where - E: Error, - { - Ok(v.into()) - } - - fn visit_i64(self, v: i64) -> Result - where - E: Error, - { - Ok(v.into()) - } - - fn visit_str(self, v: &str) -> Result - where - E: Error, - { - rust_decimal::Decimal::from_str_exact(v).map_err(E::custom) - } - } - - deserializer.deserialize_any(DumbVisitor) -} diff --git a/torn-api/src/executor.rs b/torn-api/src/executor.rs new file mode 100644 index 0000000..c0d19b6 --- /dev/null +++ b/torn-api/src/executor.rs @@ -0,0 +1,156 @@ +use http::{HeaderMap, HeaderValue, header::AUTHORIZATION}; +use serde::Deserialize; + +use crate::{ + request::{ApiResponse, IntoRequest}, + scopes::{FactionScope, ForumScope, MarketScope, RacingScope, TornScope, UserScope}, +}; + +pub trait Executor { + type Error: From + From + Send; + + fn execute( + &self, + request: R, + ) -> impl Future, Self::Error>> + Send + where + R: IntoRequest; + + fn fetch(&self, request: R) -> impl Future> + Send + where + R: IntoRequest, + { + // HACK: workaround for not using `async` in trait declaration. + // The future is `Send` but `&self` might not be. + let fut = self.execute(request); + async { + let resp = fut.await?; + + let bytes = resp.body.unwrap(); + + if bytes.starts_with(br#"{"error":{"#) { + #[derive(Deserialize)] + struct ErrorBody<'a> { + code: u16, + error: &'a str, + } + #[derive(Deserialize)] + struct ErrorContainer<'a> { + #[serde(borrow)] + error: ErrorBody<'a>, + } + + let error: ErrorContainer = serde_json::from_slice(&bytes)?; + return Err(crate::ApiError::new(error.error.code, error.error.error).into()); + } + + let resp = serde_json::from_slice(&bytes)?; + + Ok(resp) + } + } +} + +pub struct ReqwestClient(reqwest::Client); + +impl ReqwestClient { + pub fn new(api_key: &str) -> Self { + let mut headers = HeaderMap::with_capacity(1); + headers.insert( + AUTHORIZATION, + HeaderValue::from_str(&format!("ApiKey {api_key}")).unwrap(), + ); + + let client = reqwest::Client::builder() + .default_headers(headers) + .brotli(true) + .build() + .unwrap(); + + Self(client) + } +} + +pub trait ExecutorExt: Executor + Sized { + fn user(&self) -> UserScope<'_, Self>; + + fn faction(&self) -> FactionScope<'_, Self>; + + fn torn(&self) -> TornScope<'_, Self>; + + fn market(&self) -> MarketScope<'_, Self>; + + fn racing(&self) -> RacingScope<'_, Self>; + + fn forum(&self) -> ForumScope<'_, Self>; +} + +impl ExecutorExt for T +where + T: Executor + Sized, +{ + fn user(&self) -> UserScope<'_, Self> { + UserScope::new(self) + } + + fn faction(&self) -> FactionScope<'_, Self> { + FactionScope::new(self) + } + + fn torn(&self) -> TornScope<'_, Self> { + TornScope::new(self) + } + + fn market(&self) -> MarketScope<'_, Self> { + MarketScope::new(self) + } + + fn racing(&self) -> RacingScope<'_, Self> { + RacingScope::new(self) + } + + fn forum(&self) -> ForumScope<'_, Self> { + ForumScope::new(self) + } +} + +impl Executor for ReqwestClient { + type Error = crate::Error; + + async fn execute(&self, request: R) -> Result, Self::Error> + where + R: IntoRequest, + { + let request = request.into_request(); + let url = request.url(); + + let response = self.0.get(url).send().await?; + let status = response.status(); + let body = response.bytes().await.ok(); + + Ok(ApiResponse { + discriminant: request.disriminant, + status, + body, + }) + } +} + +#[cfg(test)] +mod test { + use crate::{ApiError, Error, scopes::test::test_client}; + + use super::*; + + #[tokio::test] + async fn api_error() { + let client = test_client().await; + + let resp = client.faction().basic_for_id((-1).into(), |b| b).await; + + match resp { + Err(Error::Api(ApiError::IncorrectIdEntityRelation)) => (), + other => panic!("Expected incorrect id entity relation error, got {other:?}"), + } + } +} diff --git a/torn-api/src/faction.rs b/torn-api/src/faction.rs deleted file mode 100644 index dbc37aa..0000000 --- a/torn-api/src/faction.rs +++ /dev/null @@ -1,278 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use chrono::{DateTime, TimeZone, Utc}; -use serde::{ - de::{Error, Unexpected, Visitor}, - Deserialize, Deserializer, -}; - -use torn_api_macros::{ApiCategory, IntoOwned}; - -use crate::de_util::{self, null_is_empty_dict}; - -pub use crate::common::{Attack, AttackFull, LastAction, Status, Territory}; - -#[derive(Debug, Clone, Copy, ApiCategory)] -#[api(category = "faction")] -#[non_exhaustive] -pub enum FactionSelection { - #[api(type = "Basic", flatten)] - Basic, - - #[api(type = "BTreeMap", field = "attacks")] - AttacksFull, - - #[api(type = "BTreeMap", field = "attacks")] - Attacks, - - #[api( - type = "HashMap", - field = "territory", - with = "null_is_empty_dict" - )] - Territory, - - #[api(type = "Option", field = "chain", with = "deserialize_chain")] - Chain, -} - -pub type Selection = FactionSelection; - -#[derive(Debug, IntoOwned, Deserialize)] -pub struct Member<'a> { - pub name: &'a str, - pub level: i16, - pub days_in_faction: i16, - pub position: &'a str, - pub status: Status<'a>, - pub last_action: LastAction, -} - -#[derive(Debug, IntoOwned, Deserialize)] -pub struct FactionTerritoryWar<'a> { - pub territory_war_id: i32, - pub territory: &'a str, - pub assaulting_faction: i32, - pub defending_faction: i32, - pub score: i32, - pub required_score: i32, - - #[serde(with = "chrono::serde::ts_seconds")] - pub start_time: DateTime, - - #[serde(with = "chrono::serde::ts_seconds")] - pub end_time: DateTime, -} - -#[derive(Debug, IntoOwned, Deserialize)] -pub struct Basic<'a> { - #[serde(rename = "ID")] - pub id: i32, - pub name: &'a str, - pub leader: i32, - - pub respect: i32, - pub age: i16, - pub capacity: i16, - pub best_chain: i32, - - #[serde(deserialize_with = "de_util::empty_string_is_none")] - pub tag_image: Option<&'a str>, - - #[serde(borrow)] - pub members: BTreeMap>, - - #[serde(deserialize_with = "de_util::datetime_map")] - pub peace: BTreeMap>, - - #[serde(borrow, deserialize_with = "de_util::empty_dict_is_empty_array")] - pub territory_wars: Vec>, -} - -#[derive(Debug)] -pub struct Chain { - pub current: i32, - pub max: i32, - #[cfg(feature = "decimal")] - pub modifier: rust_decimal::Decimal, - pub timeout: Option, - pub cooldown: Option, - pub start: DateTime, - pub end: DateTime, -} - -fn deserialize_chain<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct ChainVisitor; - - impl<'de> Visitor<'de> for ChainVisitor { - type Value = Option; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct Chain") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - #[derive(Deserialize)] - #[serde(rename_all = "snake_case")] - enum Fields { - Current, - Max, - Modifier, - Timeout, - Cooldown, - Start, - End, - #[serde(other)] - Ignore, - } - - let mut current = None; - let mut max = None; - #[cfg(feature = "decimal")] - let mut modifier = None; - let mut timeout = None; - let mut cooldown = None; - let mut start = None; - let mut end = None; - - while let Some(key) = map.next_key()? { - match key { - Fields::Current => { - let value = map.next_value()?; - if value != 0 { - current = Some(value); - } - } - Fields::Max => { - max = Some(map.next_value()?); - } - Fields::Modifier => { - #[cfg(feature = "decimal")] - { - modifier = Some(map.next_value()?); - } - } - Fields::Timeout => { - match map.next_value()? { - 0 => timeout = Some(None), - val => timeout = Some(Some(val)), - }; - } - Fields::Cooldown => { - match map.next_value()? { - 0 => cooldown = Some(None), - val => cooldown = Some(Some(val)), - }; - } - Fields::Start => { - let ts: i64 = map.next_value()?; - start = Some(Utc.timestamp_opt(ts, 0).single().ok_or_else(|| { - A::Error::invalid_value(Unexpected::Signed(ts), &"Epoch timestamp") - })?); - } - Fields::End => { - let ts: i64 = map.next_value()?; - end = Some(Utc.timestamp_opt(ts, 0).single().ok_or_else(|| { - A::Error::invalid_value(Unexpected::Signed(ts), &"Epoch timestamp") - })?); - } - Fields::Ignore => (), - } - } - - let Some(current) = current else { - return Ok(None); - }; - let max = max.ok_or_else(|| A::Error::missing_field("max"))?; - let timeout = timeout.ok_or_else(|| A::Error::missing_field("timeout"))?; - let cooldown = cooldown.ok_or_else(|| A::Error::missing_field("cooldown"))?; - let start = start.ok_or_else(|| A::Error::missing_field("start"))?; - let end = end.ok_or_else(|| A::Error::missing_field("end"))?; - - Ok(Some(Chain { - current, - max, - #[cfg(feature = "decimal")] - modifier: modifier.ok_or_else(|| A::Error::missing_field("modifier"))?, - timeout, - cooldown, - start, - end, - })) - } - } - - deserializer.deserialize_map(ChainVisitor) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{async_test, setup, Client, ClientTrait}; - - #[async_test] - async fn faction() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .faction(|b| { - b.selections([ - Selection::Basic, - Selection::Attacks, - Selection::Territory, - Selection::Chain, - ]) - }) - .await - .unwrap(); - - response.basic().unwrap(); - response.attacks().unwrap(); - response.attacks_full().unwrap(); - response.territory().unwrap(); - response.chain().unwrap(); - } - - #[async_test] - async fn faction_public() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .faction(|b| { - b.id(7049) - .selections([Selection::Basic, Selection::Territory, Selection::Chain]) - }) - .await - .unwrap(); - - response.basic().unwrap(); - response.territory().unwrap(); - response.chain().unwrap(); - } - - #[async_test] - async fn destroyed_faction() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .faction(|b| { - b.id(8981) - .selections([Selection::Basic, Selection::Territory, Selection::Chain]) - }) - .await - .unwrap(); - - response.basic().unwrap(); - response.territory().unwrap(); - assert!(response.chain().unwrap().is_none()); - } -} diff --git a/torn-api/src/into_owned.rs b/torn-api/src/into_owned.rs deleted file mode 100644 index 78e4abc..0000000 --- a/torn-api/src/into_owned.rs +++ /dev/null @@ -1,79 +0,0 @@ -pub use torn_api_macros::IntoOwned; - -pub trait IntoOwned { - type Owned; - - fn into_owned(self) -> Self::Owned; -} - -impl IntoOwned for Option -where - T: IntoOwned, -{ - type Owned = Option; - - fn into_owned(self) -> Self::Owned { - self.map(IntoOwned::into_owned) - } -} - -impl IntoOwned for Vec where T: IntoOwned { - type Owned = Vec<::Owned>; - - fn into_owned(self) -> Self::Owned { - let mut owned = Vec::with_capacity(self.len()); - for elem in self { - owned.push(elem.into_owned()); - } - owned - } -} - -impl IntoOwned for std::collections::HashMap where V: IntoOwned, K: Eq + std::hash::Hash { - type Owned = std::collections::HashMap::Owned>; - - fn into_owned(self) -> Self::Owned { - self.into_iter().map(|(k, v)| (k, v.into_owned())).collect() - } -} - -impl IntoOwned for std::collections::BTreeMap where V: IntoOwned, K: Eq + Ord + std::hash::Hash { - type Owned = std::collections::BTreeMap::Owned>; - - fn into_owned(self) -> Self::Owned { - self.into_iter().map(|(k, v)| (k, v.into_owned())).collect() - } -} - -impl IntoOwned for chrono::DateTime where Z: chrono::TimeZone { - type Owned = Self; - - fn into_owned(self) -> Self::Owned { - self - } -} - -impl<'a> IntoOwned for &'a str { - type Owned = String; - - fn into_owned(self) -> Self::Owned { - self.to_owned() - } -} - -macro_rules! impl_ident { - ($name:path) => { - impl IntoOwned for $name { - type Owned = $name; - fn into_owned(self) -> Self::Owned { - self - } - } - }; -} - -impl_ident!(i64); -impl_ident!(i32); -impl_ident!(i16); -impl_ident!(i8); -impl_ident!(String); diff --git a/torn-api/src/key.rs b/torn-api/src/key.rs deleted file mode 100644 index 08776ac..0000000 --- a/torn-api/src/key.rs +++ /dev/null @@ -1,256 +0,0 @@ -use std::collections::HashSet; - -use serde::{Deserialize, Serialize}; -use torn_api_macros::ApiCategory; - -#[derive(Debug, Clone, Copy, ApiCategory)] -#[api(category = "key")] -#[non_exhaustive] -pub enum Selection { - #[api(type = "Info", flatten)] - Info, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[non_exhaustive] -pub enum AccessType { - #[serde(rename = "Custom")] - Custom, - - #[serde(rename = "Public Only")] - Public, - - #[serde(rename = "Minimal Access")] - Minimal, - - #[serde(rename = "Limited Access")] - Limited, - - #[serde(rename = "Full Access")] - Full, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum KeySelection { - Info, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum UserSelection { - Ammo, - Attacks, - AttacksFull, - Bars, - Basic, - BattleStats, - Bazaar, - Cooldowns, - Crimes, - Discord, - Display, - Education, - Events, - Gym, - Hof, - Honors, - Icons, - Inventory, - JobPoints, - Log, - Medals, - Merits, - Messages, - Missions, - Money, - Networth, - NewEvents, - NewMessages, - Notifications, - Perks, - PersonalStats, - Profile, - Properties, - ReceivedEvents, - Refills, - Reports, - Revives, - RevivesFull, - Skills, - Stocks, - Timestamp, - Travel, - WeaponExp, - WorkStats, - Lookup, - PublicStatus, - #[serde(other)] - Unknown, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum FactionSelection { - Applications, - Armor, - ArmoryNews, - AttackNews, - Attacks, - AttacksFull, - Basic, - Boosters, - Cesium, - Chain, - ChainReport, - Chains, - Contributors, - Crimenews, - Crimes, - Currency, - Donations, - Drugs, - FundsNews, - MainNews, - Medical, - MembershipNews, - Positions, - Reports, - Revives, - RevivesFull, - Stats, - Temporary, - Territory, - TerritoryNews, - Timestamp, - Upgrades, - Weapons, - Lookup, - Caches, - CrimeExp, - #[serde(other)] - Unknown, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum CompanySelection { - Applications, - Companies, - Detailed, - Employees, - News, - NewsFull, - Profile, - Stock, - Timestamp, - Lookup, - #[serde(other)] - Unknown, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum TornSelection { - Bank, - Cards, - ChainReport, - Companies, - Competition, - Education, - FactionTree, - Gyms, - Honors, - Items, - ItemStats, - LogCategories, - LogTypes, - Medals, - OrganisedCrimes, - PawnShop, - PokerTables, - Properties, - Rackets, - Raids, - RankedWars, - RankedWarReport, - Stats, - Stocks, - Territory, - TerritoryWars, - Timestamp, - Lookup, - CityShops, - ItemDetails, - TerritoryNames, - TerritoryWarReport, - RaidReport, - #[serde(other)] - Unknown, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum MarketSelection { - Bazaar, - ItemMarket, - PointsMarket, - Timestamp, - Lookup, - #[serde(other)] - Unknown, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum PropertySelection { - Property, - Timestamp, - Lookup, - #[serde(other)] - Unknown, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Selections { - pub user: HashSet, - pub faction: HashSet, - pub company: HashSet, - pub torn: HashSet, - pub market: HashSet, - pub property: HashSet, - pub key: HashSet, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Info { - pub access_level: i16, - pub access_type: AccessType, - pub selections: Selections, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{async_test, setup, Client, ClientTrait}; - - #[async_test] - async fn key() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .key(|b| b.selections([Selection::Info])) - .await - .unwrap(); - - response.info().unwrap(); - } -} diff --git a/torn-api/src/lib.rs b/torn-api/src/lib.rs index 28b4a43..f9aa757 100644 --- a/torn-api/src/lib.rs +++ b/torn-api/src/lib.rs @@ -1,460 +1,150 @@ -#![warn(clippy::all, clippy::perf, clippy::style, clippy::suspicious)] - -pub mod into_owned; -pub mod local; -pub mod send; - -#[cfg(feature = "user")] -pub mod user; - -#[cfg(feature = "faction")] -pub mod faction; - -#[cfg(feature = "market")] -pub mod market; - -#[cfg(feature = "torn")] -pub mod torn; - -#[cfg(feature = "key")] -pub mod key; - -#[cfg(feature = "awc")] -pub mod awc; - -#[cfg(feature = "reqwest")] -pub mod reqwest; - -#[cfg(feature = "__common")] -pub mod common; - -mod de_util; - -use std::fmt::Write; - -use chrono::{DateTime, Utc}; -use serde::{de::Error as DeError, Deserialize}; use thiserror::Error; -pub use into_owned::IntoOwned; +pub mod executor; +pub mod models; +pub mod parameters; +pub mod request; +pub mod scopes; -pub struct ApiResponse { - pub value: serde_json::Value, +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum ApiError { + #[error("Unhandled error, should not occur")] + Unknown, + #[error("Private key is empty in current request")] + KeyIsEmpty, + #[error("Private key is wrong/incorrect format")] + IncorrectKey, + #[error("Requesting an incorrect basic type")] + WrongType, + #[error("Requesting incorect selection fields")] + WrongFields, + #[error( + "Requests are blocked for a small period of time because of too many requests per user" + )] + TooManyRequest, + #[error("Wrong ID value")] + IncorrectId, + #[error("A requested selection is private")] + IncorrectIdEntityRelation, + #[error("Current IP is banned for a small period of time because of abuse")] + IpBlock, + #[error("Api system is currently disabled")] + ApiDisabled, + #[error("Current key can't be used because owner is in federal jail")] + KeyOwnerInFederalJail, + #[error("You can only change your API key once every 60 seconds")] + KeyChange, + #[error("Error reading key from Database")] + KeyRead, + #[error("The key owner hasn't been online for more than 7 days")] + TemporaryInactivity, + #[error("Too many records have been pulled today by this user from our cloud services")] + DailyReadLimit, + #[error("An error code specifically for testing purposes that has no dedicated meaning")] + TemporaryError, + #[error("A selection is being called of which this key does not have permission to access")] + InsufficientAccessLevel, + #[error("Backend error occurred, please try again")] + Backend, + #[error("API key has been paused by the owner")] + Paused, + #[error("Must be migrated to crimes 2.0")] + NotMigratedCrimes, + #[error("Race not yet finished")] + RaceNotFinished, + #[error("Wrong cat value")] + IncorrectCategory, + #[error("This selection is only available in API v1")] + OnlyInV1, + #[error("This selection is only available in API v2")] + OnlyInV2, + #[error("Closed temporarily")] + ClosedTemporarily, + #[error("Other: {message}")] + Other { code: u16, message: String }, } -#[derive(Error, Debug)] -pub enum ResponseError { - #[error("API: {reason}")] - Api { code: u8, reason: String }, +impl ApiError { + pub fn new(code: u16, message: &str) -> Self { + match code { + 0 => Self::Unknown, + 1 => Self::KeyIsEmpty, + 2 => Self::IncorrectKey, + 3 => Self::WrongType, + 4 => Self::WrongFields, + 5 => Self::TooManyRequest, + 6 => Self::IncorrectId, + 7 => Self::IncorrectIdEntityRelation, + 8 => Self::IpBlock, + 9 => Self::ApiDisabled, + 10 => Self::KeyOwnerInFederalJail, + 11 => Self::KeyChange, + 12 => Self::KeyRead, + 13 => Self::TemporaryInactivity, + 14 => Self::DailyReadLimit, + 15 => Self::TemporaryError, + 16 => Self::InsufficientAccessLevel, + 17 => Self::Backend, + 18 => Self::Paused, + 19 => Self::NotMigratedCrimes, + 20 => Self::RaceNotFinished, + 21 => Self::IncorrectCategory, + 22 => Self::OnlyInV1, + 23 => Self::OnlyInV2, + 24 => Self::ClosedTemporarily, + other => Self::Other { + code: other, + message: message.to_owned(), + }, + } + } - #[error(transparent)] - MalformedResponse(#[from] serde_json::Error), -} - -impl ResponseError { - pub fn api_code(&self) -> Option { + pub fn code(&self) -> u16 { match self { - Self::Api { code, .. } => Some(*code), - _ => None, + Self::Unknown => 0, + Self::KeyIsEmpty => 1, + Self::IncorrectKey => 2, + Self::WrongType => 3, + Self::WrongFields => 4, + Self::TooManyRequest => 5, + Self::IncorrectId => 6, + Self::IncorrectIdEntityRelation => 7, + Self::IpBlock => 8, + Self::ApiDisabled => 9, + Self::KeyOwnerInFederalJail => 10, + Self::KeyChange => 11, + Self::KeyRead => 12, + Self::TemporaryInactivity => 13, + Self::DailyReadLimit => 14, + Self::TemporaryError => 15, + Self::InsufficientAccessLevel => 16, + Self::Backend => 17, + Self::Paused => 18, + Self::NotMigratedCrimes => 19, + Self::RaceNotFinished => 20, + Self::IncorrectCategory => 21, + Self::OnlyInV1 => 22, + Self::OnlyInV2 => 23, + Self::ClosedTemporarily => 24, + Self::Other { code, .. } => *code, } } } -impl ApiResponse { - pub fn from_value(mut value: serde_json::Value) -> Result { - #[derive(serde::Deserialize)] - struct ApiErrorDto { - code: u8, - #[serde(rename = "error")] - reason: String, - } - match value.get_mut("error") { - Some(error) => { - let dto: ApiErrorDto = serde_json::from_value(error.take())?; - Err(ResponseError::Api { - code: dto.code, - reason: dto.reason, - }) - } - None => Ok(Self { value }), - } - } - - #[allow(dead_code)] - fn decode<'de, D>(&'de self) -> serde_json::Result - where - D: Deserialize<'de>, - { - D::deserialize(&self.value) - } - - #[allow(dead_code)] - fn decode_field<'de, D>(&'de self, field: &'static str) -> serde_json::Result - where - D: Deserialize<'de>, - { - self.value - .get(field) - .ok_or_else(|| serde_json::Error::missing_field(field)) - .and_then(D::deserialize) - } - - #[allow(dead_code)] - fn decode_field_with<'de, V, F>(&'de self, field: &'static str, fun: F) -> serde_json::Result - where - F: FnOnce(&'de serde_json::Value) -> serde_json::Result, - { - self.value - .get(field) - .ok_or_else(|| serde_json::Error::missing_field(field)) - .and_then(fun) - } +#[derive(Debug, Error, PartialEq, Eq)] +pub enum ParameterError { + #[error("value `{value}` is out of range for parameter {name}")] + OutOfRange { name: &'static str, value: i32 }, } -pub trait ApiSelectionResponse: Send + Sync + From + 'static { - fn into_inner(self) -> ApiResponse; -} - -pub trait ApiSelection: Send + Sync + 'static { - type Response: ApiSelectionResponse; - - fn raw_value(self) -> &'static str; - - fn category() -> &'static str; -} - -pub struct DirectExecutor { - key: String, - _marker: std::marker::PhantomData, -} - -impl DirectExecutor { - fn new(key: String) -> Self { - Self { - key, - _marker: Default::default(), - } - } -} - -#[derive(Error, Debug)] -pub enum ApiClientError -where - C: std::error::Error, -{ - #[error(transparent)] - Client(C), - - #[error(transparent)] - Response(#[from] ResponseError), -} - -impl ApiClientError -where - C: std::error::Error, -{ - pub fn api_code(&self) -> Option { - match self { - Self::Response(err) => err.api_code(), - _ => None, - } - } -} - -#[derive(Debug)] -pub struct ApiRequest -where - A: ApiSelection, -{ - pub selections: Vec<&'static str>, - pub query_items: Vec<(&'static str, String)>, - pub comment: Option, - phantom: std::marker::PhantomData, -} - -impl std::default::Default for ApiRequest -where - A: ApiSelection, -{ - fn default() -> Self { - Self { - selections: Vec::default(), - query_items: Vec::default(), - comment: None, - phantom: Default::default(), - } - } -} - -impl ApiRequest -where - A: ApiSelection, -{ - fn add_query_item(&mut self, name: &'static str, value: impl ToString) { - if let Some((_, old)) = self.query_items.iter_mut().find(|(n, _)| *n == name) { - *old = value.to_string(); - } else { - self.query_items.push((name, value.to_string())); - } - } - - pub fn url(&self, key: &str, id: Option<&str>) -> String { - let mut url = format!("https://api.torn.com/{}/", A::category()); - - if let Some(id) = id { - write!(url, "{}", id).unwrap(); - } - - write!(url, "?selections={}&key={}", self.selections.join(","), key).unwrap(); - - for (name, value) in &self.query_items { - write!(url, "&{name}={value}").unwrap(); - } - - if let Some(comment) = &self.comment { - write!(url, "&comment={}", comment).unwrap(); - } - - url - } -} - -pub struct ApiRequestBuilder -where - A: ApiSelection, -{ - pub request: ApiRequest, - pub id: Option, -} - -impl Default for ApiRequestBuilder -where - A: ApiSelection, -{ - fn default() -> Self { - Self { - request: Default::default(), - id: None, - } - } -} - -impl ApiRequestBuilder -where - A: ApiSelection, -{ - #[must_use] - pub fn selections(mut self, selections: impl IntoIterator) -> Self { - self.request.selections.append( - &mut selections - .into_iter() - .map(ApiSelection::raw_value) - .collect(), - ); - self - } - - #[must_use] - pub fn from(mut self, from: DateTime) -> Self { - self.request.add_query_item("from", from.timestamp()); - self - } - - #[must_use] - pub fn from_timestamp(mut self, from: i64) -> Self { - self.request.add_query_item("from", from); - self - } - - #[must_use] - pub fn to(mut self, to: DateTime) -> Self { - self.request.add_query_item("to", to.timestamp()); - self - } - - #[must_use] - pub fn to_timestamp(mut self, to: i64) -> Self { - self.request.add_query_item("to", to); - self - } - - #[must_use] - pub fn stats_timestamp(mut self, ts: i64) -> Self { - self.request.add_query_item("timestamp", ts); - self - } - - #[must_use] - pub fn stats_datetime(mut self, dt: DateTime) -> Self { - self.request.add_query_item("timestamp", dt.timestamp()); - self - } - - #[must_use] - pub fn comment(mut self, comment: String) -> Self { - self.request.comment = Some(comment); - self - } - - #[must_use] - pub fn id(mut self, id: I) -> Self - where - I: ToString, - { - self.id = Some(id.to_string()); - self - } -} - -#[cfg(test)] -#[allow(unused)] -pub(crate) mod tests { - use std::sync::Once; - - #[cfg(all(not(feature = "reqwest"), feature = "awc"))] - pub use ::awc::Client; - #[cfg(feature = "reqwest")] - pub use ::reqwest::Client; - - #[cfg(all(not(feature = "reqwest"), feature = "awc"))] - pub use crate::local::ApiClient as ClientTrait; - #[cfg(feature = "reqwest")] - pub use crate::send::ApiClient as ClientTrait; - - #[cfg(all(not(feature = "reqwest"), feature = "awc"))] - pub use actix_rt::test as async_test; - #[cfg(feature = "reqwest")] - pub use tokio::test as async_test; - - use super::*; - - static INIT: Once = Once::new(); - - pub(crate) fn setup() -> String { - INIT.call_once(|| { - dotenv::dotenv().ok(); - }); - std::env::var("APIKEY").expect("api key") - } - - #[cfg(feature = "user")] - #[test] - fn selection_raw_value() { - assert_eq!(user::Selection::Basic.raw_value(), "basic"); - } - - #[cfg(all(feature = "reqwest", feature = "user"))] - #[tokio::test] - async fn reqwest() { - let key = setup(); - - Client::default().torn_api(key).user(|b| b).await.unwrap(); - } - - #[cfg(all(feature = "awc", feature = "user"))] - #[actix_rt::test] - async fn awc() { - let key = setup(); - - Client::default().torn_api(key).user(|b| b).await.unwrap(); - } - - #[test] - fn url_builder_from_dt() { - let url = ApiRequestBuilder::::default() - .from(DateTime::default()) - .request - .url("", None); - - assert_eq!("https://api.torn.com/user/?selections=&key=&from=0", url); - } - - #[test] - fn url_builder_from_ts() { - let url = ApiRequestBuilder::::default() - .from_timestamp(12345) - .request - .url("", None); - - assert_eq!( - "https://api.torn.com/user/?selections=&key=&from=12345", - url - ); - } - - #[test] - fn url_builder_to_dt() { - let url = ApiRequestBuilder::::default() - .to(DateTime::default()) - .request - .url("", None); - - assert_eq!("https://api.torn.com/user/?selections=&key=&to=0", url); - } - - #[test] - fn url_builder_to_ts() { - let url = ApiRequestBuilder::::default() - .to_timestamp(12345) - .request - .url("", None); - - assert_eq!("https://api.torn.com/user/?selections=&key=&to=12345", url); - } - - #[test] - fn url_builder_timestamp_dt() { - let url = ApiRequestBuilder::::default() - .stats_datetime(DateTime::default()) - .request - .url("", None); - - assert_eq!( - "https://api.torn.com/user/?selections=&key=×tamp=0", - url - ); - } - - #[test] - fn url_builder_timestamp_ts() { - let url = ApiRequestBuilder::::default() - .stats_timestamp(12345) - .request - .url("", None); - - assert_eq!( - "https://api.torn.com/user/?selections=&key=×tamp=12345", - url - ); - } - - #[test] - fn url_builder_duplicate() { - let url = ApiRequestBuilder::::default() - .from(DateTime::default()) - .from_timestamp(12345) - .request - .url("", None); - - assert_eq!( - "https://api.torn.com/user/?selections=&key=&from=12345", - url - ); - } - - #[test] - fn url_builder_many_options() { - let url = ApiRequestBuilder::::default() - .from(DateTime::default()) - .to_timestamp(60) - .stats_timestamp(12345) - .selections([user::Selection::PersonalStats]) - .request - .url("KEY", Some("1")); - - assert_eq!( - "https://api.torn.com/user/1?selections=personalstats&key=KEY&from=0&to=60×tamp=12345", - url - ); - } +#[derive(Debug, Error)] +pub enum Error { + #[error("Parameter error: {0}")] + Parameter(#[from] ParameterError), + #[error("Network error: {0}")] + Network(#[from] reqwest::Error), + #[error("Parsing error: {0}")] + Parsing(#[from] serde_json::Error), + #[error("Api error: {0}")] + Api(#[from] ApiError), } diff --git a/torn-api/src/local.rs b/torn-api/src/local.rs deleted file mode 100644 index 772e8fc..0000000 --- a/torn-api/src/local.rs +++ /dev/null @@ -1,282 +0,0 @@ -use std::collections::HashMap; - -use async_trait::async_trait; - -use crate::{ApiClientError, ApiRequest, ApiResponse, ApiSelection, DirectExecutor}; - -pub struct ApiProvider<'a, C, E> -where - C: ApiClient, - E: RequestExecutor, -{ - #[allow(dead_code)] - client: &'a C, - #[allow(dead_code)] - executor: E, -} - -impl<'a, C, E> ApiProvider<'a, C, E> -where - C: ApiClient, - E: RequestExecutor, -{ - pub fn new(client: &'a C, executor: E) -> ApiProvider<'a, C, E> { - Self { client, executor } - } - - #[cfg(feature = "user")] - pub async fn user(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } - - #[cfg(feature = "user")] - pub async fn users( - &self, - ids: L, - build: F, - ) -> HashMap> - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - I: ToString + std::hash::Hash + std::cmp::Eq, - L: IntoIterator, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute_many(self.client, builder.request, Vec::from_iter(ids)) - .await - } - - #[cfg(feature = "faction")] - pub async fn faction(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } - - #[cfg(feature = "faction")] - pub async fn factions( - &self, - ids: L, - build: F, - ) -> HashMap> - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - I: ToString + std::hash::Hash + std::cmp::Eq, - L: IntoIterator, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute_many(self.client, builder.request, Vec::from_iter(ids)) - .await - } - - #[cfg(feature = "market")] - pub async fn market(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } - - #[cfg(feature = "market")] - pub async fn markets( - &self, - ids: L, - build: F, - ) -> HashMap> - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - I: ToString + std::hash::Hash + std::cmp::Eq, - L: IntoIterator, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute_many(self.client, builder.request, Vec::from_iter(ids)) - .await - } - - #[cfg(feature = "torn")] - pub async fn torn(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } - - #[cfg(feature = "torn")] - pub async fn torns( - &self, - ids: L, - build: F, - ) -> HashMap> - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - I: ToString + std::hash::Hash + std::cmp::Eq, - L: IntoIterator, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute_many(self.client, builder.request, Vec::from_iter(ids)) - .await - } - - #[cfg(feature = "key")] - pub async fn key(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } -} - -#[async_trait(?Send)] -pub trait RequestExecutor -where - C: ApiClient, -{ - type Error: std::error::Error; - - async fn execute( - &self, - client: &C, - request: ApiRequest, - id: Option, - ) -> Result - where - A: ApiSelection; - - async fn execute_many( - &self, - client: &C, - request: ApiRequest, - ids: Vec, - ) -> HashMap> - where - A: ApiSelection, - I: ToString + std::hash::Hash + std::cmp::Eq; -} - -#[async_trait(?Send)] -impl RequestExecutor for DirectExecutor -where - C: ApiClient, -{ - type Error = ApiClientError; - - async fn execute( - &self, - client: &C, - request: ApiRequest, - id: Option, - ) -> Result - where - A: ApiSelection, - { - let url = request.url(&self.key, id.as_deref()); - - let value = client.request(url).await.map_err(ApiClientError::Client)?; - - Ok(ApiResponse::from_value(value)?.into()) - } - - async fn execute_many( - &self, - client: &C, - request: ApiRequest, - ids: Vec, - ) -> HashMap> - where - A: ApiSelection, - I: ToString + std::hash::Hash + std::cmp::Eq, - { - let request_ref = &request; - let tuples = futures::future::join_all(ids.into_iter().map(|i| async move { - let id_string = i.to_string(); - let url = request_ref.url(&self.key, Some(&id_string)); - - let value = client.request(url).await.map_err(ApiClientError::Client); - - ( - i, - value.and_then(|v| { - ApiResponse::from_value(v) - .map(Into::into) - .map_err(Into::into) - }), - ) - })) - .await; - - HashMap::from_iter(tuples) - } -} - -#[async_trait(?Send)] -pub trait ApiClient { - type Error: std::error::Error; - - async fn request(&self, url: String) -> Result; - - fn torn_api(&self, key: S) -> ApiProvider> - where - Self: Sized, - S: ToString, - { - ApiProvider::new(self, DirectExecutor::new(key.to_string())) - } -} diff --git a/torn-api/src/market.rs b/torn-api/src/market.rs deleted file mode 100644 index 9cbcc19..0000000 --- a/torn-api/src/market.rs +++ /dev/null @@ -1,34 +0,0 @@ -use serde::Deserialize; -use torn_api_macros::ApiCategory; - -#[derive(Debug, Clone, Copy, ApiCategory)] -#[api(category = "market")] -pub enum MarketSelection { - #[api(type = "Vec", field = "bazaar")] - Bazaar, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct BazaarItem { - pub cost: u64, - pub quantity: u32, -} - -#[cfg(test)] -mod test { - use super::*; - use crate::tests::{async_test, setup, Client, ClientTrait}; - - #[async_test] - async fn market_bazaar() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .market(|b| b.id(1).selections([MarketSelection::Bazaar])) - .await - .unwrap(); - - _ = response.bazaar().unwrap(); - } -} diff --git a/torn-api/src/models.rs b/torn-api/src/models.rs new file mode 100644 index 0000000..c77fafb --- /dev/null +++ b/torn-api/src/models.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/models.rs")); diff --git a/torn-api/src/parameters.rs b/torn-api/src/parameters.rs new file mode 100644 index 0000000..dc2378f --- /dev/null +++ b/torn-api/src/parameters.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/parameters.rs")); diff --git a/torn-api/src/request/mod.rs b/torn-api/src/request/mod.rs new file mode 100644 index 0000000..6334bbb --- /dev/null +++ b/torn-api/src/request/mod.rs @@ -0,0 +1,104 @@ +use bon::Builder; +use bytes::Bytes; +use http::StatusCode; + +use crate::{ + executor::Executor, + models::{FactionChainsResponse, FactionId}, +}; + +pub mod models; + +#[derive(Default)] +pub struct ApiRequest { + pub disriminant: D, + pub path: String, + pub parameters: Vec<(&'static str, String)>, +} + +impl ApiRequest { + pub fn url(&self) -> String { + let mut url = format!("https://api.torn.com/v2{}?", self.path); + + for (name, value) in &self.parameters { + url.push_str(&format!("{name}={value}")); + } + + url + } +} + +pub struct ApiResponse { + pub discriminant: D, + pub body: Option, + pub status: StatusCode, +} + +pub trait IntoRequest: Send { + type Discriminant: Send; + type Response: for<'de> serde::Deserialize<'de> + Send; + fn into_request(self) -> ApiRequest; +} + +pub struct FactionScope<'e, E>(&'e E) +where + E: Executor; + +impl FactionScope<'_, E> +where + E: Executor, +{ + pub async fn chains_for_id( + &self, + id: FactionId, + builder: impl FnOnce( + FactionChainsRequestBuilder, + ) -> FactionChainsRequestBuilder, + ) -> Result + where + S: faction_chains_request_builder::IsComplete, + { + let r = builder(FactionChainsRequest::with_id(id)).build(); + + self.0.fetch(r).await + } +} + +#[derive(Builder)] +#[builder(start_fn = with_id)] +pub struct FactionChainsRequest { + #[builder(start_fn)] + pub id: FactionId, + pub limit: Option, +} + +impl IntoRequest for FactionChainsRequest { + type Discriminant = FactionId; + type Response = FactionChainsResponse; + fn into_request(self) -> ApiRequest { + ApiRequest { + disriminant: self.id, + path: format!("/faction/{}/chains", self.id), + parameters: self + .limit + .into_iter() + .map(|l| ("limit", l.to_string())) + .collect(), + } + } +} + +#[cfg(test)] +mod test { + use crate::executor::ReqwestClient; + + use super::*; + + #[tokio::test] + async fn test_request() { + let client = ReqwestClient::new("nAYRXaoqzBAGalWt"); + + let r = models::TornItemsForIdsRequest::builder("1".to_owned()).build(); + client.fetch(r).await.unwrap(); + } +} diff --git a/torn-api/src/request/models.rs b/torn-api/src/request/models.rs new file mode 100644 index 0000000..a65b582 --- /dev/null +++ b/torn-api/src/request/models.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/requests.rs")); diff --git a/torn-api/src/reqwest.rs b/torn-api/src/reqwest.rs deleted file mode 100644 index 99a64c3..0000000 --- a/torn-api/src/reqwest.rs +++ /dev/null @@ -1,12 +0,0 @@ -use async_trait::async_trait; - -use crate::send::ApiClient; - -#[async_trait] -impl ApiClient for reqwest::Client { - type Error = reqwest::Error; - - async fn request(&self, url: String) -> Result { - self.get(url).send().await?.json().await - } -} diff --git a/torn-api/src/scopes.rs b/torn-api/src/scopes.rs new file mode 100644 index 0000000..85b6dfe --- /dev/null +++ b/torn-api/src/scopes.rs @@ -0,0 +1,686 @@ +include!(concat!(env!("OUT_DIR"), "/scopes.rs")); + +#[cfg(test)] +pub(super) mod test { + use std::{collections::VecDeque, sync::OnceLock, time::Duration}; + + use tokio::sync::mpsc; + + use crate::executor::ReqwestClient; + + use super::*; + + static TICKETS: OnceLock>> = OnceLock::new(); + + pub(crate) async fn test_client() -> ReqwestClient { + let (tx, mut rx) = mpsc::channel(1); + + let ticket_tx = TICKETS + .get_or_init(|| { + let (tx, mut rx) = mpsc::channel(1); + std::thread::spawn(move || { + let mut queue = VecDeque::>::new(); + + let rt = tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .unwrap(); + + rt.block_on(async move { + loop { + tokio::select! { + recv_result = rx.recv() => { + match recv_result { + Some(ticket) => queue.push_back(ticket), + None => break, + } + } + _ = tokio::time::sleep(Duration::from_secs(1)) => { + if let Some(next) = queue.pop_front() { + next.send(ReqwestClient::new(&std::env::var("API_KEY").unwrap())).await.unwrap() + } + } + } + } + }); + }); + + tx + }) + .clone(); + + ticket_tx.send(tx).await.unwrap(); + + rx.recv().await.unwrap() + } + + #[tokio::test] + async fn faction_applications() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.applications(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_attacks() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.attacks(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_attacksfull() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.attacksfull(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_balance() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.balance(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_basic() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.basic(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_basic_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.basic_for_id(19.into(), |b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_chain() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.chain(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_chain_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.chain_for_id(19.into(), |b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_chains() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.chains(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn factions_chains_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.chains_for_id(19.into(), |b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_chain_report() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.chainreport(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_chain_report_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope + .chainreport_for_chain_id(47004769.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn faction_contributors() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope + .contributors(|b| b.stat(crate::models::FactionStatEnum::Revives)) + .await + .unwrap(); + } + + #[tokio::test] + async fn faction_crimes() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.crimes(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_crime_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope + .crime_for_id(468347.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn faction_hof() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.hof(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_hof_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.hof_for_id(19.into(), |b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_members() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.members(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_members_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope + .members_for_id(19.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn faction_news() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope + .news(|b| b.cat(crate::models::FactionNewsCategory::Attack)) + .await + .unwrap(); + } + + #[tokio::test] + async fn faction_ranked_wars() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.rankedwars(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_ranked_war_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope + .rankedwars_for_id(19.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn faction_ranked_war_report_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope + .rankedwarreport_for_id(24424.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn faction_revives() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.revives(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_revives_full() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.revives_full(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_stats() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.stats(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_upgrades() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.upgrades(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_wars() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.wars(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn faction_wars_for_id() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.wars_for_id(19.into(), |b| b).await.unwrap(); + } + + #[tokio::test] + async fn lookup() { + let client = test_client().await; + + let faction_scope = FactionScope(&client); + + faction_scope.lookup(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn forum_categories() { + let client = test_client().await; + + let forum_scope = ForumScope(&client); + + forum_scope.categories(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn forum_posts_for_thread_id() { + let client = test_client().await; + + let forum_scope = ForumScope(&client); + + forum_scope + .posts_for_thread_id(16129703.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn forum_thread_for_thread_id() { + let client = test_client().await; + + let forum_scope = ForumScope(&client); + + forum_scope + .thread_for_thread_id(16129703.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn forum_threads() { + let client = test_client().await; + + let forum_scope = ForumScope(&client); + + forum_scope.threads(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn forum_threads_for_category_ids() { + let client = test_client().await; + + let forum_scope = ForumScope(&client); + + forum_scope + .threads_for_category_ids("2".to_owned(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn forum_lookup() { + let client = test_client().await; + + let forum_scope = ForumScope(&client); + + forum_scope.lookup(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn forum_timestamp() { + let client = test_client().await; + + let forum_scope = ForumScope(&client); + + forum_scope.timestamp(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn market_itemmarket_for_id() { + let client = test_client().await; + + let market_scope = MarketScope(&client); + + market_scope + .itemmarket_for_id(1.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn market_lookup() { + let client = test_client().await; + + let market_scope = MarketScope(&client); + + market_scope.lookup(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn market_timestamp() { + let client = test_client().await; + + let market_scope = MarketScope(&client); + + market_scope.timestamp(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn racing_cars() { + let client = test_client().await; + + let racing_scope = RacingScope(&client); + + racing_scope.cars(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn racing_carupgrades() { + let client = test_client().await; + + let racing_scope = RacingScope(&client); + + racing_scope.carupgrades(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn racing_races() { + let client = test_client().await; + + let racing_scope = RacingScope(&client); + + racing_scope.races(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn racing_race_for_race_id() { + let client = test_client().await; + + let racing_scope = RacingScope(&client); + + racing_scope + .race_for_race_id(14650821.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn racing_tracks() { + let client = test_client().await; + + let racing_scope = RacingScope(&client); + + racing_scope.tracks(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn racing_lookup() { + let client = test_client().await; + + let racing_scope = RacingScope(&client); + + racing_scope.lookup(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn racing_timestamp() { + let client = test_client().await; + + let racing_scope = RacingScope(&client); + + racing_scope.timestamp(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_attacklog() { + let client = test_client().await; + + let racing_scope = TornScope(&client); + + racing_scope + .attacklog(|b| b.log("ec987a60a22155cbfb7c1625cbb2092f".to_owned())) + .await + .unwrap(); + } + + #[tokio::test] + async fn torn_bounties() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.bounties(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_calendar() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.calendar(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_crimes() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.crimes(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_factionhof() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope + .factionhof(|b| b.cat(crate::models::TornFactionHofCategory::Rank)) + .await + .unwrap(); + } + + #[tokio::test] + async fn torn_factiontree() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.factiontree(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_hof() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope + .hof(|b| b.cat(crate::models::TornHofCategory::Offences)) + .await + .unwrap(); + } + + #[tokio::test] + async fn torn_itemammo() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.itemammo(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_itemmods() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.itemmods(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_items() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.items(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_items_for_ids() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope + .items_for_ids("1".to_owned(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn torn_logcategories() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.logcategories(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_logtypes() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.logtypes(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_logtypes_for_log_category_id() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope + .logtypes_for_log_category_id(23.into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn torn_subrcimes_for_crime_id() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope + .subcrimes_for_crime_id("3".into(), |b| b) + .await + .unwrap(); + } + + #[tokio::test] + async fn torn_lookup() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.lookup(|b| b).await.unwrap(); + } + + #[tokio::test] + async fn torn_timestamp() { + let client = test_client().await; + + let torn_scope = TornScope(&client); + + torn_scope.timestamp(|b| b).await.unwrap(); + } +} diff --git a/torn-api/src/send.rs b/torn-api/src/send.rs deleted file mode 100644 index ca50eb1..0000000 --- a/torn-api/src/send.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::collections::HashMap; - -use async_trait::async_trait; - -use crate::{ApiClientError, ApiRequest, ApiResponse, ApiSelection, DirectExecutor}; - -pub struct ApiProvider<'a, C, E> -where - C: ApiClient, - E: RequestExecutor, -{ - client: &'a C, - executor: E, -} - -impl<'a, C, E> ApiProvider<'a, C, E> -where - C: ApiClient, - E: RequestExecutor, -{ - pub fn new(client: &'a C, executor: E) -> ApiProvider<'a, C, E> { - Self { client, executor } - } - - #[cfg(feature = "user")] - pub async fn user(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } - - #[cfg(feature = "user")] - pub async fn users( - &self, - ids: L, - build: F, - ) -> HashMap> - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - I: ToString + std::hash::Hash + std::cmp::Eq + Send + Sync, - L: IntoIterator, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute_many(self.client, builder.request, Vec::from_iter(ids)) - .await - } - - #[cfg(feature = "faction")] - pub async fn faction(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } - - #[cfg(feature = "faction")] - pub async fn factions( - &self, - ids: L, - build: F, - ) -> HashMap> - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - I: ToString + std::hash::Hash + std::cmp::Eq + Send + Sync, - L: IntoIterator, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute_many(self.client, builder.request, Vec::from_iter(ids)) - .await - } - - #[cfg(feature = "market")] - pub async fn market(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } - - #[cfg(feature = "market")] - pub async fn markets( - &self, - ids: L, - build: F, - ) -> HashMap> - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - I: ToString + std::hash::Hash + std::cmp::Eq + Send + Sync, - L: IntoIterator, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute_many(self.client, builder.request, Vec::from_iter(ids)) - .await - } - - #[cfg(feature = "torn")] - pub async fn torn(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } - - #[cfg(feature = "torn")] - pub async fn torns( - &self, - ids: L, - build: F, - ) -> HashMap> - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - I: ToString + std::hash::Hash + std::cmp::Eq + Send + Sync, - L: IntoIterator, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute_many(self.client, builder.request, Vec::from_iter(ids)) - .await - } - - #[cfg(feature = "key")] - pub async fn key(&self, build: F) -> Result - where - F: FnOnce( - crate::ApiRequestBuilder, - ) -> crate::ApiRequestBuilder, - { - let mut builder = crate::ApiRequestBuilder::default(); - builder = build(builder); - - self.executor - .execute(self.client, builder.request, builder.id) - .await - } -} - -#[async_trait] -pub trait RequestExecutor -where - C: ApiClient, -{ - type Error: std::error::Error + Send + Sync; - - async fn execute( - &self, - client: &C, - request: ApiRequest, - id: Option, - ) -> Result - where - A: ApiSelection; - - async fn execute_many( - &self, - client: &C, - request: ApiRequest, - ids: Vec, - ) -> HashMap> - where - A: ApiSelection, - I: ToString + std::hash::Hash + std::cmp::Eq + Send + Sync; -} - -#[async_trait] -impl RequestExecutor for DirectExecutor -where - C: ApiClient, -{ - type Error = ApiClientError; - - async fn execute( - &self, - client: &C, - request: ApiRequest, - id: Option, - ) -> Result - where - A: ApiSelection, - { - let url = request.url(&self.key, id.as_deref()); - - let value = client.request(url).await.map_err(ApiClientError::Client)?; - - Ok(ApiResponse::from_value(value)?.into()) - } - - async fn execute_many( - &self, - client: &C, - request: ApiRequest, - ids: Vec, - ) -> HashMap> - where - A: ApiSelection, - I: ToString + std::hash::Hash + std::cmp::Eq + Send + Sync, - { - let request_ref = &request; - let tuples = futures::future::join_all(ids.into_iter().map(|i| async move { - let id_string = i.to_string(); - let url = request_ref.url(&self.key, Some(&id_string)); - - let value = client.request(url).await.map_err(ApiClientError::Client); - - ( - i, - value.and_then(|v| { - ApiResponse::from_value(v) - .map(Into::into) - .map_err(Into::into) - }), - ) - })) - .await; - - HashMap::from_iter(tuples) - } -} - -#[async_trait] -pub trait ApiClient: Send + Sync { - type Error: std::error::Error + Sync + Send; - - async fn request(&self, url: String) -> Result; - - fn torn_api(&self, key: S) -> ApiProvider> - where - Self: Sized, - S: ToString, - { - ApiProvider::new(self, DirectExecutor::new(key.to_string())) - } -} diff --git a/torn-api/src/torn.rs b/torn-api/src/torn.rs deleted file mode 100644 index 0d2ee4e..0000000 --- a/torn-api/src/torn.rs +++ /dev/null @@ -1,417 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use chrono::{DateTime, Utc}; -use serde::{ - de::{self, MapAccess, Visitor}, - Deserialize, Deserializer, -}; - -use torn_api_macros::ApiCategory; - -use crate::{de_util, user}; - -#[derive(Debug, Clone, Copy, ApiCategory)] -#[api(category = "torn")] -#[non_exhaustive] -pub enum TornSelection { - #[api( - field = "competition", - with = "decode_competition", - type = "Option" - )] - Competition, - - #[api( - type = "HashMap", - with = "decode_territory_wars", - field = "territorywars" - )] - TerritoryWars, - - #[api(type = "HashMap", field = "rackets")] - Rackets, - - #[api( - type = "HashMap", - with = "decode_territory", - field = "territory" - )] - Territory, - - #[api(type = "TerritoryWarReport", field = "territorywarreport")] - TerritoryWarReport, - - #[api(type = "BTreeMap", field = "items")] - Items, -} - -pub type Selection = TornSelection; - -#[derive(Debug, Clone, Deserialize)] -pub struct EliminationLeaderboard { - pub position: i16, - pub team: user::EliminationTeam, - pub score: i16, - pub lives: i16, - pub participants: Option, - pub wins: Option, - pub losses: Option, -} - -#[derive(Debug, Clone)] -pub enum Competition { - Elimination { teams: Vec }, - Unkown(String), -} - -fn decode_territory_wars<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let map: Option<_> = serde::Deserialize::deserialize(deserializer)?; - - Ok(map.unwrap_or_default()) -} - -fn decode_competition<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - struct CompetitionVisitor; - - impl<'de> Visitor<'de> for CompetitionVisitor { - type Value = Option; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct Competition") - } - - fn visit_some(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_map(self) - } - - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut name = None; - let mut teams = None; - - while let Some(key) = map.next_key()? { - match key { - "name" => { - name = Some(map.next_value()?); - } - "teams" => { - teams = Some(map.next_value()?); - } - _ => (), - }; - } - - let name = name.ok_or_else(|| de::Error::missing_field("name"))?; - - match name { - "Elimination" => Ok(Some(Competition::Elimination { - teams: teams.ok_or_else(|| de::Error::missing_field("teams"))?, - })), - "" => Ok(None), - v => Ok(Some(Competition::Unkown(v.to_owned()))), - } - } - } - - deserializer.deserialize_option(CompetitionVisitor) -} - -#[derive(Debug, Clone, Deserialize)] -pub struct TerritoryWar { - pub territory_war_id: i32, - pub assaulting_faction: i32, - pub defending_faction: i32, - - #[serde(with = "chrono::serde::ts_seconds")] - pub started: DateTime, - #[serde(with = "chrono::serde::ts_seconds")] - pub ends: DateTime, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Racket { - pub name: String, - pub level: i16, - pub reward: String, - - #[serde(with = "chrono::serde::ts_seconds")] - pub created: DateTime, - #[serde(with = "chrono::serde::ts_seconds")] - pub changed: DateTime, - - #[serde(rename = "faction")] - pub faction_id: Option, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Territory { - pub sector: i16, - pub size: i16, - pub slots: i16, - pub daily_respect: i16, - pub faction: i32, - - pub neighbors: Vec, - pub war: Option, - pub racket: Option, -} - -fn decode_territory<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - Ok(Option::deserialize(deserializer)?.unwrap_or_default()) -} - -#[derive(Clone, Debug, Deserialize)] -pub struct TerritoryWarReportTerritory { - pub name: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum TerritoryWarOutcome { - EndWithPeaceTreaty, - EndWithDestroyDefense, - FailAssault, - SuccessAssault, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct TerritoryWarReportWar { - #[serde(with = "chrono::serde::ts_seconds")] - pub start: DateTime, - #[serde(with = "chrono::serde::ts_seconds")] - pub end: DateTime, - - pub result: TerritoryWarOutcome, -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Hash)] -#[serde(rename_all = "snake_case")] -pub enum TerritoryWarReportRole { - Aggressor, - Defender, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct TerritoryWarReportFaction { - pub name: String, - pub score: i32, - pub joins: i32, - pub clears: i32, - #[serde(rename = "type")] - pub role: TerritoryWarReportRole, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct TerritoryWarReport { - pub territory: TerritoryWarReportTerritory, - pub war: TerritoryWarReportWar, - pub factions: HashMap, -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum ItemType { - Primary, - Secondary, - Melee, - Temporary, - Defensive, - Collectible, - Medical, - Drug, - Booster, - #[serde(rename = "Energy Drink")] - EnergyDrink, - Alcohol, - Book, - Candy, - Car, - Clothing, - Electronic, - Enhancer, - Flower, - Jewelry, - Other, - Special, - #[serde(rename = "Supply Pack")] - SupplyPack, - Virus, -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Hash)] -#[non_exhaustive] -//Missing hand to hand because it is not possible as a weapon -pub enum WeaponType { - Slashing, - Rifle, - SMG, - Piercing, - Clubbing, - Pistol, - #[serde(rename = "Machine gun")] - MachineGun, - Mechanical, - Temporary, - Heavy, - Shotgun, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Item<'a> { - pub name: String, - pub description: String, - #[serde(deserialize_with = "de_util::empty_string_is_none")] - pub effect: Option<&'a str>, - #[serde(deserialize_with = "de_util::empty_string_is_none")] - pub requirement: Option<&'a str>, - #[serde(rename = "type")] - pub item_type: ItemType, - pub weapon_type: Option, - #[serde(deserialize_with = "de_util::zero_is_none")] - pub buy_price: Option, - #[serde(deserialize_with = "de_util::zero_is_none")] - pub sell_price: Option, - #[serde(deserialize_with = "de_util::zero_is_none")] - pub market_value: Option, - #[serde(deserialize_with = "de_util::zero_is_none")] - pub circulation: Option, - pub image: String, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{async_test, setup, Client, ClientTrait}; - - #[async_test] - async fn competition() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .torn(|b| { - b.selections([ - TornSelection::Competition, - TornSelection::TerritoryWars, - TornSelection::Rackets, - ]) - }) - .await - .unwrap(); - - response.competition().unwrap(); - response.territory_wars().unwrap(); - response.rackets().unwrap(); - } - - #[async_test] - async fn territory() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .torn(|b| b.selections([Selection::Territory]).id("NSC")) - .await - .unwrap(); - - let territory = response.territory().unwrap(); - assert!(territory.contains_key("NSC")); - } - - #[async_test] - async fn invalid_territory() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .torn(|b| b.selections([Selection::Territory]).id("AAA")) - .await - .unwrap(); - - assert!(response.territory().unwrap().is_empty()); - } - - #[async_test] - async fn territory_war_report() { - let key = setup(); - - let response = Client::default() - .torn_api(&key) - .torn(|b| b.selections([Selection::TerritoryWarReport]).id(37403)) - .await - .unwrap(); - - assert_eq!( - response.territory_war_report().unwrap().war.result, - TerritoryWarOutcome::SuccessAssault - ); - - let response = Client::default() - .torn_api(&key) - .torn(|b| b.selections([Selection::TerritoryWarReport]).id(37502)) - .await - .unwrap(); - - assert_eq!( - response.territory_war_report().unwrap().war.result, - TerritoryWarOutcome::FailAssault - ); - - let response = Client::default() - .torn_api(&key) - .torn(|b| b.selections([Selection::TerritoryWarReport]).id(37860)) - .await - .unwrap(); - - assert_eq!( - response.territory_war_report().unwrap().war.result, - TerritoryWarOutcome::EndWithPeaceTreaty - ); - - let response = Client::default() - .torn_api(&key) - .torn(|b| b.selections([Selection::TerritoryWarReport]).id(23757)) - .await - .unwrap(); - - assert_eq!( - response.territory_war_report().unwrap().war.result, - TerritoryWarOutcome::EndWithDestroyDefense - ); - } - - #[async_test] - async fn item() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .torn(|b| b.selections([Selection::Items]).id(837)) - .await - .unwrap(); - - let item_list = response.items().unwrap(); - assert!(item_list.contains_key(&837)); - } -} diff --git a/torn-api/src/user.rs b/torn-api/src/user.rs deleted file mode 100644 index 6c697fd..0000000 --- a/torn-api/src/user.rs +++ /dev/null @@ -1,828 +0,0 @@ -use serde::{ - de::{self, MapAccess, Visitor}, - Deserialize, Deserializer, -}; -use std::{ - collections::{BTreeMap, HashMap}, - iter::zip, -}; - -use torn_api_macros::{ApiCategory, IntoOwned}; - -use crate::de_util; - -pub use crate::common::{Attack, AttackFull, LastAction, Status}; - -#[derive(Debug, Clone, Copy, ApiCategory)] -#[api(category = "user")] -#[non_exhaustive] -pub enum UserSelection { - #[api(type = "Basic", flatten)] - Basic, - #[api(type = "Profile", flatten)] - Profile, - #[api(type = "Discord", field = "discord")] - Discord, - #[api(type = "PersonalStats", field = "personalstats")] - PersonalStats, - #[api(type = "CriminalRecord", field = "criminalrecord")] - Crimes, - #[api(type = "BTreeMap", field = "attacks")] - AttacksFull, - #[api(type = "BTreeMap", field = "attacks")] - Attacks, - #[api(type = "HashMap", field = "icons")] - Icons, - #[api(type = "Awards", flatten)] - Medals, - #[api(type = "Awards", flatten)] - Honors, -} - -pub type Selection = UserSelection; - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Hash)] -pub enum Gender { - Male, - Female, - Enby, -} - -#[derive(Debug, IntoOwned)] -pub struct Faction<'a> { - pub faction_id: i32, - pub faction_name: &'a str, - pub days_in_faction: i16, - pub position: &'a str, - pub faction_tag: Option<&'a str>, - pub faction_tag_image: Option<&'a str>, -} - -fn deserialize_faction<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - #[serde(rename_all = "snake_case")] - enum Field { - FactionId, - FactionName, - DaysInFaction, - Position, - FactionTag, - FactionTagImage, - } - - struct FactionVisitor; - - impl<'de> Visitor<'de> for FactionVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct Faction") - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut faction_id = None; - let mut faction_name = None; - let mut days_in_faction = None; - let mut position = None; - let mut faction_tag = None; - let mut faction_tag_image = None; - - while let Some(key) = map.next_key()? { - match key { - Field::FactionId => { - faction_id = Some(map.next_value()?); - } - Field::FactionName => { - faction_name = Some(map.next_value()?); - } - Field::DaysInFaction => { - days_in_faction = Some(map.next_value()?); - } - Field::Position => { - position = Some(map.next_value()?); - } - Field::FactionTag => { - faction_tag = map.next_value()?; - } - Field::FactionTagImage => { - faction_tag_image = map.next_value()?; - } - } - } - let faction_id = faction_id.ok_or_else(|| de::Error::missing_field("faction_id"))?; - let faction_name = - faction_name.ok_or_else(|| de::Error::missing_field("faction_name"))?; - let days_in_faction = - days_in_faction.ok_or_else(|| de::Error::missing_field("days_in_faction"))?; - let position = position.ok_or_else(|| de::Error::missing_field("position"))?; - - if faction_id == 0 { - Ok(None) - } else { - Ok(Some(Faction { - faction_id, - faction_name, - days_in_faction, - position, - faction_tag, - faction_tag_image, - })) - } - } - } - - const FIELDS: &[&str] = &[ - "faction_id", - "faction_name", - "days_in_faction", - "position", - "faction_tag", - ]; - deserializer.deserialize_struct("Faction", FIELDS, FactionVisitor) -} - -#[derive(Debug, IntoOwned, Deserialize)] -pub struct Basic<'a> { - pub player_id: i32, - pub name: &'a str, - pub level: i16, - pub gender: Gender, - pub status: Status<'a>, -} - -#[derive(Debug, Clone, IntoOwned, PartialEq, Eq, Deserialize)] -#[into_owned(identity)] -pub struct Discord { - #[serde( - rename = "userID", - deserialize_with = "de_util::empty_string_int_option" - )] - pub user_id: Option, - #[serde(rename = "discordID", deserialize_with = "de_util::string_is_long")] - pub discord_id: Option, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct LifeBar { - pub current: i16, - pub maximum: i16, - pub increment: i16, -} - -#[derive(Debug, Clone, Copy, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum EliminationTeam2022 { - Firestarters, - HardBoiled, - QuackAddicts, - RainMen, - TotallyBoned, - RawringThunder, - DirtyCops, - LaughingStock, - JeanTherapy, - #[serde(rename = "satants-soldiers")] - SatansSoldiers, - WolfPack, - Sleepyheads, -} - -#[derive(Debug, Clone, Copy, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum EliminationTeam { - Backstabbers, - Cheese, - DeathsDoor, - RegularHumanPeople, - FlowerRangers, - ReligiousExtremists, - Hivemind, - CapsLockCrew, -} - -#[derive(Debug, Clone, IntoOwned)] -#[into_owned(identity)] -pub enum Competition { - Elimination { - score: i32, - attacks: i16, - team: EliminationTeam, - }, - DogTags { - score: i32, - position: Option, - }, - Unknown, -} - -fn deserialize_comp<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - enum Field { - Name, - Score, - Team, - Attacks, - TeamName, - Position, - #[serde(other)] - Ignore, - } - - #[derive(Deserialize)] - enum CompetitionName { - Elimination, - #[serde(rename = "Dog Tags")] - DogTags, - #[serde(other)] - Unknown, - } - - struct CompetitionVisitor; - - impl<'de> Visitor<'de> for CompetitionVisitor { - type Value = Option; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct Competition") - } - - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(None) - } - - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(self) - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut team = None; - let mut score = None; - let mut attacks = None; - let mut name = None; - let mut position = None; - - while let Some(key) = map.next_key()? { - match key { - Field::Name => { - name = Some(map.next_value()?); - } - Field::Score => { - score = Some(map.next_value()?); - } - Field::Attacks => { - attacks = Some(map.next_value()?); - } - Field::Position => { - position = Some(map.next_value()?); - } - Field::Team => { - let team_raw: &str = map.next_value()?; - team = if team_raw.is_empty() { - None - } else { - Some(match team_raw { - "backstabbers" => EliminationTeam::Backstabbers, - "cheese" => EliminationTeam::Cheese, - "deaths-door" => EliminationTeam::DeathsDoor, - "regular-human-people" => EliminationTeam::RegularHumanPeople, - "flower-rangers" => EliminationTeam::FlowerRangers, - "religious-extremists" => EliminationTeam::ReligiousExtremists, - "hivemind" => EliminationTeam::Hivemind, - "caps-lock-crew" => EliminationTeam::CapsLockCrew, - _ => Err(de::Error::unknown_variant(team_raw, &[]))?, - }) - } - } - _ => (), - } - } - - let name = name.ok_or_else(|| de::Error::missing_field("name"))?; - - match name { - CompetitionName::Elimination => { - if let Some(team) = team { - let score = score.ok_or_else(|| de::Error::missing_field("score"))?; - let attacks = attacks.ok_or_else(|| de::Error::missing_field("attacks"))?; - Ok(Some(Competition::Elimination { - team, - score, - attacks, - })) - } else { - Ok(None) - } - } - CompetitionName::DogTags => { - let score = score.ok_or_else(|| de::Error::missing_field("score"))?; - let position = position.ok_or_else(|| de::Error::missing_field("position"))?; - - Ok(Some(Competition::DogTags { score, position })) - } - CompetitionName::Unknown => Ok(Some(Competition::Unknown)), - } - } - } - - deserializer.deserialize_option(CompetitionVisitor) -} - -#[derive(Debug, IntoOwned, Deserialize)] -pub struct Profile<'a> { - pub player_id: i32, - pub name: &'a str, - pub rank: &'a str, - pub level: i16, - pub gender: Gender, - pub age: i32, - - pub life: LifeBar, - pub last_action: LastAction, - #[serde(deserialize_with = "deserialize_faction")] - pub faction: Option>, - pub job: EmploymentStatus, - pub status: Status<'a>, - - #[serde(deserialize_with = "deserialize_comp")] - pub competition: Option, - - #[serde(deserialize_with = "de_util::int_is_bool")] - pub revivable: bool, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct PersonalStats { - #[serde(rename = "attackswon")] - pub attacks_won: i32, - #[serde(rename = "attackslost")] - pub attacks_lost: i32, - #[serde(rename = "defendswon")] - pub defends_won: i32, - #[serde(rename = "defendslost")] - pub defends_lost: i32, - #[serde(rename = "statenhancersused")] - pub stat_enhancers_used: i32, - pub refills: i32, - #[serde(rename = "drugsused")] - pub drugs_used: i32, - #[serde(rename = "xantaken")] - pub xanax_taken: i32, - #[serde(rename = "lsdtaken")] - pub lsd_taken: i32, - #[serde(rename = "networth")] - pub net_worth: i64, - #[serde(rename = "energydrinkused")] - pub cans_used: i32, - #[serde(rename = "boostersused")] - pub boosters_used: i32, - pub awards: i16, - pub elo: i16, - #[serde(rename = "daysbeendonator")] - pub days_been_donator: i16, - #[serde(rename = "bestdamage")] - pub best_damage: i32, -} - -#[derive(Deserialize)] -pub struct Crimes1 { - pub selling_illegal_products: i32, - pub theft: i32, - pub auto_theft: i32, - pub drug_deals: i32, - pub computer_crimes: i32, - pub murder: i32, - pub fraud_crimes: i32, - pub other: i32, - pub total: i32, -} - -#[derive(Deserialize)] -pub struct Crimes2 { - pub vandalism: i32, - pub theft: i32, - pub counterfeiting: i32, - pub fraud: i32, - #[serde(rename = "illicitservices")] - pub illicit_services: i32, - #[serde(rename = "cybercrime")] - pub cyber_crime: i32, - pub extortion: i32, - #[serde(rename = "illegalproduction")] - pub illegal_production: i32, - pub total: i32, -} - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum CriminalRecord { - Crimes1(Crimes1), - Crimes2(Crimes2), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Icon(i16); - -impl Icon { - pub const SUBSCRIBER: Self = Self(4); - pub const LEVEL_100: Self = Self(5); - pub const GENDER_MALE: Self = Self(6); - pub const GENDER_FEMALE: Self = Self(7); - pub const MARITAL_STATUS: Self = Self(8); - pub const FACTION_MEMBER: Self = Self(9); - pub const PLAYER_COMMITTEE: Self = Self(10); - pub const STAFF: Self = Self(11); - - pub const COMPANY: Self = Self(27); - pub const BANK_INVESTMENT: Self = Self(29); - pub const PROPERTY_VAULT: Self = Self(32); - pub const DUKE_LOAN: Self = Self(33); - - pub const DRUG_COOLDOWN: Self = Self(53); - - pub const FEDDED: Self = Self(70); - pub const TRAVELLING: Self = Self(71); - pub const FACTION_LEADER: Self = Self(74); - pub const TERRITORY_WAR: Self = Self(75); - - pub const FACTION_RECRUIT: Self = Self(81); - pub const STOCK_MARKET: Self = Self(84); -} - -impl<'de> Deserialize<'de> for Icon { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct IconVisitor; - - impl<'de> Visitor<'de> for IconVisitor { - type Value = Icon; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "struct Icon") - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - if let Some(suffix) = v.strip_prefix("icon") { - Ok(Icon(suffix.parse().map_err(|_e| { - de::Error::invalid_value(de::Unexpected::Str(suffix), &"&str \"IconXX\"") - })?)) - } else { - Err(de::Error::invalid_value( - de::Unexpected::Str(v), - &"&str \"iconXX\"", - )) - } - } - } - - deserializer.deserialize_str(IconVisitor) - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum Job { - Director, - Employee, - Education, - Army, - Law, - Casino, - Medical, - Grocer, - #[serde(other)] - Other, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Company { - PlayerRun { - name: String, - id: i32, - company_type: u8, - }, - CityJob, -} - -impl<'de> Deserialize<'de> for Company { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct CompanyVisitor; - - impl<'de> Visitor<'de> for CompanyVisitor { - type Value = Company; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("enum Company") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - #[allow(clippy::enum_variant_names)] - #[derive(Deserialize)] - #[serde(rename_all = "snake_case")] - enum Field { - CompanyId, - CompanyName, - CompanyType, - #[serde(other)] - Other, - } - - let mut id = None; - let mut name = None; - let mut company_type = None; - - while let Some(key) = map.next_key()? { - match key { - Field::CompanyId => { - id = Some(map.next_value()?); - if id == Some(0) { - return Ok(Company::CityJob); - } - } - Field::CompanyType => company_type = Some(map.next_value()?), - Field::CompanyName => { - name = Some(map.next_value()?); - } - Field::Other => (), - } - } - - let id = id.ok_or_else(|| de::Error::missing_field("company_id"))?; - let name = name.ok_or_else(|| de::Error::missing_field("company_name"))?; - let company_type = - company_type.ok_or_else(|| de::Error::missing_field("company_type"))?; - - Ok(Company::PlayerRun { - name, - id, - company_type, - }) - } - } - - deserializer.deserialize_map(CompanyVisitor) - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct EmploymentStatus { - pub job: Job, - #[serde(flatten)] - pub company: Company, -} - -#[derive(Debug, Clone, Copy)] -pub struct Award { - pub award_time: chrono::DateTime, -} - -pub trait AwardMarker { - fn award_key() -> &'static str; - fn time_key() -> &'static str; -} - -#[derive(Debug, Clone)] -pub struct Awards -where - T: AwardMarker, -{ - pub inner: BTreeMap, - marker: std::marker::PhantomData, -} - -impl std::ops::Deref for Awards -where - T: AwardMarker, -{ - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for Awards -where - T: AwardMarker, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -impl Awards -where - T: AwardMarker, -{ - pub fn into_inner(self) -> BTreeMap { - self.inner - } -} - -pub struct Medals; - -impl AwardMarker for Medals { - #[inline(always)] - fn award_key() -> &'static str { - "medals_awarded" - } - #[inline(always)] - fn time_key() -> &'static str { - "medals_time" - } -} - -pub struct Honors; - -impl AwardMarker for Honors { - #[inline(always)] - fn award_key() -> &'static str { - "honors_awarded" - } - #[inline(always)] - fn time_key() -> &'static str { - "honors_time" - } -} - -impl<'de, T> Deserialize<'de> for Awards -where - T: AwardMarker, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct AwardVisitor(std::marker::PhantomData) - where - T: AwardMarker; - - impl<'de, T> Visitor<'de> for AwardVisitor - where - T: AwardMarker, - { - type Value = Awards; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct awards") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut awards: Option> = None; - let mut times: Option> = None; - - while let Some(key) = map.next_key::<&'de str>()? { - if key == T::award_key() { - awards = map.next_value()?; - } else if key == T::time_key() { - times = map.next_value()?; - } - } - - let awards = awards.ok_or_else(|| de::Error::missing_field(T::award_key()))?; - let times = times.ok_or_else(|| de::Error::missing_field(T::time_key()))?; - - Ok(Awards { - inner: zip( - awards, - times.into_iter().map(|t| Award { - award_time: chrono::DateTime::from_timestamp(t, 0).unwrap_or_default(), - }), - ) - .collect(), - marker: Default::default(), - }) - } - } - - deserializer.deserialize_map(AwardVisitor(Default::default())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{async_test, setup, Client, ClientTrait}; - - #[async_test] - async fn user() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .user(|b| { - b.selections([ - Selection::Basic, - Selection::Discord, - Selection::Profile, - Selection::PersonalStats, - Selection::Crimes, - Selection::Attacks, - Selection::Medals, - Selection::Honors, - ]) - }) - .await - .unwrap(); - - response.basic().unwrap(); - response.discord().unwrap(); - response.profile().unwrap(); - response.personal_stats().unwrap(); - response.crimes().unwrap(); - response.attacks().unwrap(); - response.attacks_full().unwrap(); - response.medals().unwrap(); - response.honors().unwrap(); - } - - #[async_test] - async fn not_in_faction() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .user(|b| b.id(28).selections([Selection::Profile])) - .await - .unwrap(); - - let faction = response.profile().unwrap().faction; - - assert!(faction.is_none()); - } - - #[async_test] - async fn bulk() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .users([1, 2111649, 374272176892674048i64], |b| { - b.selections([Selection::Basic]) - }) - .await; - - response.get(&1).as_ref().unwrap().as_ref().unwrap(); - response.get(&2111649).as_ref().unwrap().as_ref().unwrap(); - } - - #[async_test] - async fn discord() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .user(|b| b.id(374272176892674048i64).selections([Selection::Basic])) - .await - .unwrap(); - - assert_eq!(response.basic().unwrap().player_id, 2111649); - } - - #[async_test] - async fn fedded() { - let key = setup(); - - let response = Client::default() - .torn_api(key) - .user(|b| b.id(1900654).selections([Selection::Icons])) - .await - .unwrap(); - - let icons = response.icons().unwrap(); - - assert!(icons.contains_key(&Icon::FEDDED)) - } -}