diff --git a/.cargo/config.toml b/.cargo/config.toml index 46ba45d..365b0e8 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,7 @@ +[build] +rustflags = ["--cfg", "tokio_unstable"] + [env] # Based on https://doc.rust-lang.org/cargo/reference/config.html OtherBots = "Supibot,buttsbot,PotatBotat,StreamElements,yuumeibot" diff --git a/Cargo.lock b/Cargo.lock index da35376..a733c32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,25 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "async-channel" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +dependencies = [ + "concurrent-queue", + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-recursion" version = "1.1.0" @@ -49,7 +68,29 @@ checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.50", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", ] [[package]] @@ -60,7 +101,18 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.50", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", ] [[package]] @@ -69,6 +121,51 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -84,6 +181,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "bitflags" version = "1.3.2" @@ -102,12 +205,24 @@ version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "casual_logger" version = "0.6.5" @@ -145,6 +260,128 @@ dependencies = [ "windows-targets 0.52.3", ] +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_derive", + "clap_lex", + "indexmap 1.9.3", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_complete" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", + "url", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console-api" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" +dependencies = [ + "futures-core", + "prost", + "prost-types", + "tonic", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -161,6 +398,76 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crossterm" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "futures-core", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -182,9 +489,15 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.50", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.8" @@ -195,24 +508,74 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "forcebot_rs" version = "0.1.0" dependencies = [ + "async-channel", "async-recursion", "async-trait", "casual_logger", "chrono", + "console-subscriber", "dotenv", "futures", "rand", "tokio", + "tokio-console", "twitch-irc", ] @@ -231,6 +594,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[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" version = "0.3.30" @@ -287,7 +659,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.50", ] [[package]] @@ -337,12 +709,147 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "h2" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64", + "byteorder", + "flate2", + "nom", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +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-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -366,6 +873,57 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "js-sys" version = "0.3.68" @@ -387,6 +945,17 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -409,12 +978,39 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -431,6 +1027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -453,6 +1050,26 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -468,7 +1085,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.8", "libc", ] @@ -510,7 +1127,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.50", ] [[package]] @@ -531,6 +1148,30 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.1" @@ -554,6 +1195,32 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -578,6 +1245,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.78" @@ -587,6 +1278,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.35" @@ -626,6 +1349,19 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ratatui" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc0d032bccba900ee32151ec0265667535c230169f5a011154cdcd984e16829" +dependencies = [ + "bitflags 1.3.2", + "cassowary", + "crossterm", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -635,6 +1371,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.3" @@ -643,8 +1390,17 @@ checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.5", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -655,9 +1411,15 @@ checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.2", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -683,6 +1445,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + [[package]] name = "schannel" version = "0.1.23" @@ -721,6 +1495,67 @@ dependencies = [ "libc", ] +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "serde_json" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -755,6 +1590,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.50" @@ -766,6 +1618,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "tempfile" version = "3.10.0" @@ -778,6 +1636,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + [[package]] name = "thiserror" version = "1.0.57" @@ -795,9 +1668,34 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.50", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +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.36.0" @@ -814,9 +1712,50 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.48.0", ] +[[package]] +name = "tokio-console" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5ff40e8df801b383b8666967ec4aee8dc516f376d06d0e5a9f93f310763e6d2" +dependencies = [ + "atty", + "clap", + "clap_complete", + "color-eyre", + "console-api", + "crossterm", + "dirs", + "futures", + "h2", + "hdrhistogram", + "humantime", + "once_cell", + "prost-types", + "ratatui", + "regex", + "serde", + "tokio", + "toml", + "tonic", + "tower", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.2.0" @@ -825,7 +1764,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.50", ] [[package]] @@ -863,12 +1802,81 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[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.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -882,7 +1890,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.50", ] [[package]] @@ -892,8 +1900,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "twitch-irc" version = "5.0.1" @@ -915,18 +1969,77 @@ dependencies = [ "tracing", ] +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[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.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" @@ -954,7 +2067,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.50", "wasm-bindgen-shared", ] @@ -976,7 +2089,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.50", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -987,6 +2100,37 @@ version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index f3f7724..7b370e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] dotenv = "0.15.0" -tokio = { version = "1.33.0", features = ["full"] } +tokio = { version = "1.33.0", features = ["full", "tracing"] } twitch-irc = "5.0.1" rand = { version = "0.8.5", features = [] } futures = "0.3" @@ -15,6 +15,9 @@ async-trait = "0.1.77" async-recursion = "1.1.0" casual_logger = "0.6.5" chrono = "0.4.35" +tokio-console = "0.1.10" +console-subscriber = "0.2.0" +async-channel = "2.2.0" [lib] diff --git a/src/core/bot_actions.rs b/src/core/bot_actions.rs index 8941654..4a07d9e 100644 --- a/src/core/bot_actions.rs +++ b/src/core/bot_actions.rs @@ -5,40 +5,143 @@ use tokio::sync::RwLock; use crate::core::botinstance::BotInstance; -use super::{botmodules::{BotAction, BotModule}, identity::ChatBadge}; +use super::{botinstance::Channel, botmodules::{BotAction, BotModule, Routine}, identity::ChatBadge}; pub type BotAR = Arc<RwLock<BotInstance>>; pub type ActAR = Arc<RwLock<BotAction>>; +pub type RoutineAR = Arc<RwLock<Routine>>; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ExecBodyParams { pub bot : BotAR, pub msg : PrivmsgMessage, - pub parent_act : ActAR , + pub parent_act : Option<ActAR> , + pub curr_act : Option<ActAR> , +} + +#[derive(Debug,Eq,PartialEq,Hash)] +pub enum ParamLevel { + Parent, + Current, } impl ExecBodyParams { - pub async fn get_parent_module(&self) -> Option<BotModule> { + // pub async fn get_parent_module(&self) -> Option<BotModule> { + // pub async fn get_parent_module(&self) -> BotModule { + pub async fn get_module(&self) -> BotModule { - let parent_act = Arc::clone(&self.parent_act); - let parent_act_lock = parent_act.read().await; + let curr_acta = Arc::clone(&(self.curr_act.clone().unwrap())); + let parent_act_lock = curr_acta.read().await; let act = &(*parent_act_lock); match act { BotAction::C(c) => { - let temp = c.module.clone(); - Some(temp) + // let temp = c.module.clone(); + // Some(temp) + c.module.clone() }, BotAction::L(l) => { - let temp = l.module.clone(); - Some(temp) + // let temp = l.module.clone(); + // Some(temp) + l.module.clone() }, - _ => None + BotAction::R(r) => { + // let temp = r.module.clone(); + // Some(temp) + r.read().await.module.clone() + } + // _ => None } } + // pub async fn get_channel(&self) -> Option<Channel> { + pub fn get_channel(&self) -> Option<Channel> { + + dbg!("Core > ExecBodyParams > GetChannels START"); + dbg!("!! [x] Document - After SUCCESS message was sent to chat"); + dbg!(">> BotActionAR - RwLock from botmodules.rs::929:46 - current_readers = 1 "); + dbg!(">> RoutineAR - RwLock from botmodules.rs::1261:32 - current_readers = 1 "); + dbg!(">> join_handle - RwLock from botmodules.rs::1226:46 - current_readers = 0 "); + dbg!(">> BotInstanceAR - RwLock from botinstance.rs::150:28 - current_readers = 2"); + + + let curr_act = Arc::clone(&self.curr_act.clone().unwrap()); + // let curr_act_lock = curr_act.read().await; + + // Note : The below `curr_act.blocking_read()` is not possible. I get the following + // during runtime in this areas + /* + thread 'tokio-runtime-worker' panicked at src\core\bot_actions.rs:66:38: +Cannot block the current thread from within a runtime. This happens because a function attempted to block the current thread while the thread is being used to drive asynchronous tasks. + */ + let curr_act_lock = curr_act.blocking_read(); + + dbg!("Core > ExecBodyParams > After Creating ExecBodyParams.current_act.read() Guard "); + dbg!("!! [x] Document - After SUCCESS message was sent to chat"); + dbg!(">> BotActionAR - RwLock from botmodules.rs::929:46 - current_readers = 1 "); + dbg!(">> RoutineAR - RwLock from botmodules.rs::1261:32 - current_readers = 1 "); + dbg!(">> join_handle - RwLock from botmodules.rs::1226:46 - current_readers = 0 "); + dbg!(">> BotInstanceAR - RwLock from botinstance.rs::150:28 - current_readers = 2"); + + let act = &(*curr_act_lock); + + dbg!("Core > ExecBodyParams > Using the Read Guard "); + dbg!("!! [x] Document - After SUCCESS message was sent to chat"); + dbg!(">> BotActionAR - RwLock from botmodules.rs::929:46 - current_readers = 1 "); + dbg!(">> RoutineAR - RwLock from botmodules.rs::1261:32 - current_readers = 1"); + dbg!(">> join_handle - RwLock from botmodules.rs::1226:46 - current_readers = Not Listed "); + dbg!(">> BotInstanceAR - RwLock from botinstance.rs::150:28 - current_readers = 2"); + + let out = match act { + BotAction::C(_) => { + // let temp = c.module.clone(); + // Some(temp) + Some(Channel(self.msg.channel_login.clone())) + }, + BotAction::L(_) => { + // let temp = l.module.clone(); + // Some(temp) + // l.module.clone() + Some(Channel(self.msg.channel_login.clone())) + }, + BotAction::R(r) => { + // let temp = r.module.clone(); + // Some(temp) + dbg!("Core > ExecBodyParams > GetChannels - routine identified"); + dbg!("!! [x] Document"); + dbg!(">> BotActionAR - RwLock from botmodules.rs::930:46 - current_readers = 2"); + dbg!(">> RoutineAR - RwLock from botmodules.rs::1262:32 - current_readers = 1"); + dbg!(">> join_handle - RwLock from botmodules.rs::1226:46 - current_readers = 0"); + dbg!(">> BotInstanceAR - RwLock from botinstance.rs::150:28 - current_readers = 1"); + // => 03.30 - Just before deadlock + // dbg!("ISSUE : RoutineAR - RwLock from botmodules.rs::1261:32 - current_readers = 1"); + // let out = Some(r.read().await.channel.clone()); + + + dbg!("ISSUE > Never makes it after the following read guard"); + + // let guard = r.read().await; + let guard = r.blocking_read(); + + dbg!("ISSUE RESOLVED > If passed htis point"); + + let channel = guard.channel.clone(); + drop(guard); + let out = Some(channel); + + // => 03.30 - This isn't reached because of the Deadlock + dbg!(">> Just after Potential Deadlock Lock"); + out + } + // _ => None + }; + out + } + + + pub fn get_sender(&self) -> String { self.msg.sender.name.clone() } diff --git a/src/core/botinstance.rs b/src/core/botinstance.rs index 07b481d..7198d01 100644 --- a/src/core/botinstance.rs +++ b/src/core/botinstance.rs @@ -40,15 +40,15 @@ pub struct Channel(pub String); use super::bot_actions::ExecBodyParams; use super::botmodules::StatusType; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct BotManagers { pub identity: Arc<RwLock<IdentityManager>>, - pub chat: Chat, + pub chat: Arc<RwLock<Chat>>, } impl BotManagers { pub fn init( - ratelimiters: HashMap<Channel, RateLimiter>, + ratelimiters: HashMap<Channel, Arc<RwLock<RateLimiter>>>, client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, ) -> BotManagers { BotManagers { @@ -70,6 +70,7 @@ impl<T: Clone> ArcBox<T> { } } +#[derive(Debug)] pub struct BotInstance { pub prefix: char, pub bot_channel: Channel, @@ -115,7 +116,7 @@ impl BotInstance { client.join(chnl.to_owned()).unwrap(); - let n = RateLimiter::new(); + let n = Arc::new(RwLock::new(RateLimiter::new())); ratelimiters.insert(Channel(String::from(chnl)), n); } @@ -168,6 +169,7 @@ impl BotInstance { match message { ServerMessage::Notice(msg) => { + dbg!("NOTICE TRIGGERED",msg.clone()); botlog::notice( format!("NOTICE : (#{:?}) {}", msg.channel_login, msg.message_text) .as_str(), @@ -200,7 +202,18 @@ impl BotInstance { ); Log::flush(); - BotInstance::listener_main_prvmsg(Arc::clone(&bot), &msg).await; + let botarc = Arc::clone(&bot); + // let msgarc = Arc::new(RwLock::new(msg.clone())); + + let msgtext = msg.message_text.clone(); + + dbg!("will spawn listener_main_prvmsg from runner",chrono::offset::Local::now(),msgtext.clone()); + tokio::spawn( async move { + // let msgarc_c = msgarc.clone(); + BotInstance::listener_main_prvmsg(botarc, &msg.clone()).await; + }); + // BotInstance::listener_main_prvmsg(Arc::clone(&bot), &msg).await; + dbg!("completed spawn listener_main_prvmsg from runner",chrono::offset::Local::now(), msgtext); } ServerMessage::Whisper(msg) => { botlog::debug( @@ -366,13 +379,13 @@ impl BotInstance { Channel(msg.channel_login.to_string())).await; - if let StatusType::Disabled(a) = modstatus { + if let StatusType::Disabled(statuslvl) = modstatus { // [x] Should only respond if a BotAdmin , Mod , SupMod , BroadCaster // - Specifically it should respond only to those who may be able to enable the module botlog::trace( - &format!("Identified cmd is associated with Disabled Module : StatusLvl = {:?}", a), + &format!("Identified cmd is associated with Disabled Module : StatusLvl = {:?}", statuslvl), Some("BotInstance > listener_main_prvmsg()".to_string()), Some(msg), ); @@ -396,20 +409,24 @@ impl BotInstance { ); // Only respond to those with th ebelow User Roles + // [x] Need to add in Module as well let outstr = - format!("sadg Module is disabled : {:?}",a); + format!("sadg Module is disabled : {:?} {:?}",c.module.clone(),statuslvl); let params = ExecBodyParams { bot : Arc::clone(&bot), msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Some(Arc::clone(&act_clone)), }; + let params = Arc::new(RwLock::new(params)); + // When sending a BotMsgTypeNotif, send_botmsg does Roles related validation as required - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outstr ), params, @@ -461,11 +478,15 @@ impl BotInstance { let params = ExecBodyParams { bot : Arc::clone(&bot), msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Some(Arc::clone(&act_clone)), }; - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + + let params = Arc::new(RwLock::new(params)); + + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outstr.to_string() ), params.clone(), @@ -491,11 +512,15 @@ impl BotInstance { let params = ExecBodyParams { bot : Arc::clone(&bot), msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Some(Arc::clone(&act_clone)), }; - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + + let params = Arc::new(RwLock::new(params)); + + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outstr.to_string() ), params.clone(), @@ -512,11 +537,14 @@ impl BotInstance { Some(msg), ); + dbg!("Running botcommand"); + let a = Arc::clone(&bot); c.execute(ExecBodyParams { bot : a, msg : msg.clone() , - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Some(Arc::clone(&act_clone)), }).await; botlog::trace( @@ -564,7 +592,8 @@ impl BotInstance { l.execute(ExecBodyParams { bot : a, msg : msg.clone() , - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Some(Arc::clone(&act_clone)), } ).await; } diff --git a/src/core/botmodules.rs b/src/core/botmodules.rs index c104fdd..a56e5e9 100644 --- a/src/core/botmodules.rs +++ b/src/core/botmodules.rs @@ -25,14 +25,26 @@ const OF_CMD_CHANNEL:Channel = Channel(String::new()); use core::panic; +use std::borrow::Borrow; +use std::borrow::BorrowMut; use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; +use std::ops::DerefMut; use std::sync::Arc; use casual_logger::Log; +use chrono::DateTime; +// use chrono::Duration; +use chrono::Local; +use chrono::OutOfRangeError; use tokio::sync::RwLock; use async_trait::async_trait; +use tokio::task::JoinHandle; + +use tokio::time::Instant; +use tokio::time::{sleep, Duration}; use crate::core::bot_actions::actions_util; use crate::core::bot_actions::ExecBodyParams; @@ -44,6 +56,9 @@ use crate::core::bot_actions; use std::hash::{Hash, Hasher}; +use super::bot_actions::ActAR; +use super::bot_actions::ParamLevel; +use super::bot_actions::RoutineAR; use super::identity::ChatBadge; @@ -69,7 +84,6 @@ pub async fn init(mgr: Arc<ModulesManager>) { // 2. Add the BotAction to ModulesManager botc1.add_core_to_modmgr(Arc::clone(&mgr)).await; - // async fn cmd_enable(bot: BotAR, msg: PrivmsgMessage) { async fn cmd_enable(params : ExecBodyParams) { /* There should be additional validation checks @@ -179,10 +193,10 @@ pub async fn init(mgr: Arc<ModulesManager>) { let botclone = Arc::clone(&bot); let botlock = botclone.read().await; - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outmsg.to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; return; @@ -222,10 +236,10 @@ pub async fn init(mgr: Arc<ModulesManager>) { ChangeResult::Success(a) => format!("YAAY Success : {}",a), }; - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outmsg.to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; @@ -253,7 +267,6 @@ pub async fn init(mgr: Arc<ModulesManager>) { // 2. Add the BotAction to ModulesManager botc1.add_core_to_modmgr(Arc::clone(&mgr)).await; - // async fn cmd_disable(bot: BotAR, msg: PrivmsgMessage) { async fn cmd_disable(params : ExecBodyParams) { /* There should be additional validation checks @@ -361,10 +374,10 @@ pub async fn init(mgr: Arc<ModulesManager>) { // We should call a notification around here - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outmsg.to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; return; @@ -408,10 +421,10 @@ pub async fn init(mgr: Arc<ModulesManager>) { // We should call a notification around here - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outmsg.to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; } @@ -459,10 +472,11 @@ pub enum StatusType { Disabled(StatusLvl), } +#[derive(Debug)] pub enum BotAction { C(BotCommand), L(Listener), - R(Routine), + R(Arc<RwLock<Routine>>), } impl BotAction { @@ -494,6 +508,7 @@ pub struct BotCommand { impl BotCommand { pub async fn execute(&self, params : ExecBodyParams) { + // This is how BotCommand implements their Working exec_body (*self.exec_body)(params).await; } } @@ -521,6 +536,12 @@ impl BotActionTrait for BotCommand { } } +impl Debug for BotCommand { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?} {:?} {:?} {:?} {:?}", self.module, self.command, self.alias, self.help, self.required_roles) + } +} + pub struct Listener { pub module: BotModule, pub name: String, @@ -568,12 +589,1061 @@ impl BotActionTrait for Listener { } } +impl Debug for Listener { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?} {:?} {:?}", self.module, self.name, self.help) + } +} + +// #[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum RoutineAttr { + DelayedStart, + ScheduledStart(DateTime<Local>), // Scheduled Date (if any) after which, if not started, may trigger + LoopDuration(Duration), // How long to wait between iterations + LoopInfinitely, + RunOnce, + MaxTimeThreshold(DateTime<Local>), // DateTime after which, it will abort/cancel or stop + MaxIterations(i64), +} +/* + a Routine can be given different combinations of the above, but business logic may validate + these at Routine construction + + For example, a Routine could have the following characteristics + - DelayedStart (so it skips the first iteration) + - ScheduledStart(DateTime<Local>) - With a Start Date-Time for the first iteration to trigger + - LoopDuration(Duration) - How long to wait between iterations + + The above without any other thresholds would loop infinitely with the above characteristics + + Another example, + - LoopDuration(Duration) - How long to wait between iterations + - MaxTimeThreshold(DateTime<Local>) + - MaxIterations(i64) + + The above has thresholds , so if either are reached, it would abort/cancel or stop . Since there is no + ScheduledStart, the routine would have to be started manually elsewhere + + Another example , + - (no RoutineAttr) + + The above would only run once, and only when the Start() is called + +*/ + +// For some key statuses and in particular Stopping to Gracefully stop #[derive(Debug)] -pub struct Routine {} +pub enum RoutineSignal { + Stopping, // Gracefully Stopping + Stopped, // When cancelling or aborting, this also is represented by Stopped + Started, // After Routine Started + NotStarted, +} + +pub type ExecBodyParamsAr = Arc<RwLock<ExecBodyParams>>; + +pub struct Routine { + pub name : String , + pub module : BotModule , // from() can determine this if passed parents_params + pub channel : Channel , // Requiring some channel context + // exec_body: Arc<RwLock<bot_actions::actions_util::ExecBody>>, + // exec_body : fn(ExecBodyParams), + exec_body : fn(ExecBodyParamsAr), + // pub parent_params : ExecBodyParams , + // pub parent_params : Arc<RwLock<ExecBodyParams>> , + pub params : HashMap<ParamLevel,ExecBodyParamsAr>, + pub join_handle : Option<Arc<RwLock<JoinHandle<RoutineAR>>>> , + start_time : Option<DateTime<Local>> , + pub complete_iterations : i64 , + pub remaining_iterations : Option<i64> , + routine_attr : Vec<RoutineAttr> , + pub internal_signal : RoutineSignal , + pub self_routine_ar : Option<RoutineAR> , + pub self_act_ar : Option<ActAR> , +} + +// implement Debug manually witouth `exec_body` since you cant debug `ExecBody`. +impl Debug for Routine { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.name)?; + write!(f, "{:?}", self.module)?; + write!(f, "{:?}", self.channel)?; + write!(f, "{:?}", self.params)?; + write!(f, "{:?}", self.join_handle)?; + write!(f, "{:?}", self.start_time)?; + write!(f, "{:?}", self.complete_iterations)?; + write!(f, "{:?}", self.remaining_iterations)?; + write!(f, "{:?}", self.routine_attr)?; + write!(f, "{:?}", self.internal_signal)?; + write!(f, "{:?}", self.self_routine_ar)?; + write!(f, "{:?}", self.self_act_ar) + } +} + +impl Routine { + + + // // pub fn set + // // pub async fn refresh_self_ref(self) { + // pub async fn refresh_routine_internal(routine_ar : RoutineAR) + // { + + // /* + // Execute after the Routine is constructed + // - If not, a start() will also call this + + // */ + + // // 1. Update the self reference to itself + + // let mut mut_lock = routine_ar.write().await; + // mut_lock.self_routine_ar = Some(routine_ar.clone()); + + // // 2. Update the current self_act_ar + // mut_lock.self_act_ar = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone())))); + + + + + + // } + + + pub async fn validate_attr(routine_attr : &Vec<RoutineAttr>) + -> Result<String,String> + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + + /* + + GENERAL LOGIC : + [x] 1. Define RoutineAttr in a broad level that are known to be implented or are work in progress + [x] 2. Built in Logic will check these vectors, and return if Not Implemented + [x] 3. If Implemented , then there are additional internal validation based on combination done later + + */ + + + // [x] 1. Define RoutineAttr in a broad level that are known to be implented or are work in progress + + // adjust the below for those that are work in progress or that are implemented + // - This will allow other functions to validate that it is implemented + + // // WORK IN PROGRESS VECTOR - Vec<$RoutineAttr> + + // let wip_attr:Vec<RoutineAttr> = vec![ + // RoutineAttr::DelayedStart, + // RoutineAttr::ScheduledStart(chrono::offset::Local::now()), + // RoutineAttr::LoopDuration(Duration::from_secs(1)), + // RoutineAttr::LoopInfinitely, // Note : There's no added implementation for this + // RoutineAttr::RunOnce, + // RoutineAttr::MaxTimeThreshold(chrono::offset::Local::now()), + // RoutineAttr::MaxIterations(1), + + // ]; + + // let implemented_attr:Vec<RoutineAttr> = vec![ + // ]; + + + // [x] 2. Built in Logic will check these vectors, and return if Not Implemented + + // let mut unimplemented = routine_attr.iter() + // .filter(|x| { + // let inx = x; + // wip_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none() + // && implemented_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none() + // } + // ); + + let mut attribute_supported = false; + + for given_routine in routine_attr { + + // if !matches!(given_routine,RoutineAttr::DelayedStart) + // && !matches!(given_routine,RoutineAttr::ScheduledStart(_)) + // && !matches!(given_routine,RoutineAttr::LoopDuration(_)) + // && !matches!(given_routine,RoutineAttr::LoopInfinitely) + // && !matches!(given_routine,RoutineAttr::RunOnce) + // && !matches!(given_routine,RoutineAttr::MaxTimeThreshold(_)) + // && !matches!(given_routine,RoutineAttr::MaxIterations(_)) + if matches!(given_routine,RoutineAttr::DelayedStart) + || matches!(given_routine,RoutineAttr::ScheduledStart(_)) + || matches!(given_routine,RoutineAttr::LoopDuration(_)) + || matches!(given_routine,RoutineAttr::LoopInfinitely) + || matches!(given_routine,RoutineAttr::RunOnce) + || matches!(given_routine,RoutineAttr::MaxTimeThreshold(_)) + || matches!(given_routine,RoutineAttr::MaxIterations(_)) + + { + + attribute_supported = true; + + } + + }; + + + if !attribute_supported { + botlog::trace( + "[ERROR][Routine Feature NOT IMPLEMENTED]", + Some("Routine > Validate_attr()".to_string()), + None, + ); + + Log::flush(); + + botlog::trace( + format!( + "[ERROR][Routine Feature NOT IMPLEMENTED] > Problem Routine - {:?}" + ,routine_attr).as_str(), + Some("Routine > Validate_attr()".to_string()), + None, + ); + + Log::flush(); + + return Err("NOT IMPLEMENTED".to_string()); + } + + + + + + + // if unimplemented.next().is_some() { + + // botlog::trace( + // "[ERROR][Routine Feature NOT IMPLEMENTED]", + // Some("Routine > Validate_attr()".to_string()), + // None, + // ); + + // Log::flush(); + + // return Err("NOT IMPLEMENTED".to_string()); + // } + + + + + + // [x] 3. If Implemented , then there are additional internal validation based on combination done later to ERR + + // Below ensures a routine_attr containing LoopInfinitely does not contain conflicit attributes + if routine_attr.contains(&RoutineAttr::LoopInfinitely) && + ( routine_attr.contains(&RoutineAttr::RunOnce) + || routine_attr.iter().filter(|y| matches!(y,&&RoutineAttr::MaxIterations(_))).next().is_some() + || routine_attr.iter().filter(|y| matches!(y,&&RoutineAttr::MaxTimeThreshold(_))).next().is_some() + ) + { + return Err("Conflicting Routine Attributes".to_string()) + } + + // [x] if there is no RunOnce, there must be a LoopDuration + if !routine_attr.contains(&RoutineAttr::RunOnce) + && routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) ) + .next().is_none() + { + return Err("LoopDuration is required if not RunOnce".to_string()); + } + + + // [x] Err if DelayedStart but no LoopDuration + if routine_attr.contains(&RoutineAttr::DelayedStart) && + routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) ) + .next().is_none() + { + return Err("DelayedStart must include a LoopDuration".to_string()) + } + + + + + // [x] 4. If all other failure checks above works, ensure one more time that the atstribute is implemented + // - If not, routine NOT IMPLEMENTED error + + // if routine_attr.iter() + // .filter(|x| { + // let inx = x; + // wip_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none() + // || implemented_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none() + // }) + // .next() + // .is_none() + // { + + botlog::trace( + "[OK][Implemented & Validated]", + Some("Routine > Validate_attr()".to_string()), + None, + ); + + Log::flush(); + + Ok("Implemented & Validated".to_string()) + + + + } + + pub async fn validate_self_attr(self) + -> Result<String,String> + // [x] => 03.27 - COMPLETED + { + Routine::validate_attr(&self.routine_attr).await + } + + // Constructor + pub async fn from( + name : String , + module : BotModule , + channel : Channel, + routine_attr : Vec<RoutineAttr> , + // exec_body : Arc<RwLock<bot_actions::actions_util::ExecBody>> , + exec_body : fn(Arc<RwLock<ExecBodyParams>>) , + parent_params : Arc<RwLock<ExecBodyParams>> + ) -> Result< + Arc<RwLock<Routine>>, + String + > + // [x] => 03.27 - COMPLETED + { + + Routine::validate_attr(&routine_attr).await?; + + let mut params = HashMap::new(); + params.insert(ParamLevel::Parent,parent_params.clone()); + + let pparam_clone = parent_params.clone(); + let parent_guard = pparam_clone.read().await; + let curr_params = ExecBodyParams { + bot : parent_guard.bot.clone(), + msg : parent_guard.msg.clone(), + parent_act : parent_guard.curr_act.clone() , + curr_act : None, + }; + + params.insert(ParamLevel::Current,Arc::new(RwLock::new(curr_params))); + + let routine_ar = Arc::new(RwLock::new(Routine { + name , + module , + channel , + exec_body , + // parent_params , + params, + join_handle : None , + start_time : None , + complete_iterations : 0 , + remaining_iterations : None , + routine_attr : routine_attr , + internal_signal : RoutineSignal::NotStarted , + self_routine_ar : None , + self_act_ar : None , + })); + + let mut mut_lock = routine_ar.write().await; + mut_lock.self_routine_ar = Some(routine_ar.clone()); + + // 2. Update the current self_act_ar + mut_lock.self_act_ar = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone())))); + + mut_lock.params.get(&ParamLevel::Current).unwrap().write().await.curr_act = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone())))); + + Ok(routine_ar.clone()) + + // return Ok(Arc::new(RwLock::new(Routine { + // name , + // module , + // channel , + // exec_body , + // parent_params , + // join_handle : None , + // start_time : None , + // complete_iterations : 0 , + // remaining_iterations : None , + // routine_attr : routine_attr , + // internal_signal : RoutineSignal::NotStarted , + // self_routine_ar : None , + // self_act_ar : None , + // }))) ; + + + } + + pub async fn start( + trg_routine_ar : Arc<RwLock<Routine>> + // ) -> Result<String,String> + ) -> Result<Arc<RwLock<Routine>>,String> + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + + + // // [x] Prep by updating it's own self reference + // Routine::refresh_routine_internal(trg_routine_ar.clone()).await; + + // [x] Asyncio Spawn likely around here + // [x] & Assigns self.join_handle + + + /* + UMBRELLA ROUTINE LOGIC + 1. Create a loop scenario based on routine_attr such as RunOnce + 2. Run the loop depending on how the Routine is setup + + + START LOGIC : + 1. Ideally validate the routineattr that they're not a problem scenario (However, this should have been validated At Setup) + a. Extra helper validation function though would help in case the attributes were changes between setup + 2. Use these attributes only in Critical areas of the loop logic to determine changes in Loop logic + + */ + + + // Use the following to stop the function from going any further if not implemented + + Routine::validate_attr(&trg_routine_ar.read().await.routine_attr).await?; + // if !trg_routine_ar.read().await.routine_attr.contains(&RoutineAttr::RunOnce) { + + + // botlog::trace( + // format!( + // "[ERROR][Routine Feature NOT IMPLEMENTED] {} in {}", + // trg_routine_ar.read().await.name, + // trg_routine_ar.read().await.channel.0 + // ) + // .as_str(), + // Some(format!( + // "Routine > start() > (In Tokio Spawn) > {:?}", + // trg_routine_ar.read().await.module + // )), + // Some(&trg_routine_ar.read().await.parent_params.msg), + // ); + + // Log::flush(); + + // return Err("NOT IMPLEMENTED".to_string()) + + // } + + + let trg_routine_arout = Arc::clone(&trg_routine_ar); + + + botlog::trace( + "innerhelper() started", + Some(format!( + "Routine > start() > (In Tokio Spawn)", + )), + Some(&trg_routine_ar.read().await.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + Log::flush(); + + // Spawn the task + let join_handle = tokio::spawn(async move { + + botlog::trace( + ">> Within Spawn", + Some(format!( + "Routine > start() > (In Tokio Spawn)", + )), + Some(&trg_routine_ar.read().await.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + Log::flush(); + + // [x] If Scheduled Start or Delayed Start, Handle that first + + + fn duration_to_datetime(future_dt: DateTime<Local>) -> Result<Duration,OutOfRangeError> + { + (future_dt - chrono::offset::Local::now()).to_std() + } + + + + let delayduration = { + + + let lock = trg_routine_ar.read().await; + + let mut related_attrs = lock + .routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::DelayedStart) || matches!(x,&&RoutineAttr::ScheduledStart(_)) ); + + // match related_attrs.next() { + + // } + + async fn duration_from_attr(attr: &RoutineAttr,trg_routine_ar : RoutineAR) -> Option<Duration> { + // let duration_from_attr = async { + let lock = trg_routine_ar.read().await; + + match attr { + RoutineAttr::ScheduledStart(dt) => { + if let Ok(dur) = duration_to_datetime(*dt) { + Some(dur) + } else { None } + }, + RoutineAttr::DelayedStart => { + let mut loopdur_attr_iter = lock + .routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) ); + + if let Some(loopdur_attr) = loopdur_attr_iter.next() { + if let RoutineAttr::LoopDuration(dur) = loopdur_attr { + Some(*dur) + } else { None } + } else { None } + // None + }, + _ => { None } // Handle no other combination + } + } + + // The following is done twice just in case ScheduledStart and DelayedStart are defined + let delayduration01 = if let Some(attr) = related_attrs.next() { + duration_from_attr(attr, trg_routine_ar.clone()).await + } else { None }; + + let delayduration02 = if let Some(attr) = related_attrs.next() { + duration_from_attr(attr, trg_routine_ar.clone()).await + } else { None }; + + // if there is a 2nd related duration, pick the minimum, otherwise, pick the results of delayduration01 + if delayduration02.is_some() { + Some(Duration::min(delayduration01.unwrap(),delayduration02.unwrap())) + } else { delayduration01 } + + }; + + + botlog::trace( + format!( + "[TRACE][Routine Processing] {} in {} > Delay Duration - {:?} ", + trg_routine_ar.read().await.name, + trg_routine_ar.read().await.channel.0 , + delayduration , + ) + .as_str(), + Some(format!( + "Routine > start() > (In Tokio Spawn) > {:?}", + trg_routine_ar.read().await.module + )), + Some(&trg_routine_ar.read().await.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + if let Some(dur) = delayduration { + sleep(dur).await; + } + + + { // [x] Loop Initialization - Prior to Loop that calls Custom Routine Execution Body + + + let mut a = trg_routine_ar.write().await; + a.start_time = Some(chrono::offset::Local::now()); + + if let Some(&RoutineAttr::MaxIterations(iternum)) = + a.routine_attr.iter() + .filter(|x| matches!(x,RoutineAttr::MaxIterations(_))) + .next() + { + a.remaining_iterations = Some(iternum); + } + drop(a); + } + + + loop { // [x] Routine loop + + + // [x] execution body + // trg_routine_ar.read().await.loopbody().await; + { + + let trg_routine_ar_clone = trg_routine_ar.clone(); + // trg_routine_ar.write().await.loopbody().await; + let loopbodyspawn = tokio::task::spawn_blocking( move || { + // let routine_ar_clone = Arc::clone(&trg_routine_ar); + // trg_routine_ar + // trg_routine_ar_clone.blocking_write().loopbody(); + let guard1 = trg_routine_ar_clone.blocking_read(); + guard1.loopbody(); + drop(guard1); + }); + + loopbodyspawn.await.unwrap(); + + + // trg_routine_ar.write().await.loopbody() + + } + + + { // [x] End of Loop iteration + let mut a = trg_routine_ar.write().await; + + // [x] Check if Gracefully Stopping Signal was sent + if matches!(a.internal_signal,RoutineSignal::Stopping) { + a.internal_signal = RoutineSignal::Stopped; + break ; + } + + // [x] Check and adjust iterations + a.complete_iterations += 1; + if let Some(i) = a.remaining_iterations { + if i > 0 { a.remaining_iterations = Some(i-1) ; } + else { break ; } // if remaining iterations is 0, exit + } + drop(a); + + } + + // [x] End of Loop Validation + // These generally may include routine_attr related checks to , for example, break out of the loop + + if trg_routine_ar.read().await.routine_attr.contains(&RoutineAttr::RunOnce) { + if trg_routine_ar.read().await.complete_iterations > 0 { break; } + } + + // return if max time has passed + if let Some(&RoutineAttr::MaxTimeThreshold(dt)) = trg_routine_ar.read().await.routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::MaxTimeThreshold(_)) ) + .next() { + if chrono::offset::Local::now() > dt { break; } + } + + // [x] Checks for Loop duration to sleep + if let Some(&RoutineAttr::LoopDuration(dur)) = trg_routine_ar.read().await.routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) ) + .next() + { + sleep(dur).await; + }; + + + } + + dbg!("SUCCESS! Routinecompleted"); + + + botlog::trace( + format!( + "[TRACE][Routine Completed] {} in {}", + trg_routine_ar.read().await.name, + trg_routine_ar.read().await.channel.0 + ) + .as_str(), + Some(format!( + "Routine > start() > (In Tokio Spawn) > {:?}", + trg_routine_ar.read().await.module + )), + Some(&trg_routine_ar.read().await.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + botlog::trace( + format!( + "[TRACE][Routine Completed][Routine Header Test] {} in {} > Completed Iterations : {}", + trg_routine_ar.read().await.name, + trg_routine_ar.read().await.channel.0 , + trg_routine_ar.read().await.complete_iterations, + ) + .as_str(), + Some(format!( + "Routine > start() > (In Tokio Spawn) > {:?}", + trg_routine_ar.read().await.module + )), + Some(&trg_routine_ar.read().await.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + Log::flush(); + trg_routine_ar + }); + + + { // Recommendation to ensure a clean update is to use one write() lock that was awaited + // - We can isolate the write lock by ensuring it's in it's own block + + // => 03.30 - involved with Problem Deadlock Current Readers = 0 + let mut lock = trg_routine_arout.write().await; + lock.join_handle = Some(Arc::new(RwLock::new(join_handle))); + lock.internal_signal = RoutineSignal::Started; + drop(lock); // => 03.30 - added this to try to address Deadlock issue + + } + // trg_routine_arout.write().await.internal_signal = RoutineSignal::Started; + + return Ok(trg_routine_arout); + + } + + // pub async fn execute(routine:&Routine, params : ExecBodyParams) { + // (routine.exec_body)(params).await; + // } + + + // fn (){ + + // } + + // async fn loopbody(&mut self) + // fn loopbody(&mut self) + fn loopbody(&self) + // [x] => 03.27 - COMPLETED + { + botlog::trace( + "loopbody() started", + Some(format!( + "Routine > start() > (During Tokio Spawn) > Execution body", + )), + None, + ); + + Log::flush(); + + // => 03.30 - involved with Problem Deadlock Current Readers = 1 + // - Below self appears to be a Arc<RwLock<&mut Routine>> + let self_ar = Arc::new(RwLock::new(self)); + + + // => 03.31 - Temporarily removing the current_act / parrent_act functionality + /* + { + // let mutlock = self_ar.read().await; + let mutlock = self_ar.blocking_read(); + + mutlock.parent_params.blocking_write().parent_act = Some(mutlock.parent_params.blocking_read().curr_act.clone()); + mutlock.parent_params.blocking_write().curr_act = mutlock.self_act_ar.to_owned().unwrap(); + drop(mutlock); // => 03.30 - Added to address Deadlock issue + } + */ + + dbg!("Core > Before Guards"); + + // `self_ar` is the routine you want to read in the execbody i think, but its used to call that execbody. + // So execbody waits for itself to finish. + // (self_ar.read().await.exec_body)( + // self_ar.read().await.parent_params.clone() + // ).await; + + // let parent_params = { + // let guard1 = self_ar.blocking_read(); + + // let parent_params = guard1.params.get(&ParamLevel::Parent).unwrap().clone(); + // drop(guard1); + // parent_params + // }; + let curr_params = { + let guard1 = self_ar.blocking_read(); + + let curr_params = guard1.params.get(&ParamLevel::Current).unwrap().clone(); + drop(guard1); + curr_params + }; + + + dbg!("Core > Guarding and will Execute Child Execution Body"); + dbg!(">> BotActionAR - RwLock from botmodules.rs::929:46 - current_readers = 0 "); + dbg!(">> RoutineAR - RwLock from botmodules.rs::1261:32 - current_readers = Not Listed"); + dbg!(">> BotInstanceAR - RwLock from botinstance.rs::150:28 - current_readers = 3 "); + + // { + // // The below starts the Read Lock that conlicts + // let guard2 = self_ar.read().await; + + // dbg!("Core > Guarded & Executing Child Execution Body"); + // dbg!("!! [x] Document "); + // dbg!(">> BotActionAR - RwLock from botmodules.rs::929:46 - current_readers = 0 "); + // dbg!(">> RoutineAR - RwLock from botmodules.rs::1261:32 - current_readers = 1"); + // dbg!(">> BotInstanceAR - RwLock from botinstance.rs::150:28 - current_readers = 2 "); + // // this way seems to be having issues for Routine > Loopbody + // // in particular, because by this point we have a ReadGuard on Routine + // // somehow the read guard is conflicting with a read guard in the underlying + // // fn? + // (guard2.exec_body)(parent_params).await; + // drop(guard2); + + // } + + { + + let guard2 = self_ar.blocking_read(); + let exec_body_ar = guard2.exec_body.clone(); + drop(guard2); + // let guard3 = exec_body_ar.read().await; + // (guard3)(parent_params).await; + // drop(guard3); + + // [ ] May need to do a blocking async of this? + exec_body_ar(curr_params); + + + } + + + + // (self_ar.read().await.exec_body)( + // parent_params + // ).await; + + dbg!("Core > After Execution Body is completed"); + // (self.exec_body)( + // self.parent_params.clone() + // ).await; + } + + pub async fn stop(&mut self) -> Result<String,String> + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + + + let self_rw = Arc::new(RwLock::new(self)); + + { + let mut self_lock = self_rw.write().await; + self_lock.internal_signal = RoutineSignal::Stopping; + drop(self_lock); + } + + + let self_lock = self_rw.read().await; + + botlog::trace( + format!( + "[ROUTINE][Sent Gracefully Stop Signal] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > stop() > {:?}", + self_lock.module + )), + Some(&self_lock.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + Log::flush(); + + Ok("Sent Gracefully Stop Signal".to_string()) + + // botlog::trace( + // format!( + // "[ERROR][Routine NOT IMPLEMENTED] {} in {}", + // self_lock.name,self_lock.channel.0 + // ) + // .as_str(), + // Some(format!( + // "Routine > start() > (In Tokio Spawn) > {:?}", + // self_lock.module + // )), + // Some(&self_lock.parent_params.msg), + // ); + + // Log::flush(); + + // Err("NOT IMPLEMENTED".to_string()) + } + + pub async fn cancel(&mut self) -> Result<String,String> + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + + // [ ] Likely calls abort() + // Related : + // https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html#method.abort + + + let self_rw = Arc::new(RwLock::new(self)); + let self_lock = self_rw.read().await; + + + match &self_lock.join_handle { + None => return Err("No Join Handle on the Routine to Cancel".to_string()), + Some(a) => { + a.read().await.abort(); + { + let mut lock_mut = self_rw.write().await; + lock_mut.internal_signal = RoutineSignal::Stopped; + drop(lock_mut); + + } + } + } + + + botlog::trace( + format!( + "[ROUTINE][Cancelled Routine] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > cancel() > {:?}", + self_lock.module + )), + Some(&self_lock.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + Log::flush(); + Ok("Cancelled Successfully".to_string()) + + + // botlog::trace( + // format!( + // "[ERROR][Routine NOT IMPLEMENTED] {} in {}", + // self_lock.name,self_lock.channel.0 + // ) + // .as_str(), + // Some(format!( + // "Routine > start() > (In Tokio Spawn) > {:?}", + // self_lock.module + // )), + // Some(&self_lock.parent_params.msg), + // ); + + // Log::flush(); + // Err("NOT IMPLEMENTED".to_string()) + } + + pub async fn restart( + // &mut self, + self, + force : bool + ) -> Result<String,String> + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + // force flag aborts the routine immediately (like cancel()) + + + let self_rw = Arc::new(RwLock::new(self)); + + if force + { + let mut self_lock = self_rw.write().await; + self_lock.cancel().await?; + drop(self_lock); + } else { + let mut self_lock = self_rw.write().await; + self_lock.stop().await?; + drop(self_lock); + } + + Routine::start(self_rw.clone()).await?; + + let self_lock = self_rw.read().await; + + botlog::trace( + format!( + "[ROUTINE][Restarted Routine] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > restart() > {:?}", + self_lock.module + )), + Some(&self_lock.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + Log::flush(); + Ok("Restarted successfully".to_string()) + } + + pub async fn change_channel( + &mut self, + channel : Channel + ) -> Result<String,String> + // [ ] => 03.28 - REVIEW FOR COMPLETION + { + // [x] Think Ideally it should try to + // change the target channel of the + // internal process too if possible? + + self.channel = channel; + + + let self_rw = Arc::new(RwLock::new(self)); + + let self_lock = self_rw.read().await; + + botlog::trace( + format!( + "[ROUTINE][Change Channel] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > restart() > {:?}", + self_lock.module + )), + Some(&self_lock.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + Log::flush(); + + // Err("NOT IMPLEMENTED".to_string()) + Ok("Changed Successfully".to_string()) + } + + + pub async fn set_routine_attributes( + &mut self, + routine_attr : Vec<RoutineAttr> + ) -> Result<String,String> + // [ ] => 03.27 - WIP - NOT IMPLEMENTED + { + // This is way to custom set attributes first + // They will Be Validated before being applied + // IDEALLY the routine also be restarted afterwards externally + + Routine::validate_attr(&routine_attr).await?; + + + let self_rw = Arc::new(RwLock::new(self)); + { + let mut self_lock = self_rw.write().await; + self_lock.routine_attr = routine_attr; + drop(self_lock); + } + + // let self_rw = Arc::new(RwLock::new(self)); + + let self_lock = self_rw.read().await; + + botlog::trace( + format!( + "[ROUTINE][Set Routine Attributes] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > restart() > {:?}", + self_lock.module + )), + Some(&self_lock.params.get(&ParamLevel::Parent).unwrap().read().await.msg), + ); + + Log::flush(); + + + Ok("Changed Successfully".to_string()) + } + + +} + + + type StatusdbEntry = (ModGroup, Vec<StatusType>); type ModuleActions = Vec<Arc<RwLock<BotAction>>>; +#[derive(Debug)] pub struct ModulesManager { statusdb: Arc<RwLock<HashMap<BotModule, StatusdbEntry>>>, pub botactions: Arc<RwLock<HashMap<BotModule, ModuleActions>>>, diff --git a/src/core/chat.rs b/src/core/chat.rs index bb938cc..ca70c0a 100644 --- a/src/core/chat.rs +++ b/src/core/chat.rs @@ -1,8 +1,13 @@ use std::collections::HashMap; +use std::i8; use std::sync::Arc; +use std::num::{self, Saturating, Wrapping}; +use std::time::Instant; -use tokio::sync::Mutex; +use chrono::NaiveDateTime; +use tokio::sync::{Mutex, RwLock}; +use tokio::task::yield_now; use twitch_irc::login::StaticLoginCredentials; use twitch_irc::message::PrivmsgMessage; use twitch_irc::transport::tcp::{TCPTransport, TLS}; @@ -27,16 +32,54 @@ use super::identity; use async_recursion::async_recursion; -#[derive(Clone)] +use async_channel::{self, bounded, Receiver, Sender}; + + + +// pub mod chat_util { + +// use super::*; + +// use std::boxed::Box; +// use std::future::Future; +// use std::pin::Pin; + +// // pub type MsgFuture = Box< +// // dyn Fn(BotMsgType,ExecBodyParams) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync, +// // >; + +// // pub fn msgbox<T>(f: fn(BotMsgType,ExecBodyParams) -> T) -> MsgFuture +// // where +// // T: Future<Output = ()> + Send + 'static, +// // { +// // Box::new(move |a,b| Box::pin(f(a,b))) +// // } + +// pub type MsgFuture = Pin<Box<dyn Future<Output =()> + Send + 'static>>; + +// } + + + +#[derive(Clone, Debug)] pub struct Chat { - pub ratelimiters: Arc<Mutex<HashMap<Channel, RateLimiter>>>, // used to limit messages sent per channel + // pub ratelimiters: Arc<Mutex<HashMap<Channel, RateLimiter>>>, // used to limit messages sent per channel + pub ratelimiters: Arc<RwLock<HashMap<Channel, + Arc<RwLock<RateLimiter>>>>>, // used to limit messages sent per channel pub client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, + // outqueue : Arc<RwLock<Vec<(BotMsgType,ExecBodyParams)>>> + outqueue : (Sender<(BotMsgType,ExecBodyParams)>, + Arc<RwLock<Receiver<(BotMsgType,ExecBodyParams)>>>), + // https://doc.rust-lang.org/std/num/struct.Saturating.html + // spaceiter : Arc<Mutex<Wrapping<i64>>>, + spaceiter : Arc<Mutex<bool>>, } #[derive(Clone,Debug)] -pub enum BotMsgType<'a> { - SayInReplyTo(&'a PrivmsgMessage,String), +pub enum BotMsgType { + // SayInReplyTo(&'a PrivmsgMessage,String), + SayInReplyTo(Arc<PrivmsgMessage>,String), Say(String,String), Notif(String), // For Bot Sent Notifications } @@ -44,22 +87,139 @@ pub enum BotMsgType<'a> { impl Chat { pub fn init( - ratelimiters: HashMap<Channel, RateLimiter>, + ratelimiters: HashMap<Channel, Arc<RwLock<RateLimiter>>>, client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, - ) -> Chat { - Chat { - ratelimiters: Arc::new(Mutex::new(ratelimiters)), + ) -> Arc<RwLock<Chat>> { + + let (s,r) = bounded(100); + + let r = Arc::new(RwLock::new(r)); + + let spaceiter = Arc::new(Mutex::new(true)); + + + let chat = Chat { + ratelimiters: Arc::new(RwLock::new(ratelimiters)), client, - } + // outqueue : Arc::new(RwLock::new(vec![])), + outqueue : (s,r.clone()), + spaceiter , + }; + + let chat_ar = Arc::new(RwLock::new(chat)); + + let r_clone = r.clone(); + + // let chat_ar = Arc::new(RwLock::new(chat_clone)); + let chat_ar_clone = chat_ar.clone(); + + // Spawning a Task that will process the Chat.outqueue + tokio::spawn(async move { + // loop { + // // tokio::spawn(async move { + // // let r_clone = r_clone.clone(); + + // let r_clone = r_clone.clone(); + // let guard1 = r_clone.read().await; + // match guard1.recv().await { + // Ok(a) => { + // // [ ] This should spawn it's own ideally, so it does not lock other messages + // // chat_clone.send_botmsg(a.0, Arc::new(RwLock::new(a.1))).await + // let params_ar = Arc::new(RwLock::new(a.1)); + // let chat_clone = chat_clone.clone(); + // dbg!(chrono::offset::Local::now(),"Message Spawned > Starting"); + // tokio::spawn( async move { + // chat_clone.send_botmsg_inner(a.0, params_ar).await + // }); + // dbg!(chrono::offset::Local::now(),"Message Spawned > Completed"); + // } , + // Err(err) => { + // dbg!("ISSUE processing Receiver",err); + // sleep(Duration::from_millis(10)).await; + // } + // } + // // }); + // } + + let r_cc = r_clone.clone(); + + // loop { + // Spawn x helper threads to process messages in the outqueue + for _ in 1..5 { + + dbg!("Chat queue Helper thread generated"); + let r_ccc = r_cc.clone(); + let chat_c = chat_ar.clone(); + tokio::spawn(async move { + let r_cc = r_ccc.clone(); + // let chat_cc = chat_c.clone(); + let guard1 = r_cc.read().await; + + while let Ok(a) = guard1.recv().await { + // let r_cc = r_ccc.clone(); + let chat_cc = chat_c.clone(); + // let guard2 = r_cc.read().await; + // Ok(a) => { + // [ ] This should spawn it's own ideally, so it does not lock other messages + // chat_clone.send_botmsg(a.0, Arc::new(RwLock::new(a.1))).await + let params_ar = Arc::new(RwLock::new(a.1)); + // let chat_clone = chat_cc.clone(); + dbg!(chrono::offset::Local::now(),"Message Spawned > Starting"); + tokio::spawn( async move { + dbg!("Message from Chat Queue > Sending"); + chat_cc.read().await.send_botmsg_inner(a.0, params_ar).await; + dbg!("Message from Chat Queue > Sent"); + }); + dbg!(chrono::offset::Local::now(),"Message Spawned > Completed"); + // } , + // Err(err) => { + // dbg!("ISSUE processing Receiver",err); + // sleep(Duration::from_millis(10)).await; + // } + } + dbg!("ISSUE : Helper thread observed an Err"); + + // match guard1.recv().await { + // Ok(a) => { + // // [ ] This should spawn it's own ideally, so it does not lock other messages + // // chat_clone.send_botmsg(a.0, Arc::new(RwLock::new(a.1))).await + // let params_ar = Arc::new(RwLock::new(a.1)); + // // let chat_clone = chat_cc.clone(); + // dbg!(chrono::offset::Local::now(),"Message Spawned > Starting"); + // tokio::spawn( async move { + // chat_cc.read().await.send_botmsg_inner(a.0, params_ar).await + // }); + // dbg!(chrono::offset::Local::now(),"Message Spawned > Completed"); + // } , + // Err(err) => { + // dbg!("ISSUE processing Receiver",err); + // sleep(Duration::from_millis(10)).await; + // } + // } + + } + ); + yield_now().await; + + } + + }); + + // chat + chat_ar_clone } pub async fn init_channel(&mut self, chnl: Channel) { - let n = RateLimiter::new(); - self.ratelimiters.lock().await.insert(chnl, n); + let n = Arc::new(RwLock::new(RateLimiter::new())); + + let mut rguard = self.ratelimiters.write().await; + rguard.insert(chnl, n); + drop(rguard); } #[async_recursion] - pub async fn send_botmsg(&self, msginput: BotMsgType<'async_recursion>, params : ExecBodyParams) { + // pub async fn send_botmsg(&self, msginput: BotMsgType<'async_recursion>, params : ExecBodyParams) { + pub async fn send_botmsg_inner(&self, msginput: BotMsgType, params : Arc<RwLock<ExecBodyParams>>) { @@ -72,15 +232,17 @@ impl Chat { */ + let chat_ar = Arc::new(RwLock::new((*self).clone())); + botlog::trace( format!("send_bot_msg params : {:?}",msginput).as_str(), Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), + Some(¶ms.read().await.msg), ); Log::flush(); - let (channel_login,mut outmsg) = match msginput.clone() { + let (channel_login,outmsg) = match msginput.clone() { BotMsgType::SayInReplyTo(msg, outmsg) => { (msg.channel_login.clone(),outmsg) }, @@ -88,7 +250,7 @@ impl Chat { (a.clone(),b.clone()) }, BotMsgType::Notif(outmsg) => { - (params.msg.channel_login.clone(),outmsg) + (params.read().await.msg.channel_login.clone(),outmsg) } }; @@ -98,21 +260,22 @@ impl Chat { botlog::trace( "BEFORE parent_module call", Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), + Some(¶ms.read().await.msg), ); - let parent_module = params.get_parent_module().await; + let parent_module = params.read().await.get_module().await; let params_clone = params.clone(); - let botclone = Arc::clone(¶ms_clone.bot); + let botclone = Arc::clone(¶ms_clone.read().await.bot); let botlock = botclone.read().await; let modmgr = Arc::clone(&botlock.botmodules); let modstatus = (*modmgr).modstatus( - parent_module.clone().expect("ERROR - Expected a module"), + // parent_module.clone().expect("ERROR - Expected a module"), + parent_module.clone(), Channel(channel_login.clone()) ).await; - if !params.bot.read().await.bot_channels.contains(&Channel(channel_login.clone())) { + if !params.read().await.bot.read().await.bot_channels.contains(&Channel(channel_login.clone())) { botlog::warn( &format!("A message attempted to send for a Non-Joined Channel : {}",channel_login.clone()), Some("Chat > send_botmsg".to_string()), @@ -121,7 +284,7 @@ impl Chat { if let BotMsgType::SayInReplyTo(_prvmsg,_outmsg) = msginput { - self.send_botmsg(BotMsgType::Notif( + chat_ar.read().await.send_botmsg(BotMsgType::Notif( "uuh Bot can't send to a channel it isn't joined".to_string(), ), params).await; @@ -146,7 +309,7 @@ impl Chat { botlog::trace( format!("BEFORE modstatus check : modstatus = {:?}",modstatus).as_str(), Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), + Some(¶ms.read().await.msg), ); @@ -160,7 +323,7 @@ impl Chat { botlog::trace( "BEFORE msginput check", Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), + Some(¶ms.read().await.msg), ); Log::flush(); @@ -172,7 +335,7 @@ impl Chat { botlog::trace( "BEFORE potential Async recursion", Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.clone().msg), + Some(¶ms.read().await.clone().msg), ); Log::flush(); @@ -180,7 +343,8 @@ impl Chat { self.send_botmsg(BotMsgType::Notif( format!("uuh {:?} is disabled on {} : {:?}", - parent_module.clone().unwrap(), + // parent_module.clone().unwrap(), + parent_module.clone(), channel_login.clone(), lvl ), @@ -191,7 +355,7 @@ impl Chat { botlog::trace( "AFTER potential Async recursion", Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), + Some(¶ms.read().await.msg), ); @@ -228,22 +392,26 @@ impl Chat { // let params_clone = params.clone(); - let botclone = Arc::clone(¶ms.bot); + dbg!("Send_botmsg_inner > Checking user roles first"); + + let botclone = Arc::clone(¶ms.read().await.bot); let botlock = botclone.read().await; let id = botlock.get_identity(); let id = Arc::clone(&id); let idlock = id.read().await; // <-- [x] 03.24 - seems to work let user_roles = idlock.getspecialuserroles( - params.get_sender(), + params.read().await.get_sender(), Some(Channel(channel_login.clone())) ).await; botlog::trace( format!("BEFORE user roles check check : userroles = {:?}",user_roles).as_str(), Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), + Some(¶ms.read().await.msg), ); + dbg!("Send_botmsg_inner > Checking user roles first > Prepared to check for roles"); + Log::flush(); // [x] If user has any of the following target roles, they will be allowed - otherwise, they will not be allowed to send @@ -286,6 +454,8 @@ impl Chat { } + dbg!("Send_botmsg_inner > Roles Checked"); + /* At this stage from the above Validations : msginput would be : @@ -310,8 +480,11 @@ impl Chat { */ - let rl = Arc::clone(&self.ratelimiters); - let mut rllock = rl.lock().await; + // let rl = chat_ar.read().await.ratelimiters.clone(); + // // let rl = Arc::clone(&chat_ar.clone().read().await.ratelimiters); + // // let mut rllock = rl.read().await; + // let rllock = rl.write().await; + // // let mut hmap = (*rllock).clone(); botlog::debug( &format!("Ratelimiter being checked for channel : {}",channel_login.clone()), @@ -319,141 +492,600 @@ impl Chat { None, ); - let contextratelimiter = rllock - // .get_mut() - .get_mut(&Channel(channel_login.to_lowercase().clone())) - .expect("ERROR: Issue with Rate limiters"); + // let rllock_clone = Arc::new(RwLock::new((*rllock).clone())); + // let guard = rllock_clone.read().await; + // let contextratelimiter = (*guard).clone() + // // .get_mut() + // .get_mut(&Channel(channel_login.to_lowercase().clone())) + // .expect("ERROR: Issue with Rate limiters"); + // // drop(guard); - // Continue to check the limiter and sleep if required if the minimum is not reached - while let ratelimiter::LimiterResp::Sleep(sleeptime) = contextratelimiter.check_limiter() { - sleep(Duration::from_secs_f64(sleeptime)).await; - } + // let a = rllock.get_mut(&Channel(channel_login.to_lowercase().clone())).expect("ERROR: Issue with Rate limiters"); - match contextratelimiter.check_limiter() { - ratelimiter::LimiterResp::Allow => { - let maxblanks = rand::thread_rng().gen_range(1..=20); + let ratelimiters = chat_ar.read().await.ratelimiters.clone(); - for _i in 1..maxblanks { - let blankspace: &str = ""; - outmsg.push_str(blankspace); - } + // let contextratelimiter = Arc::new(RwLock::new(contextratelimiter)); - match msginput.clone() { - BotMsgType::SayInReplyTo(msg, _) => { - self.client.say_in_reply_to(msg, outmsg).await.unwrap(); - }, - BotMsgType::Say(a, _) => { - self.client.say(a, outmsg).await.unwrap(); - } - BotMsgType::Notif(outmsg) => { - self.client.say_in_reply_to(¶ms.msg, outmsg).await.unwrap(); - } - } + // // Continue to check the limiter and sleep if required if the minimum is not reached + // while let ratelimiter::LimiterResp::Sleep(sleeptime) = contextratelimiter.check_limiter(outmsg.clone()) { + // sleep(Duration::from_secs_f64(sleeptime)).await; + // } + + // match contextratelimiter.check_limiter(outmsg.clone()) { + // ratelimiter::LimiterResp::Allow => { + // // let maxblanks = rand::thread_rng().gen_range(1..=20); + + // // let mut blanks_to_add_lock = self.spaceiter.lock().await; + // // (*blanks_to_add_lock).0 += 1; + + + // // for _i in 1..(*blanks_to_add_lock).0 { + // // // let blankspace: &str = " "; + // // // let blankspace: &str = " ."; + // // let blankspace: &str = "."; + // // outmsg.push_str(blankspace); + // // } + + // // drop(blanks_to_add_lock); + + // dbg!("[ ] PROBLEM AREA - If notificaiton triggered, need this checked"); + // dbg!(outmsg.clone()); + + // botlog::trace( + // &format!("Out Message TO {} >> {}",channel_login.clone(),outmsg.clone()), + // Some("Chat > send_botmsg".to_string()), + // None, + // ); + + // match msginput.clone() { + // BotMsgType::SayInReplyTo(msg, _) => { + // self.client.say_in_reply_to(&(*msg), outmsg.clone()).await.unwrap(); + // }, + // BotMsgType::Say(a, _) => { + // self.client.say(a, outmsg.clone()).await.unwrap(); + // } + // BotMsgType::Notif(outmsg) => { + // self.client.say_in_reply_to(¶ms.read().await.msg, outmsg.clone()).await.unwrap(); + // } + // } - contextratelimiter.increment_counter(); + // // contextratelimiter.increment_counter(); + // contextratelimiter.sent_msg(outmsg.clone()); - let logstr = format!( - "(#{}) > {} ; contextratelimiter : {:?}", - channel_login.clone(), "rate limit counter increase", contextratelimiter - ); + // let logstr = format!( + // "(#{}) > {} ; contextratelimiter : {:?}", + // channel_login.clone(), "rate limit counter increase", contextratelimiter + // ); - if let BotMsgType::SayInReplyTo(msg,_ ) = msginput { - botlog::trace( - logstr.as_str(), - Some("Chat > send_botmsg".to_string()), - Some(msg), - ); + // if let BotMsgType::SayInReplyTo(msg,_ ) = msginput { + // botlog::trace( + // logstr.as_str(), + // Some("Chat > send_botmsg".to_string()), + // Some(&(*msg)), + // ); + // } else { + // botlog::trace( + // logstr.as_str(), + // Some("Chat > send_botmsg".to_string()), + // None, + // ); + // } + + + + // } + // ratelimiter::LimiterResp::Skip => { + // // (); // do nothing otherwise + // } + // ratelimiter::LimiterResp::Sleep(err_passed_sleep_time) => { + // dbg!(err_passed_sleep_time); + // // panic!("ISSUE : sleep was already awaited - Should not happen?"); + // botlog::warn( + // format!( + // "Warning : RATE LIMITERS returned Sleep Unexpectedly . This should not occur often . Rate Limiter passed Sleep({})", + // err_passed_sleep_time + // ).as_str(), + // Some("Chat > send_botmsg".to_string()), + // None, + // ); + // } + // } + + + // => [x] 04.02 - Q. Why are we spawning here? => A. Believe this was to try to ensure not blocked + tokio::spawn(async move { + + dbg!("Send_botmsg_inner > Within Spawn"); + + let chat_clone = chat_ar.clone(); + let ratelimiters_clone = ratelimiters.clone(); + let rguard = ratelimiters_clone.read().await; + let mut rclone = (*rguard).clone(); + drop(rguard); + let contextratelimiter = (rclone + .get_mut(&Channel(channel_login.to_lowercase().clone())) + .expect("ERROR: Issue with Rate limiters")).clone(); + + // let mut contextlimiter_guard = contextratelimiter.write().await; + + // Continue to check the limiter and sleep if required if the minimum is not reached + + + + loop { + // Check first if need to sleep, then sleep if required + dbg!("Send_botmsg_inner > Within Spawn > Before a Write Guard"); + let mut crl_lock = contextratelimiter.write().await; + let sleeptime = if let ratelimiter::LimiterResp::Sleep(sleeptime) = crl_lock.check_limiter(outmsg.clone()).await { + // dbg!("Send_botmsg_inner > Within Spawn > Drop a Write Guard"); + // drop(crl_lock); + // dbg!("determined to sleep > Sleeping now - ",chrono::offset::Local::now()); + // sleep(Duration::from_secs_f64(sleeptime)).await; + sleeptime + } else { 0.0 }; + + + drop(crl_lock); + + if sleeptime > 0.0 { + dbg!("determined to sleep > Sleeping now - ",chrono::offset::Local::now()); + sleep(Duration::from_secs_f64(sleeptime)).await; + } + + // if told to sleep again, check again before looping for another check + dbg!("Send_botmsg_inner > Within Spawn > Before a Write Guard"); + let mut crl_lock = contextratelimiter.write().await; + if let ratelimiter::LimiterResp::Sleep(_) = crl_lock.check_limiter(outmsg.clone()).await { + // do nothing here } else { + break; // break out if no longer sleep LimiterResp + }; + dbg!("Send_botmsg_inner > Within Spawn > Drop a Write Guard"); + drop(crl_lock); + } + + /* + // Original Logic + + dbg!("Send_botmsg_inner > Within Spawn > Before a Write Guard"); + let mut crl_lock = contextratelimiter.write().await; + // dbg!("Send_botmsg_inner > Within Spawn > Reading Guard / Before a Write Guard"); + // let mut crl_lock = contextratelimiter.read().await; + // let rslt = crl_lock.check_limiter(outmsg.clone()).await; + + dbg!("before checklimiter sleep",&crl_lock); + // Assign the message here to rate limiter - or just during + + // let a = *(contextratelimiter.read().await); + // let b = Arc::new(a); + // let c = b.check_limiter(inmsg).await; + + while let ratelimiter::LimiterResp::Sleep(sleeptime) = crl_lock.check_limiter(outmsg.clone()).await { + // while let ratelimiter::LimiterResp::Sleep(sleeptime) = crl_lock.check_limiter(outmsg.clone()).await { + + dbg!("within checklimiter sleep",&crl_lock); + + // dbg!(chrono::offset::Local::now(),"Message Spawned > Completed"); + + // drop(crl_lock); //dropping the lock first + dbg!("sleep loop start",chrono::offset::Local::now(), sleeptime); + yield_now().await; + dbg!("yield in just after loop start",chrono::offset::Local::now()); + sleep(Duration::from_secs_f64(sleeptime)).await; + + } + */ + + let mut crl_lock = contextratelimiter.write().await; + dbg!("after checklimiter sleep",&crl_lock); + match crl_lock.clone().check_limiter(outmsg.clone()).await { + ratelimiter::LimiterResp::Allow => { + // let maxblanks = rand::thread_rng().gen_range(1..=20); + + let chat_guard = chat_clone.read().await; + + let mut blanks_to_add_lock = chat_guard.spaceiter.lock().await; + + dbg!("Before blanks to add adjusted",*blanks_to_add_lock); + + // *blanks_to_add_lock += 1; + *blanks_to_add_lock = if *blanks_to_add_lock { false } else { true }; + + dbg!("After blanks to add adjusted",*blanks_to_add_lock); + + + let mut outmsg = outmsg; + // for i in 0..*blanks_to_add_lock+1 { + // dbg!("Processing blank spaces : ",i,*blanks_to_add_lock); + // // let blankspace: &str = " "; + // // let blankspace: &str = " ."; + // if i > 0 { + // // let blankspace: &str = " \u{e0000}"; + // let blankspace: &str = " "; + // outmsg.push_str(blankspace); + // dbg!("Added 'blank' string",outmsg.clone(),blankspace); + // } + // }; + + // let blankspace: &str = " "; + // let (firstword,restofstr) = outmsg.split_once(' '); + outmsg = if let Some((firstword,restofstr)) = outmsg.split_once(' ') { + if *blanks_to_add_lock { format!("{} {}\u{e0000}",firstword,restofstr,) } + else { format!("{} {}",firstword,restofstr) } + } else { outmsg.clone() } ; + + + + drop(blanks_to_add_lock); + + dbg!("Area that used to add blanks to out message"); + dbg!("OUT MESSAGE TO CHAT > ", chrono::offset::Local::now(), outmsg.clone()); + botlog::trace( - logstr.as_str(), + &format!("Out Message TO {} >> {}",channel_login.clone(),outmsg.clone()), Some("Chat > send_botmsg".to_string()), None, ); + + match msginput.clone() { + BotMsgType::SayInReplyTo(msg, _) => { + chat_clone.read().await.client.say_in_reply_to(&(*msg), outmsg.clone()).await.unwrap(); + }, + BotMsgType::Say(a, _) => { + chat_clone.read().await.client.say(a, outmsg.clone()).await.unwrap(); + } + BotMsgType::Notif(_) => { + chat_clone.read().await.client.say_in_reply_to(¶ms.read().await.msg, outmsg.clone()).await.unwrap(); + } + } + + // contextratelimiter.increment_counter(); + crl_lock.sent_msg(outmsg.clone()).await; + + let logstr = format!( + "(#{}) > {} ; contextratelimiter : {:?}", + channel_login.clone(), "rate limit counter increase", crl_lock + ); + + if let BotMsgType::SayInReplyTo(msg,_ ) = msginput { + botlog::trace( + logstr.as_str(), + Some("Chat > send_botmsg".to_string()), + Some(&(*msg)), + ); + } else { + botlog::trace( + logstr.as_str(), + Some("Chat > send_botmsg".to_string()), + None, + ); + } + + + } + ratelimiter::LimiterResp::Skip => { + // (); // do nothing otherwise + } + ratelimiter::LimiterResp::Sleep(err_passed_sleep_time) => { + dbg!("Rate Limiter returned Sleep unexpetedly",err_passed_sleep_time); + // panic!("ISSUE : sleep was already awaited - Should not happen?"); + botlog::warn( + format!( + "Warning : RATE LIMITERS returned Sleep Unexpectedly . This should not occur often . Rate Limiter passed Sleep({})", + err_passed_sleep_time + ).as_str(), + Some("Chat > send_botmsg".to_string()), + None, + ); + dbg!("Rate Limiter returned Sleep unexpetedly > Re-enqueueing message"); + // [x] Need to Re-Enqueue the message , but needs to be controlled + chat_clone.read().await.send_botmsg_inner(msginput.clone(),params).await; + } + } - } - ratelimiter::LimiterResp::Skip => { - // (); // do nothing otherwise - } - ratelimiter::LimiterResp::Sleep(_) => { - panic!("ISSUE : sleep was already awaited - Should not happen?"); - } - } + + drop(crl_lock); + dbg!("Send_botmsg_inner > Within Spawn > After Dropping a Write Guard"); + + }); Log::flush(); } + pub async fn send_botmsg(&self, msginput: BotMsgType, params : Arc<RwLock<ExecBodyParams>>) { - // pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, outmsg: String) { - // #[async_recursion] - pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, outmsg: String , params : ExecBodyParams) { + self.send_botmsg_inner(msginput, params).await; - // let params_clone = params.clone(); + // let chat = Arc::new(RwLock::new((*self).clone())); + // let chat_clone = chat.clone(); - // let botclone = Arc::clone(¶ms_clone.bot); - // let botlock = botclone.read().await; - // let id = botlock.get_identity(); - // let id = Arc::clone(&id); + // let cguard = chat_clone.read().await; + // let outguard = cguard.outqueue.1.read().await; + // // let r = (*outguard).clone(); + // match outguard.recv().await { + // Ok(a) => { - // // botlog::trace( - // // "ACQUIRING WRITE LOCK : ID", - // // Some("Chat > send_botmsg".to_string()), - // // Some(¶ms.msg), - // // ); - // // Log::flush(); - - // botlog::trace( - // "ACQUIRING READ LOCK : ID", - // Some("Chat > send_botmsg".to_string()), - // Some(¶ms.msg), - // ); - // Log::flush(); - - - // // let idlock = id.write().await; // <-- [ ] 03.24 - This is definitely locking it - // let idlock = id.read().await; // <-- [ ] 03.24 - seems to work - // let a = idlock.getspecialuserroles(params.get_sender(), Some(Channel(msg.channel_login.clone()))).await; - // botlog::trace( - // format!("GETSPECIALUSERROLES RESULT : {:?}",a).as_str(), - // Some("Chat > send_botmsg".to_string()), - // Some(¶ms.msg), - // ); - // Log::flush(); - - - - // // botlog::trace( - // // "ACQUIRED WRITE LOCK : ID", - // // Some("Chat > send_botmsg".to_string()), - // // Some(¶ms.msg), - // // ); - // // Log::flush(); + // }, + // Err(err) => { + // dbg!("ISSUE processing Receiver",err); + // sleep(Duration::from_millis(10)).await; + // } + // } - - // botlog::trace( - // "ACQUIRED READ LOCK : ID", - // Some("Chat > send_botmsg".to_string()), - // Some(¶ms.msg), - // ); - // Log::flush(); + // let msg_clone = Arc::new(msg.clone()); + // let blockingspawn = tokio::task::spawn_blocking( move || { + // // let paramsguard = params.blocking_read(); + // // let params = (*paramsguard).clone(); + // // drop(paramsguard); + // // chat_clone.blocking_read().outqueue.0.send_blocking(( + // // BotMsgType::SayInReplyTo(msg_clone, outmsg), + // // params + // // )).unwrap(); + // chat_clone.blocking_read().blocking_send_botmsg( + // msginput, + // params, + // ); + // }); - - self.send_botmsg(BotMsgType::SayInReplyTo(msg, outmsg) , params).await; + // blockingspawn.await.unwrap(); } - // pub async fn say(&self, channel_login: String, message: String) { - pub async fn say(&self, channel_login: String, message: String , params : ExecBodyParams) { - // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say - self.send_botmsg(BotMsgType::Say(channel_login.to_lowercase(), message), params).await; + pub fn blocking_send_botmsg(&self, msginput: BotMsgType, params : Arc<RwLock<ExecBodyParams>>) { + + let chat = Arc::new(RwLock::new((*self).clone())); + let chat_clone = chat.clone(); + // let msg_clone = Arc::new(msg.clone()); + let paramsguard = params.blocking_read(); + let params = (*paramsguard).clone(); + drop(paramsguard); + dbg!("Called blocking_send_botmsg",msginput.clone()); + chat_clone.blocking_read().outqueue.0.send_blocking(( + msginput, + params + )).unwrap(); + } + + pub async fn blocking_async_send_botmsg(&self, msginput: BotMsgType, params : Arc<RwLock<ExecBodyParams>>) { + + let chat = Arc::new(RwLock::new((*self).clone())); + let chat_clone = chat.clone(); + // let msg_clone = Arc::new(msg.clone()); + let blockingspawn = tokio::task::spawn_blocking( move || { + // let paramsguard = params.blocking_read(); + // let params = (*paramsguard).clone(); + // drop(paramsguard); + // chat_clone.blocking_read().outqueue.0.send_blocking(( + // BotMsgType::SayInReplyTo(msg_clone, outmsg), + // params + // )).unwrap(); + chat_clone.blocking_read().blocking_send_botmsg( + msginput, + params, + ); + }); + + blockingspawn.await.unwrap(); + + } + + + pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, outmsg: String , params : Arc<RwLock<ExecBodyParams>>) { + // let a = Arc::new(RwLock::new(self)); + // let a_clone = a.clone(); + self.send_botmsg( + BotMsgType::SayInReplyTo( + Arc::new((*msg).clone()), + outmsg) , + params + ).await; + } + + + pub fn blocking_say_in_reply_to(&self, msg: &PrivmsgMessage, outmsg: String , params : Arc<RwLock<ExecBodyParams>>) { + + let chat = Arc::new(RwLock::new((*self).clone())); + let chat_clone = chat.clone(); + let msg_clone = Arc::new(msg.clone()); + + let paramsguard = params.blocking_read(); + let params = (*paramsguard).clone(); + drop(paramsguard); + chat_clone.blocking_read().outqueue.0.send_blocking(( + BotMsgType::SayInReplyTo(msg_clone, outmsg), + params + )).unwrap(); + + } + + + + pub async fn blocking_async_say_in_reply_to(&self, msg: &PrivmsgMessage, outmsg: String , params : Arc<RwLock<ExecBodyParams>>) { + + // let params_clone = params.clone(); + + // let botclone = Arc::clone(¶ms_clone.bot); + // let botlock = botclone.read().await; + // let id = botlock.get_identity(); + // let id = Arc::clone(&id); + + // // botlog::trace( + // // "ACQUIRING WRITE LOCK : ID", + // // Some("Chat > send_botmsg".to_string()), + // // Some(¶ms.msg), + // // ); + // // Log::flush(); + + // botlog::trace( + // "ACQUIRING READ LOCK : ID", + // Some("Chat > send_botmsg".to_string()), + // Some(¶ms.msg), + // ); + // Log::flush(); + + + // // let idlock = id.write().await; // <-- [ ] 03.24 - This is definitely locking it + // let idlock = id.read().await; // <-- [ ] 03.24 - seems to work + // let a = idlock.getspecialuserroles(params.get_sender(), Some(Channel(msg.channel_login.clone()))).await; + // botlog::trace( + // format!("GETSPECIALUSERROLES RESULT : {:?}",a).as_str(), + // Some("Chat > send_botmsg".to_string()), + // Some(¶ms.msg), + // ); + // Log::flush(); + + + + // // botlog::trace( + // // "ACQUIRED WRITE LOCK : ID", + // // Some("Chat > send_botmsg".to_string()), + // // Some(¶ms.msg), + // // ); + // // Log::flush(); + + + + // botlog::trace( + // "ACQUIRED READ LOCK : ID", + // Some("Chat > send_botmsg".to_string()), + // Some(¶ms.msg), + // ); + // Log::flush(); + + + // self.send_botmsg(BotMsgType::SayInReplyTo(Arc::new(msg.clone()), outmsg) , params).await; + // self.outqueue.write().await.push(( + // BotMsgType::SayInReplyTo(Arc::new(msg.clone()), outmsg), + // params + // )); + // let paramsguard = params.blocking_read(); + // let a = (*paramsguard).clone(); + // drop(paramsguard); + // self.outqueue.0.send_blocking(( + // BotMsgType::SayInReplyTo(Arc::new(msg.clone()), outmsg), + // a + // )).unwrap(); + + + // let a = Arc::new(RwLock::new(*self)); + + let chat = Arc::new(RwLock::new((*self).clone())); + let chat_clone = chat.clone(); + let msg_clone = Arc::new(msg.clone()); + let blockingspawn = tokio::task::spawn_blocking( move || { + // let paramsguard = params.blocking_read(); + // let params = (*paramsguard).clone(); + // drop(paramsguard); + // chat_clone.blocking_read().outqueue.0.send_blocking(( + // BotMsgType::SayInReplyTo(msg_clone, outmsg), + // params + // )).unwrap(); + chat_clone.blocking_read().blocking_say_in_reply_to( + &(*msg_clone), + outmsg, + params, + ); + }); + + blockingspawn.await.unwrap(); + + + // let a = Arc::new(RwLock::new(self)); + // let a_clone = a.clone(); + // let b = self.send_botmsg(BotMsgType::SayInReplyTo(msg, outmsg) , params); + + + // struct Tester { + // queue : Vec<chat_util::MsgFuture>, + // } + + // let mut q = vec![]; + // q.push(b); + + // let inst = Tester { + // queue : q, + // }; + + + + } + + + pub async fn say(&self, channel_login: String, message: String , params : Arc<RwLock<ExecBodyParams>>) { + self.send_botmsg( + BotMsgType::Say( + channel_login, + message), + params + ).await; + } + + pub fn blocking_say(&self, channel_login: String, message: String , params : Arc<RwLock<ExecBodyParams>>) { + + let chat = Arc::new(RwLock::new((*self).clone())); + let chat_clone = chat.clone(); + + let paramsguard = params.blocking_read(); + let params = (*paramsguard).clone(); + drop(paramsguard); + chat_clone.blocking_read().outqueue.0.send_blocking(( + BotMsgType::Say(channel_login.to_lowercase(), message), + params + )).unwrap(); + + } + + + + pub async fn blocking_async_say(&self, channel_login: String, message: String , params : Arc<RwLock<ExecBodyParams>>) { + // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say + + // self.send_botmsg(BotMsgType::Say(channel_login.to_lowercase(), message), params).await; + + // let paramguard = params.blocking_read(); + // let a = (*paramguard).clone(); + // drop(paramguard); + // self.outqueue.0.send_blocking(( + // BotMsgType::Say(channel_login.to_lowercase(), message), + // a + // )).unwrap(); + + + let chat = Arc::new(RwLock::new((*self).clone())); + let chat_clone = chat.clone(); + // let msg_clone = Arc::new(msg.clone()); + let blockingspawn = tokio::task::spawn_blocking( move || { + // let paramsguard = params.blocking_read(); + // let params = (*paramsguard).clone(); + // drop(paramsguard); + // chat_clone.blocking_read().outqueue.0.send_blocking(( + // BotMsgType::Say(channel_login.to_lowercase(), message), + // params + // )).unwrap(); + chat_clone.blocking_read().blocking_say( + channel_login, + message, + params, + ); + }); + + blockingspawn.await.unwrap(); + + + } + + + + async fn _me(&self, _: String, _: String) { // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say diff --git a/src/core/identity.rs b/src/core/identity.rs index dc2da72..feb6791 100644 --- a/src/core/identity.rs +++ b/src/core/identity.rs @@ -214,11 +214,11 @@ async fn cmd_promote(params : ExecBodyParams) { { arg2 } else if let Some(a) = arg1 { if a.starts_with('-') { - botlock.botmgrs.chat.send_botmsg( + botlock.botmgrs.chat.read().await.send_botmsg( super::chat::BotMsgType::Notif( "Invalid Argument Flag".to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; return } else { arg1 } @@ -294,10 +294,10 @@ async fn cmd_promote(params : ExecBodyParams) { // We should call a notification around here - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outmsg.to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; @@ -432,11 +432,11 @@ async fn cmd_demote(params : ExecBodyParams) { { arg2 } else if let Some(a) = arg1 { if a.starts_with('-') { - botlock.botmgrs.chat.send_botmsg( + botlock.botmgrs.chat.read().await.send_botmsg( super::chat::BotMsgType::Notif( "Invalid Argument Flag".to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; return } else { arg1 } @@ -519,10 +519,10 @@ async fn cmd_demote(params : ExecBodyParams) { Some(¶ms.msg), ); - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outmsg.to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; @@ -545,6 +545,8 @@ async fn getroles(params : ExecBodyParams) { */ + dbg!("In getroles command"); + let mut argv = params.msg.message_text.split(' '); @@ -553,7 +555,28 @@ async fn getroles(params : ExecBodyParams) { let arg1 = argv.next(); let targetuser = match arg1 { - None => return, // exit if no arguments + None => { + dbg!("Expected Exit"); + botlog::debug( + "Exitting cmd getroles - Invalid arguments ", + Some("identity.rs > init > getroles()".to_string()), + Some(¶ms.msg), + ); + + + let botlock = params.bot.read().await; + + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( + "Invalid Arguments".to_string() + ), + Arc::new(RwLock::new(params.clone())), + ).await; + + drop(botlock); + + return + + }, // exit if no arguments Some(arg) => arg, }; @@ -561,12 +584,15 @@ async fn getroles(params : ExecBodyParams) { let targetchnl = arg2; + dbg!("In prior to bot read guard command"); + let botlock = params.bot.read().await; let id = botlock.get_identity(); let idlock = id.read().await; + dbg!("Pror to getspecialuserroles()"); let sproles = match targetchnl { None => { // [ ] If targetchnl is not provided, default to pulling the current channel @@ -660,14 +686,19 @@ async fn getroles(params : ExecBodyParams) { Some(¶ms.msg), ); - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + dbg!("Pror to getroles send_botmsg"); + + botlock.botmgrs.chat.read().await.send_botmsg(super::chat::BotMsgType::Notif( outmsg.to_string() ), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; // [ ] NOTE : After the above, I should receive only the roles in the context of the current channel I received this ideally and maybe BotAdmin ; not outside + + + dbg!("End of getroles command"); } @@ -689,7 +720,7 @@ pub enum Permissible { type UserRolesDB = HashMap<String, Arc<RwLock<Vec<UserRole>>>>; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct IdentityManager { special_roles_users: Arc<RwLock<UserRolesDB>>, } @@ -1535,6 +1566,8 @@ impl IdentityManager { Note : Ideally this be called for a given chatter name ? */ + dbg!("In getspecialuserroles()"); + // [ ] !!! TODO: I don't think below is evaluating by given channel botlog::debug( &format!( @@ -1566,6 +1599,9 @@ impl IdentityManager { }; + + dbg!("Before reading Vector Roles"); + let rolesdb = Arc::clone(&self.special_roles_users); let rolesdb_lock = rolesdb.read().await; @@ -1621,6 +1657,8 @@ impl IdentityManager { } } + dbg!("exit getspecialusreroles with",&evalsproles); + botlog::debug( &format!("OUT > evalsproles {:?}", &evalsproles), Some("IdentityManager > getspecialuserroles()".to_string()), diff --git a/src/core/ratelimiter.rs b/src/core/ratelimiter.rs index 4de77bc..cdfa123 100644 --- a/src/core/ratelimiter.rs +++ b/src/core/ratelimiter.rs @@ -1,8 +1,14 @@ +// Related : https://dev.twitch.tv/docs/irc/#rate-limits + + const TIME_THRESHOLD_S: u64 = 30; const TIME_MIN_S_F64: f64 = 1.0; const MSG_THRESHOLD: u32 = 20; +const DUPMSG_MIN_SEND_S: f64 = 30.0; + +use std::{sync::Arc, time::Instant}; +use tokio::sync::RwLock; -use std::time::Instant; use crate::core::botlog; #[derive(Debug, Clone)] @@ -10,9 +16,10 @@ pub struct RateLimiter { timer: Instant, msgcounter: u32, lastmsgtimer : Instant, + lastmsgdup : String, } -#[derive(Debug)] +#[derive(Debug,Clone)] pub enum LimiterResp { Allow, // when it's evaluated to be within limits Skip, // as outside of rate limits @@ -32,10 +39,20 @@ impl RateLimiter { timer: Instant::now(), msgcounter: 0, lastmsgtimer: Instant::now(), + lastmsgdup : String::new(), } } - pub fn check_limiter(&mut self) -> LimiterResp { + fn dupmsg(&self,inmsg : String) -> bool { + dbg!("dubmsg()",&inmsg,&self.lastmsgdup); + if self.lastmsgdup == inmsg && self.lastmsgtimer.elapsed().as_secs_f64() < DUPMSG_MIN_SEND_S { + // duplicate detected + // self.lastmsgdup = String::new(); // update it as no longer duplicate + return true; + } else { return false; }; + } + + pub async fn check_limiter(&mut self,inmsg : String) -> LimiterResp { let logstr = format!( @@ -49,32 +66,104 @@ impl RateLimiter { ); - let rsp = if self.timer.elapsed().as_secs() >= TIME_THRESHOLD_S { - self.timer = Instant::now(); - self.msgcounter = 0; + /* + Documented Thesholds from https://dev.twitch.tv/docs/irc/#rate-limits + - [x] self.timer.elapsed().as_secs() >= TIME_THRESHOLD_S + >> initialize with LimiterResp::Allow + - [x] self.msgcounter < MSG_THRESHOLD && + self.lastmsgtimer.elapsed().as_secs_f64() >= TIME_MIN_S_F64 + >> LimiterResp::Allow + - [x] else + >> LimiterResp::Sleep(TIME_MIN_S_F64 - self.lastmsgtimer.elapsed().as_secs_f64() + 0.1) + */ + + // let ratelimiter_ar = Arc::new(RwLock::new((*self).clone())); + let ratelimiter_ar = Arc::new(RwLock::new(self)); + let mut rl_guard = ratelimiter_ar.write().await; + + let rsp = if rl_guard.timer.elapsed().as_secs() >= TIME_THRESHOLD_S { + rl_guard.timer = Instant::now(); + rl_guard.msgcounter = 0; LimiterResp::Allow - } else if self.msgcounter < MSG_THRESHOLD && - self.lastmsgtimer.elapsed().as_secs_f64() >= TIME_MIN_S_F64 { + } else if rl_guard.msgcounter < MSG_THRESHOLD && + rl_guard.lastmsgtimer.elapsed().as_secs_f64() >= TIME_MIN_S_F64 { LimiterResp::Allow } else { // when elapsed() < TIME_THRESHOLD_S && msgcounter >= MSG_THRESHOLD // LimiterResp::Skip - LimiterResp::Sleep(TIME_MIN_S_F64 - self.lastmsgtimer.elapsed().as_secs_f64() + 0.1) + LimiterResp::Sleep(TIME_MIN_S_F64 - rl_guard.lastmsgtimer.elapsed().as_secs_f64() + 0.1) }; + // [ ] If rsp is still allow at this point, also do a dupcheck to adjust rsp to a sleep + + dbg!(inmsg.clone()); + dbg!(rl_guard.dupmsg(inmsg.clone())); + dbg!("Before Dup test",rsp.clone()); + dbg!(matches!(rsp.clone(),LimiterResp::Allow)); + + + // [-] Comment this area if removing Duplicate checking functionality + /* */ + let rsp = if rl_guard.dupmsg(inmsg.clone()) && matches!(rsp,LimiterResp::Allow) { + //self.lastmsgdup = String::new(); + // LimiterResp::Sleep(DUPMSG_MIN_SEND_S) + dbg!("Duplicate detected"); + LimiterResp::Sleep(DUPMSG_MIN_SEND_S - rl_guard.lastmsgtimer.elapsed().as_secs_f64() + 0.1) + } else { rsp.clone() }; + /* */ + + // => 04.02 - Don't update here + // // After Dup is checked, Set the lastmsgdup to latest message + // dbg!("Before Assigning Lastmsgdup",rl_guard.lastmsgdup.clone(),inmsg.clone(),rsp.clone()); + // rl_guard.lastmsgdup = inmsg.clone(); + // dbg!("After Assigning Lastmsgdup",rl_guard.lastmsgdup.clone(),inmsg.clone()); + + + // [ ] Allows if sleep came up as negative + let rsp = match rsp { + LimiterResp::Sleep(sleeptime) if sleeptime < 0.0 => LimiterResp::Allow , // allow is sleeptime evaluated is negative + _ => rsp.clone(), + } ; + + + dbg!(rsp.clone()); + botlog::trace( &format!("Limiter Response : {:?} ; Elapsed (as_sec_f64) : {}", - rsp, self.lastmsgtimer.elapsed().as_secs_f64()), + rsp, rl_guard.lastmsgtimer.elapsed().as_secs_f64()), Some("Rate Limiter Inner".to_string()), None, ); + dbg!("check_limiter() > ratelimiter guard",rl_guard.clone()); + + drop(rl_guard); + + rsp } - pub fn increment_counter(&mut self) { + // pub fn increment_counter(&mut self) { + // self.msgcounter += 1; + // self.lastmsgtimer = Instant::now(); + // } + + pub async fn sent_msg(&mut self,msgstr : String) { + + dbg!("sent_msg triggered",&msgstr); + + // let ratelimiter_ar = Arc::new(RwLock::new((*self).clone())); + // let mut rl_guard = ratelimiter_ar.write().await; + + dbg!("sent_msg > lastmsg before update",&self.lastmsgdup); + + self.lastmsgdup = msgstr; self.msgcounter += 1; self.lastmsgtimer = Instant::now(); + + dbg!("sent_msg > lastmsg after update",&self.lastmsgdup); + + // drop(rl_guard); } } diff --git a/src/custom/experimental.rs b/src/custom/experimental.rs index 409abd1..7da2ecb 100644 --- a/src/custom/experimental.rs +++ b/src/custom/experimental.rs @@ -12,6 +12,7 @@ pub use crate::core::botmodules::ModulesManager; mod experiment001; mod experiment002; +mod experiment003; // [ ] init() function that accepts bot instance - this is passed to init() on submodules @@ -21,4 +22,5 @@ pub async fn init(mgr: Arc<ModulesManager>) { experiment001::init(Arc::clone(&mgr)).await; experiment002::init(Arc::clone(&mgr)).await; + experiment003::init(Arc::clone(&mgr)).await; } diff --git a/src/custom/experimental/experiment001.rs b/src/custom/experimental/experiment001.rs index dd3cba4..36156d9 100644 --- a/src/custom/experimental/experiment001.rs +++ b/src/custom/experimental/experiment001.rs @@ -15,6 +15,7 @@ const OF_CMD_CHANNEL:Channel = Channel(String::new()); use rand::Rng; +use tokio::sync::RwLock; use std::sync::Arc; use crate::core::bot_actions::ExecBodyParams; @@ -130,10 +131,11 @@ async fn good_girl(params : ExecBodyParams) { botlock .botmgrs .chat + .read().await .say_in_reply_to( ¶ms.msg, String::from("GoodGirl xdd "), - params.clone() + Arc::new(RwLock::new(params.clone())) ).await; @@ -170,22 +172,25 @@ async fn babygirl(params : ExecBodyParams) { botlock .botmgrs .chat + .read().await .say_in_reply_to( ¶ms.msg, String::from("16:13 notohh: cafdk"), - params.clone() + Arc::new(RwLock::new(params.clone())) ).await; + sleep(Duration::from_secs_f64(0.5)).await; botlock .botmgrs .chat + .read().await .say_in_reply_to( ¶ms.msg, String::from("16:13 notohh: have fun eating princess"), - params.clone() + Arc::new(RwLock::new(params.clone())) ).await; @@ -194,10 +199,11 @@ async fn babygirl(params : ExecBodyParams) { botlock .botmgrs .chat + .read().await .say_in_reply_to( ¶ms.msg, String::from("16:13 notohh: baby girl"), - params.clone() + Arc::new(RwLock::new(params.clone())) ).await; @@ -225,5 +231,39 @@ async fn routinelike(params : ExecBodyParams) { // lines are executed after in conjunction to the spawn + // spawn 5 independent spawns + + let bot = Arc::clone(¶ms.bot); + let params_ar = Arc::new(RwLock::new(params.clone())); + let params_c = params_ar.clone(); + + for _ in 0..5 { + let bot_clone = bot.clone(); + let params_cc = params_c.clone(); + tokio::spawn( async move { + println!(">> SPAWNED Innterroutine triggered!"); + sleep(Duration::from_secs_f64(5.0)).await; + + let botlock = bot_clone.read().await; + let params_ccc = params_cc.clone(); + let paramsguard = params_ccc.read().await; + + botlock + .botmgrs + .chat + .read().await + .say_in_reply_to( + ¶msguard.msg, + String::from("SPAWNED GoodGirl xdd "), + params_cc, + ).await; + + drop(botlock); + drop(paramsguard); + + }); + }; + + } diff --git a/src/custom/experimental/experiment002.rs b/src/custom/experimental/experiment002.rs index a4ecb25..e1e621d 100644 --- a/src/custom/experimental/experiment002.rs +++ b/src/custom/experimental/experiment002.rs @@ -17,6 +17,7 @@ const OF_CMD_CHANNEL:Channel = Channel(String::new()); use std::sync::Arc; use chrono::{TimeZone,Local}; +use tokio::sync::RwLock; use crate::core::bot_actions::ExecBodyParams; @@ -116,7 +117,7 @@ async fn sayout(params : ExecBodyParams) { botlog::trace( &format!("[TRACE] Evaluated status of {} : {:?}", - trgchnl.to_string().clone(),botlock.botmgrs.chat.client.get_channel_status(trgchnl.to_string().clone()).await), + trgchnl.to_string().clone(),botlock.botmgrs.chat.read().await.client.get_channel_status(trgchnl.to_string().clone()).await), Some("Chat > send_botmsg".to_string()), None, ); @@ -156,10 +157,11 @@ async fn sayout(params : ExecBodyParams) { botlock .botmgrs .chat + .read().await .say( trgchnl.to_string(), newoutmsg.to_string(), - params.clone(), + Arc::new(RwLock::new(params.clone())), ).await; @@ -179,10 +181,11 @@ async fn sayout(params : ExecBodyParams) { botlock .botmgrs .chat + .read().await .say_in_reply_to( ¶ms.msg, String::from("Invalid arguments"), - params.clone() + Arc::new(RwLock::new(params.clone())) ).await; diff --git a/src/custom/experimental/experiment003.rs b/src/custom/experimental/experiment003.rs new file mode 100644 index 0000000..d76d8fb --- /dev/null +++ b/src/custom/experimental/experiment003.rs @@ -0,0 +1,830 @@ +/* + Custom Modules - + + Usage : + [ ] within the file's init(), define BotActions & Load them into the ModulesManager + [ ] Define Execution Bodies for these BotActions + [ ] Afterwards, add the following to parent modules.rs file + - mod <modulename>; + - within init(), <modulename>::init(mgr).await + +*/ + + +const OF_CMD_CHANNEL:Channel = Channel(String::new()); + + +use casual_logger::Log; +use rand::Rng; +use rand::thread_rng; +use rand::rngs::StdRng; +use rand::seq::SliceRandom; +use tokio::sync::RwLock; +use std::borrow::Borrow; +use std::borrow::BorrowMut; +use std::sync::Arc; + +use crate::core::bot_actions::ExecBodyParams; +use crate::core::botinstance::Channel; +use crate::core::botlog; + +use crate::core::bot_actions::actions_util; +use crate::core::botmodules::{BotAction, BotActionTrait, BotCommand, BotModule, Listener, ModulesManager, Routine, RoutineAttr}; + +use crate::core::identity::UserRole::*; + +use tokio::time::{sleep, Duration}; + +pub async fn init(mgr: Arc<ModulesManager>) { + + // 1. Define the BotAction + let botc1 = BotCommand { + module: BotModule(String::from("experiments003")), + command: String::from("test3"), // command call name + alias: vec![], // String of alternative names + exec_body: actions_util::asyncbox(test3_body), + help: String::from("Test Command tester"), + required_roles: vec![ + BotAdmin, + Mod(OF_CMD_CHANNEL), + ], + }; + + // 2. Add the BotAction to ModulesManager + botc1.add_to_modmgr(Arc::clone(&mgr)).await; + + + // 1. Define the BotAction + let botc1 = BotCommand { + module: BotModule(String::from("experiments003")), + command: String::from("countdown"), // command call name + alias: vec![], // String of alternative names + exec_body: actions_util::asyncbox(countdown_chnl_v1), + help: String::from("Test Command tester"), + required_roles: vec![ + BotAdmin, + Mod(OF_CMD_CHANNEL), + ], + }; + + // 2. Add the BotAction to ModulesManager + botc1.add_to_modmgr(Arc::clone(&mgr)).await; + + + +} + + +async fn countdown_chnl_v1(params : ExecBodyParams) { + + botlog::debug( + "[CHILDFN] countdown_chnl() triggered!", + Some("Experiments003 > countdown_chnl()".to_string()), + Some(¶ms.msg), + ); + + + /* + create a fun countdown BotCommand that allows an Elevated Chatter to + -a add channels to target a routine message + -start to start the routine with an input String, that sends a number + of messages to the targeted channels with a countdown, until it + reaches 0 when it sends a cute or funny message + + NOTE : At the moment, I don't have customizable persistence, so I would just use + counters from the Routine itself + */ + + /* + Because of some bot core features are not available, v1.0 of this could be : + [x] 1. Create a Routine & start a routine + [x] 2. Have the routine go through each joined channel randomly and countdown + [x] 3. At the end, say "0, I love you uwu~" in the last chosen channel + */ + + /* + Usage => 03.28 - skipping arguments as functinoality isn't enhanced + + -a <channel> => 03.28 - Not sure if this is possible at the moment? + + -start + + -stop => 03.28 - Not sure if this is possible at the moment? + + */ + + /* + [ ] Functional Use Case + + 1. -a <channel> adds targetted channels + + */ + + // [-] Unwraps arguments from message + + // let (arg1, arg2) = { + + // let mut argv = params.msg.message_text.split(' '); + + // argv.next(); // Skip the command name + + // let arg1 = argv.next(); + + // let arg2 = argv.next(); + + // (arg1, arg2) + // }; + + + + // [ ] 1. Create a Routine & start a routine + + let params_ar = Arc::new(RwLock::new(params)); + + // let parentmodule = params.get_parent_module().await; + let module = params_ar.read().await.get_module().await; + // let channel = params.get_channel().await; + // let channel = params.get_channel().await; + // let channel = params.get_channel(); + + let channel_task_grabber = tokio::task::spawn_blocking( move || { + let params_clone = params_ar.clone(); + let channel = params_clone.blocking_read().get_channel(); + drop(params_clone); + (channel,params_ar) + }); + + let (channel,params_ar) = channel_task_grabber.await.unwrap(); + + let routine_attr = vec![ + // RoutineAttr::RunOnce + RoutineAttr::MaxIterations(5), + RoutineAttr::LoopDuration(Duration::from_secs(1)) + ]; + // let exec_body = actions_util::asyncbox(rtestbody); + // let exec_body = actions_util::asyncbox(innertester); // <-- 03.27 - when below is uncommented, this is throwing an issue + let exec_body = innertester; + + // async fn innertester(params : ExecBodyParams) { + fn innertester(params : Arc<RwLock<ExecBodyParams>>) { + + { + // let curract_guard = params.curr_act.read().await; + let paramguard1 = params.blocking_read(); + let curract = paramguard1.curr_act.clone().unwrap(); + let curract_guard = curract.blocking_read(); + + + // let logmsg_botact = match *params.curr_act.read().await { + + let logmsg_botact = match *curract_guard { + BotAction::C(_) => "command", + BotAction::R(_) => "routine", + BotAction::L(_) => "listener", + } ; + + + botlog::trace( + format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(), + Some("Experiments003 > countdown_chnl()".to_string()), + Some(¶ms.blocking_read().msg), + ); + Log::flush(); + } + + { + let bot = Arc::clone(¶ms.blocking_read().bot); + // let botlock = bot.read().await; + let botlock = bot.blocking_read(); + + // let curract_guard = params.curr_act.write().await; + let paramguard1 = params.blocking_read(); + let curract = paramguard1.curr_act.clone().unwrap(); + let curract_guard = curract.blocking_write(); + + // let routine_lock = arr.write().await; + + if let BotAction::R(arr) = &*curract_guard { + // if let BotAction::R(arr) = &*params.curr_act.read().await { + + botlog::trace( + "Before loading remaining iterations", + Some("Experiments003 > countdown_chnl()".to_string()), + None, + ); + Log::flush(); + + + // let iterleft = arr.read().await.remaining_iterations.unwrap_or(0); + + // // let iterleft = if arr.read().await.remaining_iterations.is_none() { 0i64 } + // // else { arr.read().await.remaining_iterations.unwrap() }; + // let iterleft = match arr.read().await.remaining_iterations { + // None => 0, + // Some(a) => a, + // }; + + // let routine_lock = arr.read().await; + // if let Some(a) = routine_lock.remaining_iterations.clone() { + // println!("Remaining iterations > {}",a) + // } + + let iterleft; + { + // let routine_lock = arr.write().await; + // let routine_lock = arr.blocking_write(); + let routine_lock = arr.blocking_read(); + iterleft = routine_lock.remaining_iterations.unwrap_or(0); + println!("remaining iterations : {:?}", iterleft); + } + + botlog::trace( + "after loading remaining iterations", + Some("Experiments003 > countdown_chnl()".to_string()), + None, + ); + Log::flush(); + + // [ ] get joined channels + let joinedchannels = botlock.bot_channels.clone(); + + + fn pick_a_channel(chnlvec : Vec<Channel>) -> Channel { + + + botlog::trace( + "In Pick_a_Channel()", + Some("Experiments003 > countdown_chnl()".to_string()), + None, + ); + Log::flush(); + + // More Information : https://docs.rs/rand/0.7.2/rand/seq/trait.SliceRandom.html#tymethod.choose + + let mut rng = thread_rng(); + + // let joinedchannels = botlock.bot_channels.clone(); + (*chnlvec.choose(&mut rng).unwrap()).clone() + } + + + let chosen_channel = pick_a_channel(joinedchannels); + + dbg!("SUCCESS", chosen_channel.clone()); + + botlog::trace( + format!("Picked a channel: {:?}", chosen_channel).as_str(), + Some("Experiments003 > countdown_chnl()".to_string()), + Some(¶ms.blocking_read().msg), + ); + Log::flush(); + + // [ ] !! ISSUE : With this fix though, the custom fn is no longer + // async. This is an issue because chat module is async + // - There should be no need to change chat , as async allows + // us to multitask with messages + + // let a = || { + + // let chosen_channel_ar = Arc::new(RwLock::new(chosen_channel)); + + // let params_clone = params.clone(); + // // let bot = Arc::clone(¶ms.blocking_read().bot); + + + // // dbg!("in chat async function"); + + // // let botlock = bot.blocking_read(); + // let channel_ar_clone = chosen_channel_ar.clone(); + + // let outmsg = if iterleft <= 1 { + // format!("{} I love you uwu~",iterleft) + // } else { format!("{}",iterleft) }; + + // botlock.botmgrs.chat + // .say( + // channel_ar_clone.blocking_read().0.clone(), + // outmsg, + // params_clone, + // ); + + let outmsg = if iterleft <= 1 { + // format!("{} I love you uwu~",iterleft) + let msgtxt = params.blocking_read().msg.message_text.clone(); + // format!("{} {} I love you uwu~",iterleft,msgtxt) + format!("{} {}",iterleft,msgtxt) + } else { format!("{} Tomfoolery Clap",iterleft) }; + + botlock.botmgrs.chat.blocking_read() + .blocking_say( + chosen_channel.0.clone(), + outmsg, + Arc::new(RwLock::new(params.blocking_read().clone())), + ); + + } + } + + + } + + + + // [ ] setup the routine + if let Ok(newr) = Routine::from( + "Routine Test".to_string(), + module, + channel.unwrap(), + routine_attr, + // Arc::new(RwLock::new(exec_body)), + exec_body, + Arc::new(RwLock::new(params_ar.clone().read().await.clone())), + ).await { + let newr_ar = newr.clone(); + // [ ] start the routine + if let Ok(_) = Routine::start(newr_ar.clone()).await { + + + botlog::debug( + "Successfully started", + Some("experiment003 > countdown_chnl()".to_string()), + Some(¶ms_ar.read().await.msg), + ); + + + Log::flush(); + + let bot = Arc::clone(¶ms_ar.read().await.bot); + + // let botlock = bot.read().await; + + // uses chat.say_in_reply_to() for the bot controls for messages + + // [ ] 04.01 - in an ASYNC context, ensure to raise a Blocking Spawn + // botlock + // .botmgrs + // .chat + // .say_in_reply_to( + // ¶ms_ar.read().await.msg, + // "Started Routine!".to_string(), + // Arc::new(RwLock::new(params_ar.read().await.clone())) + // ); + + // let jhandle = newr.clone().read().await.join_handle.clone().unwrap(); + // let a = jhandle.write().await; + // a. + // sleep(Duration::from_secs(300)).await; + + + // let loopbodyspawn = tokio::task::spawn_blocking( move || { + // let botlock = bot.blocking_read(); + // botlock + // .botmgrs + // .chat + // .say_in_reply_to( + // ¶ms_ar.blocking_read().msg, + // "Started Routine!".to_string(), + // Arc::new(RwLock::new(params_ar.blocking_read().clone())) + // ); + // }); + + // loopbodyspawn.await.unwrap(); + + // let botlock = bot.read().await; + // botlock + // .botmgrs + // .chat + // .say_in_reply_to( + // ¶ms_ar.blocking_read().msg, + // "Started Routine!".to_string(), + // Arc::new(RwLock::new(params_ar.blocking_read().clone())) + // ).await; + // drop(botlock); + + + let botlock = bot.read().await; + let params_guard = params_ar.read().await; + botlock + .botmgrs + .chat + .read().await + .say_in_reply_to( + // ¶ms_ar.blocking_read().msg, + ¶ms_guard.msg, + "Started Routine!".to_string(), + Arc::new(RwLock::new(params_guard.clone())) + ).await; + drop(botlock); + drop(params_guard); + + } + + + } + + + // botlock + // .botmgrs + // .chat + // .say_in_reply_to( + // ¶ms.msg, + // format!("{:?}",), + // params.clone() + // ).await; + + + +} + + + + +async fn test3_body(params : ExecBodyParams) { + // println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call + botlog::debug( + "testy triggered!", + Some("Experiments003 > test3 command body".to_string()), + Some(¶ms.msg), + ); + + /* + Test Routine Start() by : + 1. In this single exec body , create a Routine + 2. Create a Routine Execution Body + 3. Pass the Execution Body & Routine Attributes to create the Routine + 4. Start the Routine + 5. For RunOnce , we should see it only trigger once, and then complete in the logs + + */ + + // [x] Get the module from params + + + let params_ar = Arc::new(RwLock::new(params)); + + // let parentmodule = params.get_parent_module().await; + let module = params_ar.read().await.get_module().await; + // let channel = params.get_channel().await; + // let channel = params.get_channel().await; + // let channel = params.get_channel(); + let routine_attr = vec![ + RoutineAttr::RunOnce + ]; + // let exec_body = actions_util::asyncbox(rtestbody); + // let exec_body = actions_util::asyncbox(rtestbody); // <-- 03.27 - when below is uncommented, this is throwing an issue + + // let channel = params.get_channel(); + let channel_task_grabber = tokio::task::spawn_blocking( move || { + let params_clone = params_ar.clone(); + let channel = params_clone.blocking_read().get_channel(); + drop(params_clone); + (channel,params_ar) + }); + + let (channel,params_ar) = channel_task_grabber.await.unwrap(); + + + + + + let exec_body = rtestbody; + + // let parent_params = params.clone(); + // let params_clone = params.clone(); + + + // async fn rtestbody(params : ExecBodyParams) { + fn rtestbody(params : Arc<RwLock<ExecBodyParams>>) { + + + // let guard1 = params.curr_act.read().await; + let paramguard1 = params.blocking_read(); + let curract = paramguard1.curr_act.clone().unwrap(); + let guard1 = curract.blocking_read(); + { + let logmsg_botact = match *guard1 { + BotAction::C(_) => "command", + BotAction::R(_) => "routine", + BotAction::L(_) => "listener", + } ; + + botlog::trace( + format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(), + Some("Experiments003 > test3 command body".to_string()), + Some(¶ms.blocking_read().msg), + ); + Log::flush(); + } + + + { + let logmsg_botact = match &*guard1 { + BotAction::C(_) => "command 2", + BotAction::R(_) => "routine 2", + BotAction::L(_) => "listener 2", + } ; + + botlog::trace( + format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(), + Some("Experiments003 > test3 command body".to_string()), + Some(¶ms.blocking_read().msg), + ); + Log::flush(); + } + + // drop(guard1); + + + { + dbg!("Custom > within Child Custom fn - Before Critical area"); + println!("Critical code area start"); // <= 03.29 - This is printed + + // if let BotAction::R(c) = &*guard { + // println!("{:?}",c.read().await.channel); + // } + + // let routine_channel = if let BotAction::R(c) = &*guard1 { + // dbg!("Custom > within Child Custom fn - During Critical area > Routine Guard "); + // let routineguard = c.read().await; + // Some(routineguard.channel.clone()); + // } else { + // None + // }; + + + // let chnl = match &*guard1 { + // BotAction::R(arr) => { + // dbg!("Custom > within Child Custom fn - During Critical area > Before Routine Guard "); + // let routineguard = arr.read().await; + // dbg!("Custom > within Child Custom fn - During Critical area > After Routine Guard "); + // Some(routineguard.channel.clone()) + // }, + // BotAction::C(_) | BotAction::L(_) => None , + // } ; + + // let chnl = params.get_channel().await; + // let chnl = params.get_channel().await; + let chnl = params.blocking_read().get_channel(); + dbg!("Custom > within Child Custom fn - after GetChannel"); + println!("{:?}",chnl); + + + println!("Critical code area end"); // <= 03.29 - ISSUE This is NOT printed + dbg!("Custom > within Child Custom fn - Before Critical area"); + + // if let BotAction::R(arr) = &*params.curr_act.read().await { + // for curriter in 0..5 { + // println!("tester - Routine - Completed Iterations : {}", + // arr.read().await.complete_iterations); + // println!("tester - Custom Loop - Completed Iterations : {}", + // curriter); + // sleep(Duration::from_secs_f64(0.5)).await; + // } + // } + } + + } + + + let params_clone = params_ar.clone(); + + botlog::debug( + format!("RTESTBODY : module - {:?} ; channel - {:?}", + module,channel + ).as_str(), + Some("experiment003 > test3_body".to_string()), + Some(¶ms_clone.read().await.msg), + ); + + + + let a = Routine::from( + "Routine Test".to_string(), + module, + channel.unwrap(), + routine_attr, + // Arc::new(RwLock::new(exec_body)), + exec_body, + Arc::new(RwLock::new(params_clone.read().await.clone())) + ).await; + + + + if let Ok(newr) = a { + + // NOTE : The below is a "Playing aound" feature + // In the below, we're unnecessarily adjusting the ExecBodyParams of the parent + + // To get the reference to the Routine or the BotAction there are now refernces to itself + + // [ ] before execute , be sure to adjust curr_act + + // let mut params_mut = params; + let newr_ar = newr.clone(); + + // params_mut.curr_act = Arc::new(RwLock::new( + // BotAction::R(newr_ar.clone()) + // )); + + + // { + // newr_ar.write().await.parent_params = params_mut.clone(); + // } + + let rslt = Routine::start(newr_ar.clone()).await; + + // let rslt = newr_ar.read().await.start().await; + + let rsltstr = match rslt { + Ok(_) => "successful".to_string(), + Err(a) => a, + }; + + + botlog::debug( + format!("TEST3_BODY RESULT : {:?}", + rsltstr + ).as_str(), + Some("experiment003 > test3_body".to_string()), + Some(&(params_ar.clone()).read().await.msg), + ); + + Log::flush(); + + let bot = Arc::clone(¶ms_ar.read().await.bot); + + let botlock = bot.read().await; + + // uses chat.say_in_reply_to() for the bot controls for messages + botlock + .botmgrs + .chat + .read().await + .say_in_reply_to( + ¶ms_ar.read().await.msg, + format!("Routine Result : {:?}",rsltstr), + Arc::new(RwLock::new(params_clone.read().await.clone())) + ).await; + + // [x] Will not be handling JoinHandles here . If immediate abort() handling is required, below is an example that works + /* + + + let a = newr.clone().read().await.join_handle.clone(); + match a { + Some(b) => { + b.read().await.borrow().abort(); // [x] <-- This aborts if wanting to abort immediately + //() + }, + None => (), + } + + */ + + + } + + + Log::flush(); + + +} + + + + + +async fn good_girl(params : ExecBodyParams) { + + // [ ] Uses gen_ratio() to output bool based on a ratio probability . + // - For example gen_ratio(2,3) is 2 out of 3 or 0.67% (numerator,denomitator) + // - More Info : https://rust-random.github.io/rand/rand/trait.Rng.html#method.gen_ratio + + if params.msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase() + || params.msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() + { + botlog::debug( + "Good Girl Detected > Pausechamp", + Some("experiments > goodgirl()".to_string()), + Some(¶ms.msg), + ); + + let rollwin = rand::thread_rng().gen_ratio(1, 10); + + if rollwin { + botlog::debug( + "Oh that's a good girl!", + Some("experiments > goodgirl()".to_string()), + Some(¶ms.msg), + ); + + let bot = Arc::clone(¶ms.bot); + + + let botlock = bot.read().await; + + // uses chat.say_in_reply_to() for the bot controls for messages + botlock + .botmgrs + .chat + .read().await + .say_in_reply_to( + ¶ms.msg, + String::from("GoodGirl xdd "), + Arc::new(RwLock::new(params.clone())) + ).await; + + + } + } +} + +async fn testy(params : ExecBodyParams) { + println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call + botlog::debug( + "testy triggered!", + Some("experiments > testy()".to_string()), + Some(¶ms.msg), + ); +} + + +async fn babygirl(params : ExecBodyParams) { + + + println!("babygirl triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call + botlog::debug( + "babygirl triggered!", + Some("experiments > babygirl()".to_string()), + Some(¶ms.msg), + ); + + + let bot = Arc::clone(¶ms.bot); + + let botlock = bot.read().await; + + + botlock + .botmgrs + .chat + .read().await + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: cafdk"), + Arc::new(RwLock::new(params.clone())) + ).await; + + + sleep(Duration::from_secs_f64(0.5)).await; + + botlock + .botmgrs + .chat + .read().await + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: have fun eating princess"), + Arc::new(RwLock::new(params.clone())) + ).await; + + + sleep(Duration::from_secs_f64(2.0)).await; + + botlock + .botmgrs + .chat + .read().await + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: baby girl"), + Arc::new(RwLock::new(params.clone())) + ).await; + + + +} + + +async fn routinelike(params : ExecBodyParams) { + println!("routinelike triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call + botlog::debug( + "routinelike triggered!", + Some("experiments > routinelike()".to_string()), + Some(¶ms.msg), + ); + + // spawn an async block that runs independently from others + + tokio::spawn( async { + for _ in 0..5 { + println!(">> Innterroutine triggered!"); + sleep(Duration::from_secs_f64(5.0)).await; + } + } + ); + + // lines are executed after in conjunction to the spawn + +} + diff --git a/src/main.rs b/src/main.rs index 6bc6c0f..3a797a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,12 +14,22 @@ pub type BotAR = Arc<RwLock<BotInstance>>; #[tokio::main] pub async fn main() { + console_subscriber::init(); + + Log::set_file_ext(Extension::Log); Log::set_level(Level::Trace); Log::set_retention_days(2); // Log::set_level(Level::Notice); - + // fn innerbody() -> i64 { + // println!("hello"); + // 64 + // } + + // let exec_body:fn() -> i64 = innerbody; + +