diff --git a/.dockerignore b/.dockerignore index 94a969dc1f2264778bbdf7ecd0917c3cc4c7f9be..4154867b1d308d6f4c92e46df27b00d7fe985168 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,3 @@ /target .env -Dockerfile \ No newline at end of file +Dockerfile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index af965808505f24eca6498781a71d48d5c6268168..d9f37096bf3bdd440eba3f41da533a78fa1982e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,8 @@ # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: - test + - build + - deploy .test-template: tags: @@ -39,14 +41,50 @@ fmt: - pre-commit run fmt -av build_img: + stage: build tags: - kube only: - - master + refs: + - master + changes: + - "src/**/*" + - "Cargo.toml" + - "Cargo.lock" image: name: r.j3ss.co/img entrypoint: [""] + script: - img login --password "${DOCKER_PASSWORD}" --username "${DOCKER_USER}" "${DOCKER_REGISTRY}" - img build --no-console -t docker.le-memese.com/bots/s3bot:latest . - img push docker.le-memese.com/bots/s3bot:latest + +deploy: + stage: deploy + tags: + - kube + only: + refs: + - master + needs: + - build_img + image: + name: alpine/helm:3.7.1 + entrypoint: ["/bin/sh", "-c"] + environment: + name: prod + action: start + url: "https://s3bot.le-memese.com" + script: + - helm + upgrade + s3bot + ./deploy/helm + --install + --wait + --create-namespace + --atomic + --timeout 2m + --namespace "$NAMESPACE" + -f "$HELM_CONFIG" diff --git a/Cargo.lock b/Cargo.lock index 48e9db9dfe516c23feb6dfd77fa6d6a5c598e352..f7f525ab9722b92b4f44ef29ba7f969579131646 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,7 +72,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand", + "rand 0.8.5", "sha1 0.10.5", "smallvec", "tokio", @@ -230,7 +230,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom", + "getrandom 0.2.8", "once_cell", "version_check", ] @@ -333,6 +333,19 @@ dependencies = [ "toml", ] +[[package]] +name = "async-compression" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-trait" version = "0.1.64" @@ -554,6 +567,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -774,6 +797,15 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fern" version = "0.6.1" @@ -801,6 +833,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -810,6 +857,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.26" @@ -909,6 +966,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -931,7 +999,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -947,6 +1015,7 @@ dependencies = [ "grammers-mtsender", "grammers-session", "grammers-tl-types", + "html5ever", "locate-locale", "log", "md5", @@ -964,7 +1033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "573508524fd529ced63fe827fb89629a186414541fd9e645fef9ba499fc69c63" dependencies = [ "aes", - "getrandom", + "getrandom 0.2.8", "glass_pumpkin", "hmac", "num-bigint", @@ -982,7 +1051,7 @@ dependencies = [ "bytes", "crc32fast", "flate2", - "getrandom", + "getrandom 0.2.8", "grammers-crypto", "grammers-tl-types", "log", @@ -1108,6 +1177,20 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "html5ever" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "http" version = "0.2.8" @@ -1119,6 +1202,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "http-range" version = "0.1.5" @@ -1143,6 +1237,43 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" +[[package]] +name = "hyper" +version = "0.14.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1187,6 +1318,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.5" @@ -1197,6 +1337,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + [[package]] name = "is-terminal" version = "0.4.3" @@ -1322,6 +1468,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + [[package]] name = "md5" version = "0.7.0" @@ -1386,6 +1552,30 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nom" version = "7.1.3" @@ -1405,7 +1595,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -1449,6 +1639,51 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_info" version = "3.6.0" @@ -1495,7 +1730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e0b28ace46c5a396546bcf443bf422b57049617433d8854227352a4a9b24e7" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1524,6 +1759,63 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1548,6 +1840,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1601,6 +1899,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.5" @@ -1608,8 +1920,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -1619,7 +1941,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -1628,7 +1959,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.8", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -1679,6 +2028,70 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +dependencies = [ + "async-compression", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1702,6 +2115,18 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.12" @@ -1726,14 +2151,26 @@ dependencies = [ "futures", "grammers-client", "grammers-session", + "lazy_static", "log", - "rand", + "rand 0.8.5", "rayon", + "regex", + "reqwest", "serde", "serde_json", "tokio", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1746,6 +2183,39 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.16" @@ -1843,6 +2313,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.7" @@ -1868,6 +2344,38 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1891,6 +2399,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1984,6 +2517,27 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-util" version = "0.7.7" @@ -2007,6 +2561,12 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -2028,6 +2588,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -2070,6 +2636,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -2081,12 +2653,40 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -2124,6 +2724,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -2153,6 +2765,26 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "winapi" version = "0.2.8" @@ -2277,6 +2909,15 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index bc66a3b666993989f4e18730e03b3bdafae004c2..fbc8a78bd4d69a0e6911da6956149abfa7c15f45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,14 @@ dotenvy = "^0.15.6" dyn-clone = "1.0.10" fern = { version = "0.6.1", features = ["chrono", "colored"] } futures = "0.3.26" -grammers-client = { version = "0.4.0", features = ["markdown"] } +grammers-client = { version = "0.4.0", features = ["markdown", "html"] } grammers-session = "0.4.0" +lazy_static = "1.4.0" log = "0.4.17" rand = "0.8.5" rayon = "1.6.1" +regex = "1.7.1" +reqwest = { version = "0.11.14", features = ["gzip", "json", "tokio-rustls"] } serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" tokio = { version = "1.25.0", features = [ diff --git a/Dockerfile b/Dockerfile index 54495390c433c5ac6f2239133c8490b2bdf62a54..9c43cb2713d106694696514412405e12b83da785 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ WORKDIR /app COPY Cargo.toml Cargo.lock askama.toml ./ COPY src ./src COPY static ./static +# Build binary in release mode. RUN cargo build --release --all-features FROM debian:bullseye-20230109-slim as base @@ -16,12 +17,16 @@ RUN apt-get update \ COPY static ./static +# Copy built binary to a new image. COPY --from=builder /app/target/release/s3bot /usr/local/bin/ ENTRYPOINT ["/usr/local/bin/s3bot"] FROM base as rootless +# Create a user and make the image rootless. So no one +# can escalate privileges even if they have access to +# container. RUN useradd --create-home -u 1000 --user-group s3bot WORKDIR /home/s3bot RUN mv /static ./static diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..37a940ead71f2401b9f1c4f1c32704a039386cbc --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +<div align="center"> + <img src="./logo.png" width="300"> + <h1 align="center"> + Automated telegram account + </h1> +</div> + +This project is an attempt to add some utillities to your account. + +This app uses telegram API and connects to it as a user, not as a bot. To use it, you need to register a new application, and obtain `api hash token` and `application id` from official telegram website. You can do it [here](https://core.telegram.org/api/obtaining_api_id). + + +## How to + +First of all, you need to install [Rust](http://rust-lang.org/). Personally I recommend to use [rustup](https://rustup.rs/). + +Make sure everything is fine by running `cargo --version`. + +1. Compile the app. + ```bash + cargo build --release + ``` + After that command you'll find a compiled binary in `target/release/s3bot`. + +2. Run the compiled binary. + + +## Configuration + +You can configure this app by either command line arguments or with environment variables. + +If you place .env file in current working directory, contents will be loaded as environment variables. + +For additional help please use: + +```bash +s3bot --help +``` \ No newline at end of file diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 0000000000000000000000000000000000000000..0e8a0eb36f4ca2c939201c0d54b5d82a1ea34778 --- /dev/null +++ b/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0978bd78de08dbbfe43c3683263a7cb8e30cbcb2 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: s3bot +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "latest" diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt new file mode 100644 index 0000000000000000000000000000000000000000..d39da692a8f1ecede816a7fe1fa85f08c1e6a604 --- /dev/null +++ b/helm/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "s3bot.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "s3bot.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "s3bot.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "s3bot.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..129e6f954ed0ac4dd634b8c8842e8c1de16dd00c --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "s3bot.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "s3bot.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "s3bot.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "s3bot.labels" -}} +helm.sh/chart: {{ include "s3bot.chart" . }} +{{ include "s3bot.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "s3bot.selectorLabels" -}} +app.kubernetes.io/name: {{ include "s3bot.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "s3bot.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "s3bot.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d6d2c0be0592dd1d12d5f95ab1d31dea5b6cd13a --- /dev/null +++ b/helm/templates/deployment.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "s3bot.fullname" . }} + labels: + {{- include "s3bot.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: 1 + {{- end }} + selector: + matchLabels: + {{- include "s3bot.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "s3bot.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "s3bot.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ default 8000 .Values.env.BOT_SERVER_PORT }} + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: http + readinessProbe: + httpGet: + path: /health + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.env }} + env: + {{- range $key, $val := . }} + - name: {{ $key | quote }} + value: {{ $val | quote }} + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/templates/ingress.yaml b/helm/templates/ingress.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b37b206cfe11e62a2004f57b59a2b25c5d0bd404 --- /dev/null +++ b/helm/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "s3bot.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "s3bot.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..06b58d561c5e7e54476994fd03b327f3080356ed --- /dev/null +++ b/helm/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "s3bot.fullname" . }} + labels: + {{- include "s3bot.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "s3bot.selectorLabels" . | nindent 4 }} diff --git a/helm/templates/serviceaccount.yaml b/helm/templates/serviceaccount.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0b01689f598cf4646ee507042254a3405fd3a12a --- /dev/null +++ b/helm/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "s3bot.serviceAccountName" . }} + labels: + {{- include "s3bot.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/templates/tests/test-connection.yaml b/helm/templates/tests/test-connection.yaml new file mode 100644 index 0000000000000000000000000000000000000000..092ba623b424162c33de0a17aaa363ac99b0951a --- /dev/null +++ b/helm/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "s3bot.fullname" . }}-test-connection" + labels: + {{- include "s3bot.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "s3bot.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b6e61d22321a1f48b44d3ae79faa27f23f4c31cb --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,89 @@ +# Default values for s3bot. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +image: + repository: docker.le-memese.com/bots/s3bot + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +env: + BOT_SERVER_HOST: "0.0.0.0" + BOT_SERVER_PORT: 8000 + BOT_SERVER_USERNAME: "s3rius_san" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: + {} + # fsGroup: 2000 + +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: + {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f0c18852f0ceba3af1f519e355cb9a9dfac8be13 Binary files /dev/null and b/logo.png differ diff --git a/src/args.rs b/src/args.rs index ca9fc82077ff83c15ef7f05f45f29d2416d4c7b9..15ab61b19805f7e82c996a9940abba6681e6419e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -77,8 +77,21 @@ pub struct BotConfig { )] pub session_file: String, - #[arg(name = "bot-excluded-chats", long, env = "BOT_EXCLUDED_CHATS")] + #[arg( + name = "bot-excluded-chats", + long, + env = "BOT_EXCLUDED_CHATS", + value_delimiter = ',' + )] pub excluded_chats: Vec<i64>, + + #[arg( + name = "bot-currency-excluded-chats", + long, + env = "BOT_CURRENCY_EXCLUDED_CHATS", + value_delimiter = ',' + )] + pub currency_excluded_chats: Vec<i64>, } #[derive(Clone, Parser, Debug)] diff --git a/src/bot/filters/chain.rs b/src/bot/filters/filtered_handler.rs similarity index 76% rename from src/bot/filters/chain.rs rename to src/bot/filters/filtered_handler.rs index 14f57e7b858a9ad456a0e17155c239a886fbee11..44fd29f9d7426246dfce2b3862b6eb89565dde7d 100644 --- a/src/bot/filters/chain.rs +++ b/src/bot/filters/filtered_handler.rs @@ -4,6 +4,11 @@ use crate::bot::handlers::Handler; use super::base::Filter; +/// This is a structure to match +/// handlers with corresponding filters. +/// +/// It's handy, because for different messages +/// we have different set of rules. #[derive(Clone)] pub struct FilteredHandler { filters: Vec<Box<dyn Filter>>, @@ -23,6 +28,8 @@ impl FilteredHandler { self } + /// This method performs checks for all filters we have. + /// We run it not in parralel for fast fail strategy. pub fn check(&self, update: &Update) -> bool { for filter in &self.filters { match filter.filter(update) { diff --git a/src/bot/filters/groups.rs b/src/bot/filters/groups.rs deleted file mode 100644 index 8b137891791fe96927ad78e64b0aad7bded08bdc..0000000000000000000000000000000000000000 --- a/src/bot/filters/groups.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/bot/filters/message_fitlers.rs b/src/bot/filters/message_fitlers.rs index 0c8ea458f2ad0a553cf15b3404219613c351bf17..f2d973d20848cffd4328f954b429c54b674f109f 100644 --- a/src/bot/filters/message_fitlers.rs +++ b/src/bot/filters/message_fitlers.rs @@ -1,5 +1,7 @@ use grammers_client::Update; +use crate::utils::messages::get_message; + use super::base::Filter; #[allow(dead_code)] @@ -15,9 +17,17 @@ pub enum TextMatchMethod { IMatches, } +/// This filter is used to filter out +/// that marked as silent. +#[derive(Clone)] +pub struct SilentFilter; + +/// This filter checks that current +/// chat is not one of excluded chats. #[derive(Clone)] pub struct ExcludedChatsFilter(pub Vec<i64>); +/// This filter checks for message directions. #[derive(Clone)] pub struct MessageDirectionFilter(pub MessageDirection); @@ -31,10 +41,8 @@ impl Filter for ExcludedChatsFilter { Update::CallbackQuery(query) => query.chat().clone(), _ => return Ok(false), }; - if self.0.contains(&a.id()) { - return Ok(false); - } - Ok(true) + // Check that list of excluded chats doesn't contain our chat. + Ok(!self.0.contains(&a.id())) } } @@ -44,6 +52,7 @@ impl Filter for MessageDirectionFilter { let res = matches!( (self.0, message.outgoing()), + // Here we check that message's direction matches the direction we want. (MessageDirection::Outgoing, true) | (MessageDirection::Incoming, false) ); Ok(res) @@ -72,3 +81,11 @@ impl<'a> Filter for TextFilter<'a> { Ok(false) } } + +impl Filter for SilentFilter { + fn filter(&self, update: &Update) -> anyhow::Result<bool> { + let Some(message) = get_message(update) else {return Ok(false)}; + // Check that message is not silent. + Ok(!message.silent()) + } +} diff --git a/src/bot/filters/mod.rs b/src/bot/filters/mod.rs index 51fe228303aff09c868cf87148887a45cfe05f07..9b169f65c8ca930922c8675f1073758a0743f8c0 100644 --- a/src/bot/filters/mod.rs +++ b/src/bot/filters/mod.rs @@ -1,3 +1,3 @@ -mod base; -pub mod chain; +pub mod base; +pub mod filtered_handler; pub mod message_fitlers; diff --git a/src/bot/handlers/basic/currency_converter.rs b/src/bot/handlers/basic/currency_converter.rs new file mode 100644 index 0000000000000000000000000000000000000000..57f854ce08a747c3f0182b976661ead578c3e656 --- /dev/null +++ b/src/bot/handlers/basic/currency_converter.rs @@ -0,0 +1,176 @@ +use std::{collections::HashMap, time::Duration}; + +use grammers_client::{Client, InputMessage, Update}; +use regex::Regex; + +use crate::{ + bot::{filters::base::Filter, handlers::Handler}, + utils::messages::get_message, +}; + +lazy_static::lazy_static! { + static ref SUPPORTED_CURS: Vec<&'static str> = vec![ + "GBP", + "HUF", + "USD", + "EUR", + "CNY", + "NOK", + "UAH", + "SEK", + "CHF", + "KRW", + "JPY", + "KZT", + "PLN", + "TRY", + "AMD", + "RSD", + ]; + + static ref CONVERTION_ALIASES: HashMap<&'static str, &'static str> = HashMap::from( + [ + // GBP + ("фунт", "GBP"), + // USD + ("бакÑ", "USD"), + ("доллар", "USD"), + // EUR + ("евро", "EUR"), + // JPY + ("иен", "JPY"), + ("йен", "JPY"), + // KRW + ("вон", "KRW"), + // CHF + ("франк", "CHF"), + // SEK + ("крон", "CHF"), + // CNY + ("юан", "CNY"), + // UAH + ("гривна", "UAH"), + ("гривны", "UAH"), + ("гривен", "UAH"), + ("грiвен", "UAH"), + // KZT + ("тенге", "KZT"), + ("Ñ‚Ñнге", "KZT"), + // PLN + ("злот", "PLN"), + // TRY + ("лир", "TRY"), + // AMD + ("драм", "AMD"), + // RSD + ("динар", "RSD"), + ] + ); + + static ref CUR_REGEX: Regex = { + #[allow(clippy::clone_double_ref)] + let a = CONVERTION_ALIASES.keys() + .copied() + .chain(SUPPORTED_CURS.iter().copied()) + .collect::<Vec<_>>() + .join("|"); + Regex::new(format!(r"\s*(?P<cur_value>\d+([\.,]\d+)?)\s+(?P<cur_name>{a})").as_str()).unwrap() + }; +} + +#[derive(Clone)] +pub struct CurrencyTextFilter; + +#[derive(Clone)] +pub struct CurrencyConverter { + client: reqwest::Client, +} + +impl CurrencyConverter { + pub fn new() -> anyhow::Result<Self> { + let client = reqwest::ClientBuilder::new() + .timeout(Duration::from_secs(2)) + .gzip(true) + .build()?; + Ok(Self { client }) + } +} + +/// This filter check if the message matches regex for currencies. +impl Filter for CurrencyTextFilter { + fn filter(&self, update: &Update) -> anyhow::Result<bool> { + let Some(message) = get_message(update) else { + return Ok(false); + }; + Ok(CUR_REGEX.is_match(message.text())) + } +} + +#[async_trait::async_trait] +impl Handler for CurrencyConverter { + async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> { + let Some(message) = get_message(update) else{ return Ok(())}; + let response = self + .client + .get("https://www.cbr-xml-daily.ru/daily_json.js") + .send() + .await? + .error_for_status()? + .json::<serde_json::Value>() + .await?; + + let Some(valutes) = response + .get("Valute") + .and_then(serde_json::Value::as_object) else{ + log::warn!("Can't get valutes fom response."); + return Ok(()); + }; + + let mut calucates = Vec::new(); + + for capture in CUR_REGEX.captures_iter(message.text()) { + // We parse supplied value from message + let Some(num_value) = capture + .name("cur_value") + // Convert match to string. + .map(|mtch| mtch.as_str()) + // Parse it. + .and_then(|val| val.parse::<f64>().ok()) else{ + continue; + }; + let cur_name = capture.name("cur_name").map(|mtch| mtch.as_str()); + let Some(cur_name) = cur_name + // We check if the value is an alias. + .and_then(|val| CONVERTION_ALIASES.get(val).copied()) + // get previous value if not. + .or(cur_name) else{ + continue; + }; + let calculated = valutes + .get(cur_name) + .and_then(|info| info.get("Value")) + .map(ToString::to_string) + .and_then(|value| value.as_str().parse::<f64>().ok()) + .map(|multiplier| multiplier * num_value); + if let Some(value) = calculated { + calucates.push(format!( + "<pre>{num_value} {cur_name} = {value:.2} RUB</pre><br>" + )); + } + } + + if !calucates.is_empty() { + let mut bot_response = + String::from("<b>ПолагаÑÑÑŒ на текущий ÐºÑƒÑ€Ñ Ð²Ð°Ð»ÑŽÑ‚ могу Ñказать Ñледующее:</b>\n\n"); + for calc in calucates { + bot_response.push_str(calc.as_str()); + } + message + // We send it as silent, so we can filter this message later. + .reply(InputMessage::html(bot_response).silent(true)) + .await?; + } + + Ok(()) + } +} diff --git a/src/bot/handlers/basic/help.rs b/src/bot/handlers/basic/help.rs index 5f96340ef85e008ef47d860e8eeae5d4f61a5d39..00a96ca9f4b5264a00648b40fbed5f3e4cfe3b50 100644 --- a/src/bot/handlers/basic/help.rs +++ b/src/bot/handlers/basic/help.rs @@ -10,7 +10,7 @@ impl Handler for Help { async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> { let Update::NewMessage(message) = update else {return Ok(())}; - message.reply("Хелпа").await?; + message.reply("Я больше не раÑÑказываю что Ñ ÑƒÐ¼ÐµÑŽ.").await?; Ok(()) } diff --git a/src/bot/handlers/basic/mod.rs b/src/bot/handlers/basic/mod.rs index c1ee8d4df2f7fe224628d91ecd5304ece7ab33f4..3f81a214a474dcf573fca1e475444a68aa3e2012 100644 --- a/src/bot/handlers/basic/mod.rs +++ b/src/bot/handlers/basic/mod.rs @@ -1,2 +1,3 @@ +pub mod currency_converter; pub mod get_chat_id; pub mod help; diff --git a/src/bot/handlers/fun/blyaficator.rs b/src/bot/handlers/fun/blyaficator.rs index 6a4ce8341a87c40e8c354665ac06e95411595889..c8660f897be6a983b92788f7a646a733bdc22b2c 100644 --- a/src/bot/handlers/fun/blyaficator.rs +++ b/src/bot/handlers/fun/blyaficator.rs @@ -6,6 +6,7 @@ use grammers_client::{Client, Update}; const BLYA_WORDS: &[&str] = &[", блÑ,", ", Ñука,", ", ёбаный рот,", ", охуеть конечно,"]; +/// It's time to add some блÑs. #[derive(Clone)] pub struct Blyaficator; diff --git a/src/bot/handlers/fun/greeter.rs b/src/bot/handlers/fun/greeter.rs index b66c68536dfaffc03c1162506aa98b3b5ac40552..fc83bd3314217f75bf3a6ebef547cb15f52e6f59 100644 --- a/src/bot/handlers/fun/greeter.rs +++ b/src/bot/handlers/fun/greeter.rs @@ -4,6 +4,17 @@ use rand::seq::IteratorRandom; use crate::bot::handlers::base::Handler; +lazy_static::lazy_static! { + static ref GREETINGS: &'static [&'static str] = &[ + "Привет!", + "Добрый день!", + "ЗдравÑтвуйте.", + "ПриетÑтвую.", + "Доброго времени Ñуток.", + ]; +} + +/// Greeter just replies to greeting messages. #[derive(Clone)] pub struct Greeter; @@ -18,9 +29,8 @@ impl Handler for Greeter { return Ok(()); } - let reply_text = ["Привет!", "Добрый день!", "ЗдравÑтвуйте.", "ПриетÑтвую"] - .into_iter() - .choose(&mut rand::thread_rng()); + // Choose random greeting from the list of greetings. + let reply_text = GREETINGS.iter().choose(&mut rand::thread_rng()).copied(); if let Some(text) = reply_text { message.reply(text).await?; diff --git a/src/bot/main.rs b/src/bot/main.rs index 284f666c45bb567fc64eb7909d0b86c73586e673..c05bb8decfd4460c04ad19e92c560de8e952e361 100644 --- a/src/bot/main.rs +++ b/src/bot/main.rs @@ -8,19 +8,30 @@ use tokio::sync::RwLock; use super::{ filters::{ - chain::FilteredHandler, + filtered_handler::FilteredHandler, message_fitlers::{ - ExcludedChatsFilter, MessageDirection, MessageDirectionFilter, TextFilter, - TextMatchMethod, + ExcludedChatsFilter, MessageDirection, MessageDirectionFilter, SilentFilter, + TextFilter, TextMatchMethod, }, }, handlers::{ - basic::{get_chat_id::GetChatId, help::Help}, + basic::{ + currency_converter::{CurrencyConverter, CurrencyTextFilter}, + get_chat_id::GetChatId, + help::Help, + }, fun::{blyaficator::Blyaficator, greeter::Greeter}, Handler, }, }; +/// Authorization function. +/// +/// This function asks for login code and +/// waits for it to become available. +/// +/// Also it validates two-factor authentication +/// password if it was supplied. async fn authorize( args: &BotConfig, client: &Client, @@ -32,18 +43,19 @@ async fn authorize( .await?; let mut code = None; + // Check for code to becom available every second. while code.is_none() { tokio::time::sleep(Duration::from_secs(1)).await; { code = web_code.read().await.clone(); } } - + // Acutal signing in. let signed_in = client.sign_in(&token, &code.unwrap()).await; match signed_in { + // If signing i Err(SignInError::PasswordRequired(password_token)) => { - // Note: this `prompt` method will echo the password in the console. - // Real code might want to use a better way to handle this. + // If the password was not supplied, we use the hint in panic. let hint = password_token.hint().unwrap_or("None"); let password = args .tfa_password @@ -60,48 +72,82 @@ async fn authorize( Ok(()) } +/// This little function is used to execute handlers on updates and print errors +/// if something bad happens. +/// +/// The reason, I created a separate function is simple. I spawn every handler as a +/// separate task and I don't care if fails. async fn handle_with_log(handler: Box<dyn Handler>, client: Client, update_data: Update) { if let Err(err) = handler.react(&client, &update_data).await { log::error!("{err}"); } } -async fn run(args: BotConfig, client: Client) { +/// Acutal logic on handling updates. +/// +/// This function handles every update we get from telegram +/// and spawns correcsponding handlers. +/// +/// Also, every available handler is defined here. +async fn run(args: BotConfig, client: Client) -> anyhow::Result<()> { let handlers: Vec<FilteredHandler> = vec![ + // Printing help. + FilteredHandler::new(Help).add_filter(TextFilter(&[".h"], TextMatchMethod::IMatches)), + // Greeting my fellow humans. FilteredHandler::new(Greeter) + .add_filter(SilentFilter) .add_filter(MessageDirectionFilter(MessageDirection::Incoming)) .add_filter(TextFilter(&["привет"], TextMatchMethod::IStartsWith)) .add_filter(ExcludedChatsFilter(args.excluded_chats)), - FilteredHandler::new(Help).add_filter(TextFilter(&[".h"], TextMatchMethod::IMatches)), + // Getting chat id. FilteredHandler::new(GetChatId) .add_filter(TextFilter(&[".cid"], TextMatchMethod::IMatches)), + // Make Ð±Ð»Ñ fun again. FilteredHandler::new(Blyaficator) .add_filter(TextFilter(&[".bl"], TextMatchMethod::IStartsWith)), + // Handler for converting currecies. + FilteredHandler::new(CurrencyConverter::new()?) + .add_filter(SilentFilter) + .add_filter(ExcludedChatsFilter(args.currency_excluded_chats)) + .add_filter(CurrencyTextFilter), ]; loop { + // Get new update let update = client.next_update().await; if update.is_err() { log::error!("{}", update.unwrap_err()); break; } - if let Some(update_data) = update.unwrap() { - let update_ref = &update_data; - let matched_handlers = handlers - .par_iter() - .filter(move |val| val.check(update_ref)) - .collect::<Vec<_>>(); - for handler in matched_handlers { - tokio::spawn(handle_with_log( - handler.handler.clone(), - client.clone(), - update_data.clone(), - )); - } + // We get update if there's no error + let Some(update_data) = update.ok().and_then(|inner|inner) else{ + log::warn!("Empty update is found."); + continue; + }; + // A reference to update, so we can easily move it. + let update_ref = &update_data; + let filtered = handlers + // A parralel iterator over matchers. + .par_iter() + // Here we get all handlers that match filters. + .filter(move |val| val.check(update_ref)) + // For each matched handler we spawn a new task. + .collect::<Vec<_>>(); + for handler in filtered { + tokio::spawn(handle_with_log( + handler.handler.clone(), + client.clone(), + update_data.clone(), + )); } } + Ok(()) } +/// The main entrypoint for bot. +/// +/// This function starts bot, performs login and +/// starts endless loop. pub async fn start(args: BotConfig, web_code: Arc<RwLock<Option<String>>>) -> anyhow::Result<()> { log::info!("Connecting to Telegram..."); let client = Client::connect(Config { @@ -115,17 +161,20 @@ pub async fn start(args: BotConfig, web_code: Arc<RwLock<Option<String>>>) -> an }, }) .await?; + log::info!("Connected!"); + if client.is_authorized().await? { // If we already authrized, we write random token, so web won't update it. let mut code_writer = web_code.write().await; *code_writer = Some(String::new()); } else { + // If we don't have token, wait for it. authorize(&args, &client, web_code).await?; client.session().save_to_file(args.session_file.as_str())?; } - run(args.clone(), client).await; + run(args.clone(), client).await?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index 1d4ed0d8f8bead92de0d5f527789f8576ebede54..060f508fd25c1cbfdac39ab0876772f5ffe9ca3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,27 +25,27 @@ async fn main() -> anyhow::Result<()> { let token_lock = Arc::new(RwLock::new(None)); - let bot_token = token_lock.clone(); - let server_token = token_lock.clone(); - - let web_server = server::create(args.server.clone(), server_token)?; - let bot_future = bot::start(args.bot.clone(), bot_token); - - let tasks = [ - tokio::task::spawn(bot_future), - tokio::task::spawn(error_wrap(web_server)), - ]; - - let completed = tasks - .into_iter() - .collect::<FuturesUnordered<_>>() - .take(1) - .collect::<Vec<_>>() - .await; - - if let Some(fut) = completed.into_iter().next() { - fut? - } else { - Ok(()) - } + [ + // Spawining bot task + tokio::task::spawn(bot::start(args.bot.clone(), token_lock.clone())), + // Spawning server task. + tokio::task::spawn(error_wrap(server::create( + args.server.clone(), + token_lock.clone(), + )?)), + ] + .into_iter() + // Turning all tasks in unirdered futures set. + .collect::<FuturesUnordered<_>>() + // Grab first completed future + .take(1) + // Take the value + .next() + // Await for it to complete + .await + // Unwrap (since we can guarantee that it's not empty). + // Throw all errors by using ??. First for joining task, second from the task itself. + .unwrap()??; + + Ok(()) } diff --git a/src/server/main.rs b/src/server/main.rs index 8559ea06af58f2075725f79d7092f43db2af1033..e17691efc19807472bee0d6f08efcef1d123d12a 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -11,9 +11,10 @@ pub fn create(args: ServerConfig, token: Arc<RwLock<Option<String>>>) -> anyhow: let addr = (args.host.clone(), args.port); let server = HttpServer::new(move || { App::new() - .wrap(actix_web::middleware::Logger::new( - "\"%r\" \"-\" \"%s\" \"%a\" \"%D\"", - )) + .wrap( + actix_web::middleware::Logger::new("\"%r\" \"-\" \"%s\" \"%a\" \"%D\"") + .exclude("/health"), + ) .app_data(Data::new(token.clone())) .app_data(Data::new(args.clone())) .service(login) diff --git a/src/server/templates/mod.rs b/src/server/templates/mod.rs index d0d2b26268e0443b406bdf1576130bec300228a7..f6b9e03d439c13accf95a188f9640c68633ac18f 100644 --- a/src/server/templates/mod.rs +++ b/src/server/templates/mod.rs @@ -1,5 +1,16 @@ use askama::Template; +/// Index pages. +/// +/// This page is used to authenticate users. +/// It has two states: activated or not. +/// +/// It the activated is false, we +/// render two input fields, with telegram code +/// and server's password. +/// +/// If the user is authenticated, we just render +/// some random information. #[derive(Template)] #[template(path = "index.html")] pub struct Index {