this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Big refactor time, changes will be addressed as the next release's changelog

roufpup 35889177 861d275c

+2844 -1771
-17
.devcontainer/devcontainer.json
··· 1 - { 2 - "image": "mcr.microsoft.com/devcontainers/rust:latest", 3 - "features": { 4 - "ghcr.io/devcontainers/features/rust:1": {}, 5 - "ghcr.io/coder/devcontainer-features/code-server:2": { 6 - "auth": "none", 7 - "appName": "RustCode", 8 - "workspace": "/home/vscode/fluxer-rs", 9 - "disableTelemetry": true, 10 - "disableWorkspaceTrust": true 11 - } 12 - }, 13 - "postCreateCommand": "bash .devcontainer/setup.sh", 14 - "mounts": [ 15 - "source=/home/coder,target=/home/vscode,type=bind" 16 - ] 17 - }
-7
.devcontainer/setup.sh
··· 1 - #!/bin/bash 2 - set -e 3 - 4 - sudo apt-get update && sudo apt-get install -y clang 5 - cargo install --locked wild-linker 6 - 7 - code-server --install-extension rust-lang.rust-analyzer
+1
.gitignore
··· 3 3 .idea/ 4 4 .devcontainer/ 5 5 examples/examplebot/target 6 + TODO
+708 -19
Cargo.lock
··· 209 209 ] 210 210 211 211 [[package]] 212 + name = "atomic-waker" 213 + version = "1.1.2" 214 + source = "registry+https://github.com/rust-lang/crates.io-index" 215 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 216 + 217 + [[package]] 212 218 name = "atomic_enum" 213 219 version = "0.2.0" 214 220 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 240 246 "num-traits", 241 247 "pastey", 242 248 "rayon", 243 - "thiserror", 249 + "thiserror 2.0.18", 244 250 "v_frame", 245 251 "y4m", 246 252 ] ··· 437 443 ] 438 444 439 445 [[package]] 446 + name = "cesu8" 447 + version = "1.1.0" 448 + source = "registry+https://github.com/rust-lang/crates.io-index" 449 + checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 450 + 451 + [[package]] 440 452 name = "cexpr" 441 453 version = "0.6.0" 442 454 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 450 462 version = "1.0.4" 451 463 source = "registry+https://github.com/rust-lang/crates.io-index" 452 464 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 465 + 466 + [[package]] 467 + name = "cfg_aliases" 468 + version = "0.2.1" 469 + source = "registry+https://github.com/rust-lang/crates.io-index" 470 + checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 453 471 454 472 [[package]] 455 473 name = "cfg_block" ··· 527 545 checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 528 546 529 547 [[package]] 548 + name = "combine" 549 + version = "4.6.7" 550 + source = "registry+https://github.com/rust-lang/crates.io-index" 551 + checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 552 + dependencies = [ 553 + "bytes", 554 + "memchr", 555 + ] 556 + 557 + [[package]] 530 558 name = "concurrent-queue" 531 559 version = "2.5.0" 532 560 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 536 564 ] 537 565 538 566 [[package]] 567 + name = "core-foundation" 568 + version = "0.9.4" 569 + source = "registry+https://github.com/rust-lang/crates.io-index" 570 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 571 + dependencies = [ 572 + "core-foundation-sys", 573 + "libc", 574 + ] 575 + 576 + [[package]] 577 + name = "core-foundation" 578 + version = "0.10.1" 579 + source = "registry+https://github.com/rust-lang/crates.io-index" 580 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 581 + dependencies = [ 582 + "core-foundation-sys", 583 + "libc", 584 + ] 585 + 586 + [[package]] 539 587 name = "core-foundation-sys" 540 588 version = "0.8.7" 541 589 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 795 843 checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 796 844 797 845 [[package]] 846 + name = "emojis" 847 + version = "0.8.0" 848 + source = "registry+https://github.com/rust-lang/crates.io-index" 849 + checksum = "50c1c1870b766fc398e5f0526498d09c94b6de15be5fd769a28bbc804fb1b05d" 850 + dependencies = [ 851 + "phf", 852 + ] 853 + 854 + [[package]] 855 + name = "encoding_rs" 856 + version = "0.8.35" 857 + source = "registry+https://github.com/rust-lang/crates.io-index" 858 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 859 + dependencies = [ 860 + "cfg-if", 861 + ] 862 + 863 + [[package]] 798 864 name = "enfync" 799 865 version = "0.1.6" 800 866 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 987 1053 988 1054 [[package]] 989 1055 name = "fluxer-rs" 990 - version = "0.1.2" 1056 + version = "0.1.3" 991 1057 dependencies = [ 1058 + "anyhow", 992 1059 "async-trait", 993 1060 "derive_builder", 1061 + "emojis", 994 1062 "ezsockets", 995 - "image", 996 1063 "log", 997 - "minreq", 1064 + "macros", 1065 + "reqwest", 998 1066 "serde", 999 1067 "serde_json", 1000 1068 "serde_with", 1069 + "thiserror 2.0.18", 1001 1070 "tokio", 1002 1071 ] 1003 1072 ··· 1161 1230 checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 1162 1231 dependencies = [ 1163 1232 "cfg-if", 1233 + "js-sys", 1164 1234 "libc", 1165 1235 "r-efi", 1166 1236 "wasip2", 1237 + "wasm-bindgen", 1167 1238 ] 1168 1239 1169 1240 [[package]] ··· 1219 1290 checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 1220 1291 1221 1292 [[package]] 1293 + name = "h2" 1294 + version = "0.4.13" 1295 + source = "registry+https://github.com/rust-lang/crates.io-index" 1296 + checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" 1297 + dependencies = [ 1298 + "atomic-waker", 1299 + "bytes", 1300 + "fnv", 1301 + "futures-core", 1302 + "futures-sink", 1303 + "http", 1304 + "indexmap 2.13.0", 1305 + "slab", 1306 + "tokio", 1307 + "tokio-util", 1308 + "tracing", 1309 + ] 1310 + 1311 + [[package]] 1222 1312 name = "half" 1223 1313 version = "2.7.1" 1224 1314 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1288 1378 ] 1289 1379 1290 1380 [[package]] 1381 + name = "http-body" 1382 + version = "1.0.1" 1383 + source = "registry+https://github.com/rust-lang/crates.io-index" 1384 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1385 + dependencies = [ 1386 + "bytes", 1387 + "http", 1388 + ] 1389 + 1390 + [[package]] 1391 + name = "http-body-util" 1392 + version = "0.1.3" 1393 + source = "registry+https://github.com/rust-lang/crates.io-index" 1394 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 1395 + dependencies = [ 1396 + "bytes", 1397 + "futures-core", 1398 + "http", 1399 + "http-body", 1400 + "pin-project-lite", 1401 + ] 1402 + 1403 + [[package]] 1291 1404 name = "httparse" 1292 1405 version = "1.10.1" 1293 1406 source = "registry+https://github.com/rust-lang/crates.io-index" 1294 1407 checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1295 1408 1296 1409 [[package]] 1410 + name = "hyper" 1411 + version = "1.8.1" 1412 + source = "registry+https://github.com/rust-lang/crates.io-index" 1413 + checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 1414 + dependencies = [ 1415 + "atomic-waker", 1416 + "bytes", 1417 + "futures-channel", 1418 + "futures-core", 1419 + "h2", 1420 + "http", 1421 + "http-body", 1422 + "httparse", 1423 + "itoa", 1424 + "pin-project-lite", 1425 + "pin-utils", 1426 + "smallvec", 1427 + "tokio", 1428 + "want", 1429 + ] 1430 + 1431 + [[package]] 1432 + name = "hyper-rustls" 1433 + version = "0.27.7" 1434 + source = "registry+https://github.com/rust-lang/crates.io-index" 1435 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1436 + dependencies = [ 1437 + "http", 1438 + "hyper", 1439 + "hyper-util", 1440 + "rustls 0.23.37", 1441 + "rustls-pki-types", 1442 + "tokio", 1443 + "tokio-rustls", 1444 + "tower-service", 1445 + ] 1446 + 1447 + [[package]] 1448 + name = "hyper-util" 1449 + version = "0.1.20" 1450 + source = "registry+https://github.com/rust-lang/crates.io-index" 1451 + checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" 1452 + dependencies = [ 1453 + "base64 0.22.1", 1454 + "bytes", 1455 + "futures-channel", 1456 + "futures-util", 1457 + "http", 1458 + "http-body", 1459 + "hyper", 1460 + "ipnet", 1461 + "libc", 1462 + "percent-encoding", 1463 + "pin-project-lite", 1464 + "socket2", 1465 + "system-configuration", 1466 + "tokio", 1467 + "tower-service", 1468 + "tracing", 1469 + "windows-registry", 1470 + ] 1471 + 1472 + [[package]] 1297 1473 name = "iana-time-zone" 1298 1474 version = "0.1.65" 1299 1475 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1535 1711 ] 1536 1712 1537 1713 [[package]] 1714 + name = "ipnet" 1715 + version = "2.12.0" 1716 + source = "registry+https://github.com/rust-lang/crates.io-index" 1717 + checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" 1718 + 1719 + [[package]] 1720 + name = "iri-string" 1721 + version = "0.7.10" 1722 + source = "registry+https://github.com/rust-lang/crates.io-index" 1723 + checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" 1724 + dependencies = [ 1725 + "memchr", 1726 + "serde", 1727 + ] 1728 + 1729 + [[package]] 1538 1730 name = "is_terminal_polyfill" 1539 1731 version = "1.70.2" 1540 1732 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1589 1781 ] 1590 1782 1591 1783 [[package]] 1784 + name = "jni" 1785 + version = "0.21.1" 1786 + source = "registry+https://github.com/rust-lang/crates.io-index" 1787 + checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 1788 + dependencies = [ 1789 + "cesu8", 1790 + "cfg-if", 1791 + "combine", 1792 + "jni-sys", 1793 + "log", 1794 + "thiserror 1.0.69", 1795 + "walkdir", 1796 + "windows-sys 0.45.0", 1797 + ] 1798 + 1799 + [[package]] 1800 + name = "jni-sys" 1801 + version = "0.3.0" 1802 + source = "registry+https://github.com/rust-lang/crates.io-index" 1803 + checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 1804 + 1805 + [[package]] 1592 1806 name = "jobserver" 1593 1807 version = "0.1.34" 1594 1808 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1741 1955 ] 1742 1956 1743 1957 [[package]] 1958 + name = "lru-slab" 1959 + version = "0.1.2" 1960 + source = "registry+https://github.com/rust-lang/crates.io-index" 1961 + checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 1962 + 1963 + [[package]] 1964 + name = "macros" 1965 + version = "0.1.0" 1966 + dependencies = [ 1967 + "heck", 1968 + "log", 1969 + "proc-macro2", 1970 + "quote", 1971 + "syn 2.0.117", 1972 + ] 1973 + 1974 + [[package]] 1744 1975 name = "matchers" 1745 1976 version = "0.2.0" 1746 1977 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1804 2035 dependencies = [ 1805 2036 "libmimalloc-sys", 1806 2037 ] 2038 + 2039 + [[package]] 2040 + name = "mime" 2041 + version = "0.3.17" 2042 + source = "registry+https://github.com/rust-lang/crates.io-index" 2043 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1807 2044 1808 2045 [[package]] 1809 2046 name = "minimal-lexical" ··· 1966 2203 version = "0.3.1" 1967 2204 source = "registry+https://github.com/rust-lang/crates.io-index" 1968 2205 checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 2206 + 2207 + [[package]] 2208 + name = "openssl-probe" 2209 + version = "0.2.1" 2210 + source = "registry+https://github.com/rust-lang/crates.io-index" 2211 + checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" 1969 2212 1970 2213 [[package]] 1971 2214 name = "pack1" ··· 2018 2261 checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 2019 2262 2020 2263 [[package]] 2264 + name = "phf" 2265 + version = "0.13.1" 2266 + source = "registry+https://github.com/rust-lang/crates.io-index" 2267 + checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" 2268 + dependencies = [ 2269 + "phf_shared", 2270 + ] 2271 + 2272 + [[package]] 2273 + name = "phf_shared" 2274 + version = "0.13.1" 2275 + source = "registry+https://github.com/rust-lang/crates.io-index" 2276 + checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" 2277 + dependencies = [ 2278 + "siphasher", 2279 + ] 2280 + 2281 + [[package]] 2021 2282 name = "pin-project-lite" 2022 2283 version = "0.2.16" 2023 2284 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2199 2460 checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 2200 2461 2201 2462 [[package]] 2463 + name = "quinn" 2464 + version = "0.11.9" 2465 + source = "registry+https://github.com/rust-lang/crates.io-index" 2466 + checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 2467 + dependencies = [ 2468 + "bytes", 2469 + "cfg_aliases", 2470 + "pin-project-lite", 2471 + "quinn-proto", 2472 + "quinn-udp", 2473 + "rustc-hash 2.1.1", 2474 + "rustls 0.23.37", 2475 + "socket2", 2476 + "thiserror 2.0.18", 2477 + "tokio", 2478 + "tracing", 2479 + "web-time", 2480 + ] 2481 + 2482 + [[package]] 2483 + name = "quinn-proto" 2484 + version = "0.11.13" 2485 + source = "registry+https://github.com/rust-lang/crates.io-index" 2486 + checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 2487 + dependencies = [ 2488 + "aws-lc-rs", 2489 + "bytes", 2490 + "getrandom 0.3.4", 2491 + "lru-slab", 2492 + "rand", 2493 + "ring", 2494 + "rustc-hash 2.1.1", 2495 + "rustls 0.23.37", 2496 + "rustls-pki-types", 2497 + "slab", 2498 + "thiserror 2.0.18", 2499 + "tinyvec", 2500 + "tracing", 2501 + "web-time", 2502 + ] 2503 + 2504 + [[package]] 2505 + name = "quinn-udp" 2506 + version = "0.5.14" 2507 + source = "registry+https://github.com/rust-lang/crates.io-index" 2508 + checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 2509 + dependencies = [ 2510 + "cfg_aliases", 2511 + "libc", 2512 + "once_cell", 2513 + "socket2", 2514 + "tracing", 2515 + "windows-sys 0.60.2", 2516 + ] 2517 + 2518 + [[package]] 2202 2519 name = "quote" 2203 2520 version = "1.0.44" 2204 2521 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2290 2607 "rand", 2291 2608 "rand_chacha", 2292 2609 "simd_helpers", 2293 - "thiserror", 2610 + "thiserror 2.0.18", 2294 2611 "v_frame", 2295 2612 "wasm-bindgen", 2296 2613 ] ··· 2389 2706 checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" 2390 2707 2391 2708 [[package]] 2709 + name = "reqwest" 2710 + version = "0.13.2" 2711 + source = "registry+https://github.com/rust-lang/crates.io-index" 2712 + checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" 2713 + dependencies = [ 2714 + "base64 0.22.1", 2715 + "bytes", 2716 + "encoding_rs", 2717 + "futures-core", 2718 + "h2", 2719 + "http", 2720 + "http-body", 2721 + "http-body-util", 2722 + "hyper", 2723 + "hyper-rustls", 2724 + "hyper-util", 2725 + "js-sys", 2726 + "log", 2727 + "mime", 2728 + "percent-encoding", 2729 + "pin-project-lite", 2730 + "quinn", 2731 + "rustls 0.23.37", 2732 + "rustls-pki-types", 2733 + "rustls-platform-verifier", 2734 + "sync_wrapper", 2735 + "tokio", 2736 + "tokio-rustls", 2737 + "tower", 2738 + "tower-http", 2739 + "tower-service", 2740 + "url", 2741 + "wasm-bindgen", 2742 + "wasm-bindgen-futures", 2743 + "web-sys", 2744 + ] 2745 + 2746 + [[package]] 2392 2747 name = "rgb" 2393 2748 version = "0.8.52" 2394 2749 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2479 2834 2480 2835 [[package]] 2481 2836 name = "rustls" 2482 - version = "0.23.36" 2837 + version = "0.23.37" 2483 2838 source = "registry+https://github.com/rust-lang/crates.io-index" 2484 - checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" 2839 + checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" 2485 2840 dependencies = [ 2486 2841 "aws-lc-rs", 2487 2842 "log", ··· 2493 2848 ] 2494 2849 2495 2850 [[package]] 2851 + name = "rustls-native-certs" 2852 + version = "0.8.3" 2853 + source = "registry+https://github.com/rust-lang/crates.io-index" 2854 + checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" 2855 + dependencies = [ 2856 + "openssl-probe", 2857 + "rustls-pki-types", 2858 + "schannel", 2859 + "security-framework", 2860 + ] 2861 + 2862 + [[package]] 2496 2863 name = "rustls-pki-types" 2497 2864 version = "1.14.0" 2498 2865 source = "registry+https://github.com/rust-lang/crates.io-index" 2499 2866 checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" 2500 2867 dependencies = [ 2868 + "web-time", 2501 2869 "zeroize", 2502 2870 ] 2503 2871 2504 2872 [[package]] 2873 + name = "rustls-platform-verifier" 2874 + version = "0.6.2" 2875 + source = "registry+https://github.com/rust-lang/crates.io-index" 2876 + checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" 2877 + dependencies = [ 2878 + "core-foundation 0.10.1", 2879 + "core-foundation-sys", 2880 + "jni", 2881 + "log", 2882 + "once_cell", 2883 + "rustls 0.23.37", 2884 + "rustls-native-certs", 2885 + "rustls-platform-verifier-android", 2886 + "rustls-webpki 0.103.9", 2887 + "security-framework", 2888 + "security-framework-sys", 2889 + "webpki-root-certs", 2890 + "windows-sys 0.61.2", 2891 + ] 2892 + 2893 + [[package]] 2894 + name = "rustls-platform-verifier-android" 2895 + version = "0.1.1" 2896 + source = "registry+https://github.com/rust-lang/crates.io-index" 2897 + checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" 2898 + 2899 + [[package]] 2505 2900 name = "rustls-webpki" 2506 2901 version = "0.101.7" 2507 2902 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2536 2931 checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 2537 2932 2538 2933 [[package]] 2934 + name = "same-file" 2935 + version = "1.0.6" 2936 + source = "registry+https://github.com/rust-lang/crates.io-index" 2937 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 2938 + dependencies = [ 2939 + "winapi-util", 2940 + ] 2941 + 2942 + [[package]] 2943 + name = "schannel" 2944 + version = "0.1.28" 2945 + source = "registry+https://github.com/rust-lang/crates.io-index" 2946 + checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 2947 + dependencies = [ 2948 + "windows-sys 0.61.2", 2949 + ] 2950 + 2951 + [[package]] 2539 2952 name = "schemars" 2540 2953 version = "0.9.0" 2541 2954 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2573 2986 dependencies = [ 2574 2987 "ring", 2575 2988 "untrusted", 2989 + ] 2990 + 2991 + [[package]] 2992 + name = "security-framework" 2993 + version = "3.7.0" 2994 + source = "registry+https://github.com/rust-lang/crates.io-index" 2995 + checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" 2996 + dependencies = [ 2997 + "bitflags", 2998 + "core-foundation 0.10.1", 2999 + "core-foundation-sys", 3000 + "libc", 3001 + "security-framework-sys", 3002 + ] 3003 + 3004 + [[package]] 3005 + name = "security-framework-sys" 3006 + version = "2.17.0" 3007 + source = "registry+https://github.com/rust-lang/crates.io-index" 3008 + checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" 3009 + dependencies = [ 3010 + "core-foundation-sys", 3011 + "libc", 2576 3012 ] 2577 3013 2578 3014 [[package]] ··· 2808 3244 ] 2809 3245 2810 3246 [[package]] 3247 + name = "sync_wrapper" 3248 + version = "1.0.2" 3249 + source = "registry+https://github.com/rust-lang/crates.io-index" 3250 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3251 + dependencies = [ 3252 + "futures-core", 3253 + ] 3254 + 3255 + [[package]] 2811 3256 name = "synstructure" 2812 3257 version = "0.13.2" 2813 3258 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2819 3264 ] 2820 3265 2821 3266 [[package]] 3267 + name = "system-configuration" 3268 + version = "0.7.0" 3269 + source = "registry+https://github.com/rust-lang/crates.io-index" 3270 + checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" 3271 + dependencies = [ 3272 + "bitflags", 3273 + "core-foundation 0.9.4", 3274 + "system-configuration-sys", 3275 + ] 3276 + 3277 + [[package]] 3278 + name = "system-configuration-sys" 3279 + version = "0.6.0" 3280 + source = "registry+https://github.com/rust-lang/crates.io-index" 3281 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 3282 + dependencies = [ 3283 + "core-foundation-sys", 3284 + "libc", 3285 + ] 3286 + 3287 + [[package]] 2822 3288 name = "tempfile" 2823 3289 version = "3.25.0" 2824 3290 source = "registry+https://github.com/rust-lang/crates.io-index" 2825 3291 checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" 2826 3292 dependencies = [ 2827 3293 "fastrand", 2828 - "getrandom 0.3.4", 3294 + "getrandom 0.4.1", 2829 3295 "once_cell", 2830 3296 "rustix 1.1.4", 2831 3297 "windows-sys 0.61.2", ··· 2833 3299 2834 3300 [[package]] 2835 3301 name = "thiserror" 3302 + version = "1.0.69" 3303 + source = "registry+https://github.com/rust-lang/crates.io-index" 3304 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 3305 + dependencies = [ 3306 + "thiserror-impl 1.0.69", 3307 + ] 3308 + 3309 + [[package]] 3310 + name = "thiserror" 2836 3311 version = "2.0.18" 2837 3312 source = "registry+https://github.com/rust-lang/crates.io-index" 2838 3313 checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" 2839 3314 dependencies = [ 2840 - "thiserror-impl", 3315 + "thiserror-impl 2.0.18", 3316 + ] 3317 + 3318 + [[package]] 3319 + name = "thiserror-impl" 3320 + version = "1.0.69" 3321 + source = "registry+https://github.com/rust-lang/crates.io-index" 3322 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 3323 + dependencies = [ 3324 + "proc-macro2", 3325 + "quote", 3326 + "syn 2.0.117", 2841 3327 ] 2842 3328 2843 3329 [[package]] ··· 2916 3402 ] 2917 3403 2918 3404 [[package]] 3405 + name = "tinyvec" 3406 + version = "1.10.0" 3407 + source = "registry+https://github.com/rust-lang/crates.io-index" 3408 + checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" 3409 + dependencies = [ 3410 + "tinyvec_macros", 3411 + ] 3412 + 3413 + [[package]] 3414 + name = "tinyvec_macros" 3415 + version = "0.1.1" 3416 + source = "registry+https://github.com/rust-lang/crates.io-index" 3417 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 3418 + 3419 + [[package]] 2919 3420 name = "tokio" 2920 3421 version = "1.49.0" 2921 3422 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2947 3448 source = "registry+https://github.com/rust-lang/crates.io-index" 2948 3449 checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 2949 3450 dependencies = [ 2950 - "rustls 0.23.36", 3451 + "rustls 0.23.37", 2951 3452 "tokio", 2952 3453 ] 2953 3454 ··· 2959 3460 dependencies = [ 2960 3461 "futures-util", 2961 3462 "log", 2962 - "rustls 0.23.36", 3463 + "rustls 0.23.37", 2963 3464 "rustls-pki-types", 2964 3465 "tokio", 2965 3466 "tokio-rustls", ··· 2979 3480 "http", 2980 3481 "httparse", 2981 3482 "js-sys", 2982 - "thiserror", 3483 + "thiserror 2.0.18", 2983 3484 "tokio", 2984 3485 "tokio-tungstenite", 2985 3486 "wasm-bindgen", ··· 2987 3488 ] 2988 3489 2989 3490 [[package]] 3491 + name = "tokio-util" 3492 + version = "0.7.18" 3493 + source = "registry+https://github.com/rust-lang/crates.io-index" 3494 + checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 3495 + dependencies = [ 3496 + "bytes", 3497 + "futures-core", 3498 + "futures-sink", 3499 + "pin-project-lite", 3500 + "tokio", 3501 + ] 3502 + 3503 + [[package]] 3504 + name = "tower" 3505 + version = "0.5.3" 3506 + source = "registry+https://github.com/rust-lang/crates.io-index" 3507 + checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 3508 + dependencies = [ 3509 + "futures-core", 3510 + "futures-util", 3511 + "pin-project-lite", 3512 + "sync_wrapper", 3513 + "tokio", 3514 + "tower-layer", 3515 + "tower-service", 3516 + ] 3517 + 3518 + [[package]] 3519 + name = "tower-http" 3520 + version = "0.6.8" 3521 + source = "registry+https://github.com/rust-lang/crates.io-index" 3522 + checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" 3523 + dependencies = [ 3524 + "bitflags", 3525 + "bytes", 3526 + "futures-util", 3527 + "http", 3528 + "http-body", 3529 + "iri-string", 3530 + "pin-project-lite", 3531 + "tower", 3532 + "tower-layer", 3533 + "tower-service", 3534 + ] 3535 + 3536 + [[package]] 3537 + name = "tower-layer" 3538 + version = "0.3.3" 3539 + source = "registry+https://github.com/rust-lang/crates.io-index" 3540 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 3541 + 3542 + [[package]] 3543 + name = "tower-service" 3544 + version = "0.3.3" 3545 + source = "registry+https://github.com/rust-lang/crates.io-index" 3546 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 3547 + 3548 + [[package]] 2990 3549 name = "tracing" 2991 3550 version = "0.1.44" 2992 3551 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3004 3563 checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" 3005 3564 dependencies = [ 3006 3565 "crossbeam-channel", 3007 - "thiserror", 3566 + "thiserror 2.0.18", 3008 3567 "time", 3009 3568 "tracing-subscriber", 3010 3569 ] ··· 3060 3619 ] 3061 3620 3062 3621 [[package]] 3622 + name = "try-lock" 3623 + version = "0.2.5" 3624 + source = "registry+https://github.com/rust-lang/crates.io-index" 3625 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3626 + 3627 + [[package]] 3063 3628 name = "tungstenite" 3064 3629 version = "0.26.2" 3065 3630 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3071 3636 "httparse", 3072 3637 "log", 3073 3638 "rand", 3074 - "rustls 0.23.36", 3639 + "rustls 0.23.37", 3075 3640 "rustls-pki-types", 3076 3641 "sha1", 3077 - "thiserror", 3642 + "thiserror 2.0.18", 3078 3643 "utf-8", 3079 3644 ] 3080 3645 ··· 3085 3650 checksum = "1f2fe423c2c954948babb36edda12b737e321d8541d4eae519694f7d512ecab6" 3086 3651 dependencies = [ 3087 3652 "mimalloc", 3088 - "thiserror", 3653 + "thiserror 2.0.18", 3089 3654 "tracing", 3090 3655 "tracing-subscriber", 3091 3656 "turso_sdk_kit", ··· 3136 3701 "strum", 3137 3702 "strum_macros", 3138 3703 "tempfile", 3139 - "thiserror", 3704 + "thiserror 2.0.18", 3140 3705 "tracing", 3141 3706 "tracing-subscriber", 3142 3707 "turso_ext", ··· 3180 3745 "miette", 3181 3746 "strum", 3182 3747 "strum_macros", 3183 - "thiserror", 3748 + "thiserror 2.0.18", 3184 3749 "turso_macros", 3185 3750 ] 3186 3751 ··· 3225 3790 "roaring", 3226 3791 "serde", 3227 3792 "serde_json", 3228 - "thiserror", 3793 + "thiserror 2.0.18", 3229 3794 "tracing", 3230 3795 "turso_core", 3231 3796 "turso_parser", ··· 3381 3946 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 3382 3947 3383 3948 [[package]] 3949 + name = "walkdir" 3950 + version = "2.5.0" 3951 + source = "registry+https://github.com/rust-lang/crates.io-index" 3952 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 3953 + dependencies = [ 3954 + "same-file", 3955 + "winapi-util", 3956 + ] 3957 + 3958 + [[package]] 3959 + name = "want" 3960 + version = "0.3.1" 3961 + source = "registry+https://github.com/rust-lang/crates.io-index" 3962 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 3963 + dependencies = [ 3964 + "try-lock", 3965 + ] 3966 + 3967 + [[package]] 3384 3968 name = "wasi" 3385 3969 version = "0.11.1+wasi-snapshot-preview1" 3386 3970 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3522 4106 ] 3523 4107 3524 4108 [[package]] 4109 + name = "web-time" 4110 + version = "1.1.0" 4111 + source = "registry+https://github.com/rust-lang/crates.io-index" 4112 + checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 4113 + dependencies = [ 4114 + "js-sys", 4115 + "wasm-bindgen", 4116 + ] 4117 + 4118 + [[package]] 4119 + name = "webpki-root-certs" 4120 + version = "1.0.6" 4121 + source = "registry+https://github.com/rust-lang/crates.io-index" 4122 + checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" 4123 + dependencies = [ 4124 + "rustls-pki-types", 4125 + ] 4126 + 4127 + [[package]] 3525 4128 name = "webpki-roots" 3526 4129 version = "0.25.4" 3527 4130 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3564 4167 ] 3565 4168 3566 4169 [[package]] 4170 + name = "winapi-util" 4171 + version = "0.1.11" 4172 + source = "registry+https://github.com/rust-lang/crates.io-index" 4173 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 4174 + dependencies = [ 4175 + "windows-sys 0.61.2", 4176 + ] 4177 + 4178 + [[package]] 3567 4179 name = "windows-core" 3568 4180 version = "0.62.2" 3569 4181 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3605 4217 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 3606 4218 3607 4219 [[package]] 4220 + name = "windows-registry" 4221 + version = "0.6.1" 4222 + source = "registry+https://github.com/rust-lang/crates.io-index" 4223 + checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 4224 + dependencies = [ 4225 + "windows-link", 4226 + "windows-result", 4227 + "windows-strings", 4228 + ] 4229 + 4230 + [[package]] 3608 4231 name = "windows-result" 3609 4232 version = "0.4.1" 3610 4233 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3620 4243 checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 3621 4244 dependencies = [ 3622 4245 "windows-link", 4246 + ] 4247 + 4248 + [[package]] 4249 + name = "windows-sys" 4250 + version = "0.45.0" 4251 + source = "registry+https://github.com/rust-lang/crates.io-index" 4252 + checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 4253 + dependencies = [ 4254 + "windows-targets 0.42.2", 3623 4255 ] 3624 4256 3625 4257 [[package]] ··· 3651 4283 3652 4284 [[package]] 3653 4285 name = "windows-targets" 4286 + version = "0.42.2" 4287 + source = "registry+https://github.com/rust-lang/crates.io-index" 4288 + checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 4289 + dependencies = [ 4290 + "windows_aarch64_gnullvm 0.42.2", 4291 + "windows_aarch64_msvc 0.42.2", 4292 + "windows_i686_gnu 0.42.2", 4293 + "windows_i686_msvc 0.42.2", 4294 + "windows_x86_64_gnu 0.42.2", 4295 + "windows_x86_64_gnullvm 0.42.2", 4296 + "windows_x86_64_msvc 0.42.2", 4297 + ] 4298 + 4299 + [[package]] 4300 + name = "windows-targets" 3654 4301 version = "0.52.6" 3655 4302 source = "registry+https://github.com/rust-lang/crates.io-index" 3656 4303 checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" ··· 3684 4331 3685 4332 [[package]] 3686 4333 name = "windows_aarch64_gnullvm" 4334 + version = "0.42.2" 4335 + source = "registry+https://github.com/rust-lang/crates.io-index" 4336 + checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 4337 + 4338 + [[package]] 4339 + name = "windows_aarch64_gnullvm" 3687 4340 version = "0.52.6" 3688 4341 source = "registry+https://github.com/rust-lang/crates.io-index" 3689 4342 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" ··· 3696 4349 3697 4350 [[package]] 3698 4351 name = "windows_aarch64_msvc" 4352 + version = "0.42.2" 4353 + source = "registry+https://github.com/rust-lang/crates.io-index" 4354 + checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 4355 + 4356 + [[package]] 4357 + name = "windows_aarch64_msvc" 3699 4358 version = "0.52.6" 3700 4359 source = "registry+https://github.com/rust-lang/crates.io-index" 3701 4360 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" ··· 3705 4364 version = "0.53.1" 3706 4365 source = "registry+https://github.com/rust-lang/crates.io-index" 3707 4366 checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 4367 + 4368 + [[package]] 4369 + name = "windows_i686_gnu" 4370 + version = "0.42.2" 4371 + source = "registry+https://github.com/rust-lang/crates.io-index" 4372 + checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 3708 4373 3709 4374 [[package]] 3710 4375 name = "windows_i686_gnu" ··· 3732 4397 3733 4398 [[package]] 3734 4399 name = "windows_i686_msvc" 4400 + version = "0.42.2" 4401 + source = "registry+https://github.com/rust-lang/crates.io-index" 4402 + checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 4403 + 4404 + [[package]] 4405 + name = "windows_i686_msvc" 3735 4406 version = "0.52.6" 3736 4407 source = "registry+https://github.com/rust-lang/crates.io-index" 3737 4408 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" ··· 3744 4415 3745 4416 [[package]] 3746 4417 name = "windows_x86_64_gnu" 4418 + version = "0.42.2" 4419 + source = "registry+https://github.com/rust-lang/crates.io-index" 4420 + checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 4421 + 4422 + [[package]] 4423 + name = "windows_x86_64_gnu" 3747 4424 version = "0.52.6" 3748 4425 source = "registry+https://github.com/rust-lang/crates.io-index" 3749 4426 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" ··· 3756 4433 3757 4434 [[package]] 3758 4435 name = "windows_x86_64_gnullvm" 4436 + version = "0.42.2" 4437 + source = "registry+https://github.com/rust-lang/crates.io-index" 4438 + checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 4439 + 4440 + [[package]] 4441 + name = "windows_x86_64_gnullvm" 3759 4442 version = "0.52.6" 3760 4443 source = "registry+https://github.com/rust-lang/crates.io-index" 3761 4444 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" ··· 3765 4448 version = "0.53.1" 3766 4449 source = "registry+https://github.com/rust-lang/crates.io-index" 3767 4450 checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 4451 + 4452 + [[package]] 4453 + name = "windows_x86_64_msvc" 4454 + version = "0.42.2" 4455 + source = "registry+https://github.com/rust-lang/crates.io-index" 4456 + checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 3768 4457 3769 4458 [[package]] 3770 4459 name = "windows_x86_64_msvc"
+8 -7
Cargo.toml
··· 1 1 [package] 2 2 name = "fluxer-rs" 3 - version = "0.1.2" 3 + version = "0.1.3" 4 4 edition = "2024" 5 5 license = "MIT" 6 - description = "A rust implementation of the fluxer api to be used on fluxer.app and other self hosted instances" 6 + description = "A rust framework for interacting with fluxer instances" 7 7 homepage = "https://git.killuaa.dev/roufpup/fluxer-rs" 8 8 repository = "https://git.killuaa.dev/roufpup/fluxer-rs" 9 9 readme = "README.md" 10 10 11 11 [workspace] 12 - members = [ 13 - "examples/examplebot", 14 - ] 12 + members = ["examples/examplebot", "macros"] 15 13 16 14 [dependencies] 15 + anyhow = "1.0.102" 17 16 async-trait = "0.1.89" 18 17 derive_builder = "0.20.2" 19 - image = "0.25.9" 20 18 log = "0.4.29" 21 - minreq = { version = "2.14.1", features = ["https"] } 22 19 serde_json = "1.0.149" 23 20 serde_with = "3.16.1" 21 + macros = { path = "./macros" } 22 + thiserror = "2.0.18" 23 + reqwest = "0.13.2" 24 + emojis = "0.8.0" 24 25 25 26 [dependencies.ezsockets] 26 27 version = "0.7.1"
+86 -70
README.md
··· 1 1 # About 2 - A rust implementation of the fluxer api to be used on fluxer.app and other self hosted instances 2 + A rust framework for interacting with fluxer instances 3 + 4 + !!! Expect breaking changes !!! 3 5 4 6 For opening issues and feature requests please head over to the MIRROR repo over on https://github.com/roufpup/fluxer-rs 5 7 ··· 10 12 # Example usage 11 13 12 14 ```rs 15 + // For more verbose messages like from the https client choose debug instead of info 16 + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 13 17 14 - let bot: FluxerBot = FluxerBot::init( 15 - "<Your bot token here>".to_string(), 16 - "wss://gateway.fluxer.app?v=1&encoding=json&compress=none".to_string(), 17 - "https://api.fluxer.app/v1".to_string(), 18 - ) 19 - .await; 18 + let bot = FluxerBot::init( 19 + "<your bot token here>", 20 + "wss://gateway.fluxer.app?v=1&encoding=json&compress=none", 21 + "https://api.fluxer.app/v1", 22 + )?; 20 23 21 - let bot_arc = Arc::new(bot); 22 - bot_arc 23 - .start(ColorbotDispatchHandler { 24 - bot: bot_arc.clone(), 25 - }) 26 - .await; 24 + bot.start(ColorbotDispatchHandler {}).await; 27 25 ``` 28 - We pass an Arc of the bot to the dispatch handler implementation so we can use the api of our above created bot inside different dispatch callbacks as shown below 26 + Each client using this crate must create their own dispatch handler by implementing the trait `DispatchHandlerTrait`. Once you do you can override the default functions of the dispatch handler that will be called for each dispatch event. The default functions are generated with a macro, and the name of each function follows this pattern: "handle_{}_dispatch" where {} is the name of the dispatch event in snake case. So the `MESSAGE_CREATE` dispatch event would have a function by the name of `handle_message_create_dispatch` 29 27 30 28 ```rs 31 - 32 - pub struct ColorbotDispatchHandler { 33 - pub bot: Arc<FluxerBot>, 34 - } 29 + pub struct ColorbotDispatchHandler {} 35 30 36 31 impl DispatchHandlerTrait for ColorbotDispatchHandler { 37 - async fn handle_message_create_dispatch(&self, data: MessageEventData) { 32 + async fn handle_message_create_dispatch( 33 + &self, 34 + data: MessageData, 35 + api: &FluxerApiHandler, 36 + ) -> Result<(), FluxerRsError> { 38 37 let mut cmd_handler = CommandHandler::init("!".to_string()); 39 38 40 - cmd_handler.register_command( 41 - "ping".to_string(), 42 - PingCommand { 43 - bot: self.bot.clone(), 44 - channel_id: data.channel_id.clone(), 45 - id: data.id.clone(), 46 - }, 47 - ); 39 + register_commands!(cmd_handler,[ 40 + {"addrole", AddRoleCommand}, 41 + {"ping", PingCommand}, 42 + {"edit", EditCommand}, 43 + {"react", ReactCommand}, 44 + {"removereact", RemoveReactCommand}, 45 + {"removerole", RemoveRoleCommand}, 46 + {"createrole", CreateRoleCommand}, 47 + {"deleterole", DeleteRoleCommand}, 48 + ]); 48 49 49 - cmd_handler.handle(&data).await; 50 + cmd_handler.handle(&data, api).await 50 51 } 52 + } 51 53 ``` 52 - This is also another thing to touch on how to reply to different dispatch events. All you need to do is implement the DispatchHandlerTrait for your struct and implement the functions that you want to override instead of letting the crate use the predefined default ones. 53 - 54 - This crate also will provide some high level implementations bots use in general, currently there only is a CommandHandler which will let you easily register custom commands for your bots. 54 + This crate also will provide some high level implementations bots use in general, currently there only is a CommandHandler which will let you easily register custom commands for your bots as shown partially in the above example. 55 55 56 56 Here is an example of how to make a command for the CommandHandler 57 57 58 58 ```rs 59 - pub struct PingCommand { 60 - pub bot: Arc<FluxerBot>, 61 - pub channel_id: String, 62 - pub id: String, 63 - } 59 + use fluxer_rs::{ 60 + api::{common::send_reply}, 61 + command, 62 + }; 64 63 65 - impl CommandTrait for PingCommand { 66 - async fn execute(&self) { 67 - let _ = self.bot.api.execute_call( 68 - SendMessageBuilder::default() 69 - .channel_id(self.channel_id.clone()) 70 - .content("pong".to_string()) 71 - .message_reference( 72 - MessageReferenceBuilder::default() 73 - .message_id(self.id.clone()) 74 - .build() 75 - .unwrap(), 76 - ) 77 - .build() 78 - .unwrap(), 79 - ); 80 - } 64 + #[command(PingCommand)] 65 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 66 + let data = feedback.data; 67 + 68 + send_reply(api, &data.channel_id, &data.id, "pong").await?; 69 + Ok(()) 81 70 } 82 71 ``` 83 - When creating a command we pass all the required data into the command struct and just implement the CommandTrait for it. In this example the execute function will simply send a message back to the user by replying to their original message with `pong` 72 + When creating a command, all you have to do is use the command macro and pass the command name which you later register to your dispatch handler. The signiture of the function must be the same as you see it in the example, an async function that takes two references, an api handler and a command feedback. 73 + 74 + This crate also implements some basic functions that would be used a lot of the time which can be found under `fluxer_rs::api::common`, so you won't have to build an api call every time. 84 75 85 76 Of course you don't have to use this and are free to make your own better custom handler for commands :p 86 77 87 78 # Dispatch events 88 - Not all dispatch events are implemented currently as i am limited on time for how much i can do in such a short period of time, but i will be implementing them constantly as much as possible as fast as possible. 79 + Not all op codes are implemented currently, and not all dispatch events have been registered yet. The reason for the latter is that i choose for the most part to implement data structure myself instead of generating it from a spec. So whenever an unknown dispatch event hits it would print the json that was attempted to be sent, then i implement the structure from it. Which leads to the next paragraph. 89 80 90 - When developing a bot in debug mode, be wary as there is a certain panic function that will occur when an unimplemented dispatch event occurs. It's there by design for my own ease of use ( So i have no choice but to implement the event if i don't want constant crashing ;3). While it's not optimal i do advise to use release mode for now while still a bunch of dispatch events are missing. 81 + When developing a bot in debug mode, be wary as there is a certain panic that will occur when an unimplemented dispatch event occurs. It's there by design for my own ease of use ( So i have no choice but to implement the event if i don't want constant crashing ;3). While it's not optimal i do advise to use release mode for now while still a bunch of dispatch events are missing as it will only send an error log in the terminal for said dispatch event. 91 82 92 83 # API 93 84 As far as API calls go there are very few implemented at the moment but are quite useful ones like: ··· 104 95 Whenever an api call is missing but you really want to use it be not afraid you can also implement an API call on your own in the meanwhile until it gets added. Here is an example: 105 96 106 97 ```rs 107 - #[derive(Clone, Builder)] 98 + #[derive(Clone, Debug, Builder)] 108 99 #[builder(try_setter, setter(into))] 109 100 pub struct EditMessage { 110 - // Path params 111 101 pub channel_id: String, 112 102 pub message_id: String, 113 103 114 104 pub content: String, 115 105 #[builder(default)] 116 106 pub embeds: Option<Vec<Embed>>, 117 - #[builder(default)] 118 - pub message_reference: Option<MessageReference>, 119 107 } 120 108 109 + impl ApiCall for EditMessage { 110 + type ReturnType = MessageData; 121 111 122 - impl ApiCall for EditMessage { 123 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 124 - let body = serde_json::to_string(self).unwrap(); 125 - info!("BODY CHECK {body}"); 126 - req.with_body(body) 127 - .with_header("Authorization", format!("Bot {token}")) 112 + fn get_req( 113 + &self, 114 + req: reqwest::RequestBuilder, 115 + token: &str, 116 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 117 + let value = serde_json::to_string(self)?; 118 + 119 + Ok(req 120 + .body(value) 121 + .header("Authorization", format!("Bot {token}"))) 128 122 } 129 123 130 124 fn get_info(&self) -> (String, FluxerApiCallType) { ··· 133 127 FluxerApiCallType::Patch, 134 128 ) 135 129 } 130 + 131 + fn get_data(&self, body: &str) -> Result<Self::ReturnType, FluxerRsError> { 132 + let value = serde_json::from_str::<MessageData>(body)?; 133 + Ok(value) 134 + } 136 135 } 137 136 138 137 impl Serialize for EditMessage { ··· 145 144 if let Some(embed) = self.embeds.clone() { 146 145 state.serialize_entry("embeds", &embed)?; 147 146 }; 148 - if let Some(message_reference) = self.message_reference.clone() { 149 - state.serialize_entry("message_reference", &message_reference)?; 150 - } 151 147 state.end() 152 148 } 153 149 } 154 150 ``` 155 151 What you basically need to do is implement the ApiCall trait for the struct that will serve as your storage for parameters and potentially body data if required. 156 152 157 - In this example taken right out of the crate you implement two functions for the struct, `get_req` and `get_info`, in the first one you construct the request with all the headers and body you need and return it. While in the second you set some information for the api call like the path that will get called which will include also the path parameters taken out of the struct as well as the http request method type. 153 + In this example taken right out of the crate you implement three functions for the struct, `get_req`, `get_info` and `get_data`, in the first one you construct the request with all the headers and body you need and return it. In the second you set some information for the api call like the path that will get called which will include also the path parameters taken out of the struct, and the http request method type. In the last function you return the data type that the api call would usually return. In a lot of cases that would be nothing then you just need to set `type ReturnType = ();` in your implementation. But in this case the api will return `MessageData`, so we deserialize the body and return it so it can be given to the client using the crate. 158 154 159 155 I've added a bit of custom serialization to make everything a bit easier when using a struct that shares both the serializable data and the path parameter data 160 156 161 157 If you'd like to implement an api call exactly like this in your own app you will have to add the `derive_builder` crate as a dependency as that is what provides the builder macros and generation. 162 158 163 - After you have implemented the api call you simply call it like in the example above for the PingCommand, the name of the builder is the same as the struct in this case it will be `EditMessageBuilder` 159 + After you have implemented the api call you simply call it like the common api functions do (see example below), the name of the builder is the same as the struct in this case it will be `EditMessageBuilder` 160 + 161 + ```rs 162 + pub async fn edit_message( 163 + api: &FluxerApiHandler, 164 + channel_id: &str, 165 + message_id: &str, 166 + content: &str, 167 + ) -> Result<MessageData, FluxerRsError> { 168 + let call = EditMessageBuilder::default() 169 + .channel_id(channel_id) 170 + .message_id(message_id) 171 + .content(content) 172 + .build() 173 + .map_err(ApiHandlerError::from)?; 174 + 175 + let result = api.execute_call(call).await?; 176 + 177 + Ok(result) 178 + } 179 + ```
+18 -36
examples/examplebot/src/commands/addrole.rs
··· 1 - use std::sync::Arc; 1 + use fluxer_rs::api::common::{give_role, send_message}; 2 + use fluxer_rs::command; 2 3 3 - use fluxer_rs::{ 4 - api::data_structure::{message::SendMessageBuilder, role::AddRoleToMemberBuilder}, 5 - fluxerbot::FluxerBot, 6 - high_level::command_handler::{CommandHandler, CommandTrait}, 7 - }; 4 + #[command(AddRoleCommand)] 5 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 6 + let data = feedback.data; 7 + let args = &feedback.args; 8 8 9 - pub struct AddRoleCommand { 10 - pub bot: Arc<FluxerBot>, 11 - pub channel_id: String, 12 - pub content: String, 13 - } 9 + if args.len() != 3 { 10 + send_message(api, &data.channel_id, "Invalid syntax").await?; 11 + return Ok(()); 12 + } 14 13 15 - impl CommandTrait for AddRoleCommand { 16 - async fn execute(&self) { 17 - if let Some((_, body)) = CommandHandler::remove_pfx("!", &self.content).await { 18 - let body_split = body.split(" ").collect::<Vec<&str>>(); 19 - 20 - if body_split.len() != 3 || body.is_empty() { 21 - let _ = self.bot.api.execute_call( 22 - SendMessageBuilder::default() 23 - .channel_id(self.channel_id.clone()) 24 - .content("Invalid syntax") 25 - .build() 26 - .unwrap(), 27 - ); 28 - return; 29 - } 14 + give_role( 15 + api, 16 + args.first().unwrap(), 17 + args.get(1).unwrap(), 18 + args.get(2).unwrap(), 19 + ) 20 + .await?; 30 21 31 - let _ = self.bot.api.execute_call( 32 - AddRoleToMemberBuilder::default() 33 - .guild_id(body_split.first().unwrap().to_string()) 34 - .user_id(body_split.get(1).unwrap().to_string()) 35 - .role_id(body_split.get(2).unwrap().to_string()) 36 - .build() 37 - .unwrap(), 38 - ); 39 - } 40 - } 22 + Ok(()) 41 23 }
+19 -35
examples/examplebot/src/commands/createrole.rs
··· 1 - use std::sync::Arc; 2 - 3 1 use fluxer_rs::{ 4 - api::data_structure::{message::SendMessageBuilder, role::CreateRoleBuilder}, 5 - fluxerbot::FluxerBot, 6 - high_level::command_handler::{CommandHandler, CommandTrait}, 2 + api::common::{create_role, send_message}, 3 + command, 7 4 }; 8 5 9 - pub struct CreateRoleCommand { 10 - pub bot: Arc<FluxerBot>, 11 - pub channel_id: String, 12 - pub content: String, 13 - } 6 + #[command(CreateRoleCommand)] 7 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 + let data = feedback.data; 9 + let args = &feedback.args; 14 10 15 - impl CommandTrait for CreateRoleCommand { 16 - async fn execute(&self) { 17 - if let Some((_, body)) = CommandHandler::remove_pfx("!", &self.content).await { 18 - let body_split = body.split(" ").collect::<Vec<&str>>(); 11 + if args.len() != 4 { 12 + send_message(api, &data.channel_id, "Invalid syntax").await?; 13 + return Ok(()); 14 + } 19 15 20 - if body_split.len() != 4 || body.is_empty() { 21 - let _ = self.bot.api.execute_call( 22 - SendMessageBuilder::default() 23 - .channel_id(self.channel_id.clone()) 24 - .content("Invalid syntax") 25 - .build() 26 - .unwrap(), 27 - ); 28 - return; 29 - } 16 + create_role( 17 + api, 18 + args.first().unwrap(), 19 + args.get(1).unwrap(), 20 + args.get(2).unwrap(), 21 + args.get(3).unwrap(), 22 + ) 23 + .await?; 30 24 31 - let _ = self.bot.api.execute_call( 32 - CreateRoleBuilder::default() 33 - .name(body_split.first().unwrap().to_string()) 34 - .guild_id(body_split.get(1).unwrap().to_string()) 35 - .permission(body_split.get(2).unwrap().to_string()) 36 - .color(u32::from_str_radix(body_split.get(3).unwrap(), 16).unwrap()) 37 - .build() 38 - .unwrap(), 39 - ); 40 - } 41 - } 25 + Ok(()) 42 26 }
+12 -33
examples/examplebot/src/commands/deleterole.rs
··· 1 - use std::sync::Arc; 2 - 3 1 use fluxer_rs::{ 4 - api::data_structure::{message::SendMessageBuilder, role::DeleteRoleBuilder}, 5 - fluxerbot::FluxerBot, 6 - high_level::command_handler::{CommandHandler, CommandTrait}, 2 + api::common::{delete_role, send_message}, 3 + command, 7 4 }; 8 5 9 - pub struct DeleteRoleCommand { 10 - pub bot: Arc<FluxerBot>, 11 - pub channel_id: String, 12 - pub content: String, 13 - } 6 + #[command(DeleteRoleCommand)] 7 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 + let data = feedback.data; 9 + let args = &feedback.args; 14 10 15 - impl CommandTrait for DeleteRoleCommand { 16 - async fn execute(&self) { 17 - if let Some((_, body)) = CommandHandler::remove_pfx("!", &self.content).await { 18 - let body_split = body.split(" ").collect::<Vec<&str>>(); 11 + if args.len() != 2 || args.is_empty() { 12 + send_message(api, &data.channel_id, "Invalid syntax").await?; 13 + return Ok(()); 14 + } 19 15 20 - if body_split.len() != 2 || body.is_empty() { 21 - let _ = self.bot.api.execute_call( 22 - SendMessageBuilder::default() 23 - .channel_id(self.channel_id.clone()) 24 - .content("Invalid syntax") 25 - .build() 26 - .unwrap(), 27 - ); 28 - return; 29 - } 16 + delete_role(api, args.first().unwrap(), args.get(1).unwrap()).await?; 30 17 31 - let _ = self.bot.api.execute_call( 32 - DeleteRoleBuilder::default() 33 - .guild_id(body_split.first().unwrap().to_string()) 34 - .role_id(body_split.get(1).unwrap().to_string()) 35 - .build() 36 - .unwrap(), 37 - ); 38 - } 39 - } 18 + Ok(()) 40 19 }
+23 -58
examples/examplebot/src/commands/edit.rs
··· 1 - use std::sync::Arc; 1 + use fluxer_rs::{ 2 + api::common::{edit_message_with_embeds, fetch_message, send_message}, 3 + command, 4 + }; 2 5 3 - use fluxer_rs::{api::data_structure::message::{EditMessageBuilder, FetchMessage, FetchMessageBuilder, SendMessageBuilder}, fluxerbot::FluxerBot, gateway::data_structure::message::MessageEventData, high_level::command_handler::{CommandHandler, CommandTrait}}; 6 + #[command(EditCommand)] 7 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 + let data = feedback.data; 9 + let args = &feedback.args; 4 10 5 - pub struct EditCommand { 6 - pub bot: Arc<FluxerBot>, 7 - pub channel_id: String, 8 - pub content: String, 9 - } 11 + if args.len() != 2 { 12 + send_message(api, &data.channel_id, "Invalid syntax").await?; 13 + return Ok(()); 14 + } 10 15 11 - impl CommandTrait for EditCommand { 12 - async fn execute(&self) { 13 - if let Some((_, body)) = CommandHandler::remove_pfx("!", &self.content).await { 14 - let body_split = body.split(" ").collect::<Vec<&str>>(); 16 + let resp = fetch_message(api, args.first().unwrap(), args.get(1).unwrap()).await?; 17 + edit_message_with_embeds( 18 + api, 19 + &resp.channel_id, 20 + &resp.id, 21 + "EEEEEEEEEEEEE", 22 + resp.embeds.unwrap(), 23 + ) 24 + .await?; 15 25 16 - if body_split.len() != 2 || body.is_empty() { 17 - let _ = self.bot.api.execute_call( 18 - SendMessageBuilder::default() 19 - .channel_id(self.channel_id.clone()) 20 - .content("Invalid syntax") 21 - .build() 22 - .unwrap(), 23 - ); 24 - return; 25 - } 26 - 27 - let resp = self.bot.api.execute_call( 28 - FetchMessageBuilder::default() 29 - .channel_id(body_split.first().unwrap().to_string()) 30 - .message_id(body_split.get(1).unwrap().to_string()) 31 - .build() 32 - .unwrap(), 33 - ); 34 - 35 - let message_info: Option<MessageEventData> = match resp { 36 - Ok(value) => Some(FetchMessage::get_resp(value.as_str().unwrap())), 37 - Err(err) => { 38 - let _ = self.bot.api.execute_call( 39 - SendMessageBuilder::default() 40 - .channel_id(self.channel_id.clone()) 41 - .content(err.to_string()) 42 - .build() 43 - .unwrap(), 44 - ); 45 - None 46 - } 47 - }; 48 - 49 - if let Some(value) = message_info { 50 - let _ = self.bot.api.execute_call( 51 - EditMessageBuilder::default() 52 - .message_id(value.id) 53 - .channel_id(value.channel_id) 54 - .embeds(value.embeds) 55 - .content("EEEEEEEEEEEEE") 56 - .build() 57 - .unwrap(), 58 - ); 59 - } 60 - } 61 - } 62 - } 26 + Ok(()) 27 + }
+7 -26
examples/examplebot/src/commands/ping.rs
··· 1 - use std::sync::Arc; 2 - 3 1 use fluxer_rs::{ 4 - api::data_structure::message::{MessageReferenceBuilder, SendMessageBuilder}, 5 - fluxerbot::FluxerBot, 6 - high_level::command_handler::CommandTrait, 2 + api::{common::send_reply}, 3 + command, 7 4 }; 8 5 9 - pub struct PingCommand { 10 - pub bot: Arc<FluxerBot>, 11 - pub channel_id: String, 12 - pub id: String, 13 - } 6 + #[command(PingCommand)] 7 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 + let data = feedback.data; 14 9 15 - impl CommandTrait for PingCommand { 16 - async fn execute(&self) { 17 - let _ = self.bot.api.execute_call( 18 - SendMessageBuilder::default() 19 - .channel_id(self.channel_id.clone()) 20 - .content("pong".to_string()) 21 - .message_reference( 22 - MessageReferenceBuilder::default() 23 - .message_id(self.id.clone()) 24 - .build() 25 - .unwrap(), 26 - ) 27 - .build() 28 - .unwrap(), 29 - ); 30 - } 10 + send_reply(api, &data.channel_id, &data.id, "pong").await?; 11 + Ok(()) 31 12 }
+19 -41
examples/examplebot/src/commands/react.rs
··· 1 - use std::sync::Arc; 2 - 3 1 use fluxer_rs::{ 4 - api::data_structure::{message::SendMessageBuilder, reaction::AddOwnReacitonBuilder}, 5 - fluxerbot::FluxerBot, 6 - high_level::command_handler::{CommandHandler, CommandTrait}, 2 + api::common::{react, send_message}, 3 + command, 4 + util::get_emoji, 7 5 }; 8 6 9 - pub struct ReactCommand { 10 - pub bot: Arc<FluxerBot>, 11 - pub channel_id: String, 12 - pub content: String, 13 - } 7 + #[command(ReactCommand)] 8 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 9 + let data = feedback.data; 10 + let args = &feedback.args; 14 11 15 - impl CommandTrait for ReactCommand { 16 - async fn execute(&self) { 17 - if let Some((_, body)) = CommandHandler::remove_pfx("!", &self.content).await { 18 - let body_split = body.split(" ").collect::<Vec<&str>>(); 12 + if args.len() != 3 { 13 + send_message(api, &data.channel_id, "Invalid syntax").await?; 14 + return Ok(()); 15 + } 19 16 20 - if body_split.len() != 3 || body.is_empty() { 21 - let _ = self.bot.api.execute_call( 22 - SendMessageBuilder::default() 23 - .channel_id(self.channel_id.clone()) 24 - .content("Invalid syntax") 25 - .build() 26 - .unwrap(), 27 - ); 28 - return; 29 - } 17 + react( 18 + api, 19 + args.first().unwrap(), 20 + args.get(1).unwrap(), 21 + &get_emoji(args.get(2).unwrap()), 22 + ) 23 + .await?; 30 24 31 - let _ = self.bot.api.execute_call( 32 - AddOwnReacitonBuilder::default() 33 - .channel_id(body_split.first().unwrap().to_string()) 34 - .message_id(body_split.get(1).unwrap().to_string()) 35 - .emoji( 36 - body_split 37 - .get(2) 38 - .unwrap() 39 - .to_string() 40 - .trim_matches('<') 41 - .trim_matches('>'), 42 - ) 43 - .build() 44 - .unwrap(), 45 - ); 46 - } 47 - } 25 + Ok(()) 48 26 }
+18 -41
examples/examplebot/src/commands/remove_react.rs
··· 1 - use std::sync::Arc; 2 - 3 1 use fluxer_rs::{ 4 - api::data_structure::{message::SendMessageBuilder, reaction::RemoveAllReactionBuilder}, 5 - fluxerbot::FluxerBot, 6 - high_level::command_handler::{CommandHandler, CommandTrait}, 2 + api::common::{remove_all_emoji_reactions, send_message}, 3 + command, util::get_emoji, 7 4 }; 8 5 9 - pub struct RemoveReactCommand { 10 - pub bot: Arc<FluxerBot>, 11 - pub channel_id: String, 12 - pub content: String, 13 - } 6 + #[command(RemoveReactCommand)] 7 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 + let data = feedback.data; 9 + let args = &feedback.args; 14 10 15 - impl CommandTrait for RemoveReactCommand { 16 - async fn execute(&self) { 17 - if let Some((_, body)) = CommandHandler::remove_pfx("!", &self.content).await { 18 - let body_split = body.split(" ").collect::<Vec<&str>>(); 11 + if args.len() != 3 { 12 + send_message(api, &data.channel_id, "Invalid syntax").await?; 13 + return Ok(()); 14 + } 19 15 20 - if body_split.len() != 3 || body.is_empty() { 21 - let _ = self.bot.api.execute_call( 22 - SendMessageBuilder::default() 23 - .channel_id(self.channel_id.clone()) 24 - .content("Invalid syntax") 25 - .build() 26 - .unwrap(), 27 - ); 28 - return; 29 - } 16 + remove_all_emoji_reactions( 17 + api, 18 + args.first().unwrap(), 19 + args.get(1).unwrap(), 20 + &get_emoji(args.get(2).unwrap()), 21 + ) 22 + .await?; 30 23 31 - let _ = self.bot.api.execute_call( 32 - RemoveAllReactionBuilder::default() 33 - .channel_id(body_split.first().unwrap().to_string()) 34 - .message_id(body_split.get(1).unwrap().to_string()) 35 - .emoji( 36 - body_split 37 - .get(2) 38 - .unwrap() 39 - .to_string() 40 - .trim_matches('<') 41 - .trim_matches('>'), 42 - ) 43 - .build() 44 - .unwrap(), 45 - ); 46 - } 47 - } 24 + Ok(()) 48 25 }
+20 -34
examples/examplebot/src/commands/removerole.rs
··· 1 - use std::sync::Arc; 2 - 3 1 use fluxer_rs::{ 4 - api::data_structure::{message::SendMessageBuilder, role::RemoveRoleFromMemberBuilder}, 5 - fluxerbot::FluxerBot, 6 - high_level::command_handler::{CommandHandler, CommandTrait}, 2 + api::{ 3 + common::{remove_role, send_message}, 4 + }, 5 + command, 7 6 }; 8 7 9 - pub struct RemoveRoleCommand { 10 - pub bot: Arc<FluxerBot>, 11 - pub channel_id: String, 12 - pub content: String, 13 - } 8 + #[command(RemoveRoleCommand)] 9 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 10 + let data = feedback.data; 11 + let args = &feedback.args; 14 12 15 - impl CommandTrait for RemoveRoleCommand { 16 - async fn execute(&self) { 17 - if let Some((_, body)) = CommandHandler::remove_pfx("!", &self.content).await { 18 - let body_split = body.split(" ").collect::<Vec<&str>>(); 13 + if args.len() != 3 { 14 + send_message(api, &data.channel_id, "Invalid syntax").await?; 15 + return Ok(()); 16 + } 19 17 20 - if body_split.len() != 3 || body.is_empty() { 21 - let _ = self.bot.api.execute_call( 22 - SendMessageBuilder::default() 23 - .channel_id(self.channel_id.clone()) 24 - .content("Invalid syntax") 25 - .build() 26 - .unwrap(), 27 - ); 28 - return; 29 - } 18 + remove_role( 19 + api, 20 + args.first().unwrap(), 21 + args.get(1).unwrap(), 22 + args.get(2).unwrap(), 23 + ) 24 + .await?; 30 25 31 - let _ = self.bot.api.execute_call( 32 - RemoveRoleFromMemberBuilder::default() 33 - .guild_id(body_split.first().unwrap().to_string()) 34 - .user_id(body_split.get(1).unwrap().to_string()) 35 - .role_id(body_split.get(2).unwrap().to_string()) 36 - .build() 37 - .unwrap(), 38 - ); 39 - } 40 - } 26 + Ok(()) 41 27 }
+41 -85
examples/examplebot/src/dispatch.rs
··· 1 - use std::sync::Arc; 2 - 3 1 use fluxer_rs::{ 4 - EmbedAuthorBuilder, EmbedBuilder, EmbedFieldBuilder, EmbedFooterBuilder, EmbedMediaBuilder, 5 - api::data_structure::message::SendMessageBuilder, 6 - fluxerbot::FluxerBot, 7 - gateway::{ 8 - data_structure::{guild::GuildCreateData, message::MessageEventData}, 9 - op_handlers::dispatch::DispatchHandlerTrait, 10 - }, 2 + api::{FluxerApiHandler, channels::messages::SendMessageBuilder}, 3 + error::FluxerRsError, 4 + gateway::dispatch::DispatchHandlerTrait, 11 5 high_level::command_handler::CommandHandler, 6 + register_commands, 7 + serde::types::{ 8 + guild::GuildCreateData, 9 + message::{ 10 + EmbedAuthorBuilder, EmbedBuilder, EmbedFieldBuilder, EmbedFooterBuilder, 11 + EmbedMediaBuilder, MessageData, 12 + }, 13 + }, 12 14 }; 13 15 14 16 use crate::commands::{ 15 - addrole::AddRoleCommand, createrole::CreateRoleCommand, deleterole::DeleteRoleCommand, edit::EditCommand, ping::PingCommand, react::ReactCommand, remove_react::RemoveReactCommand, removerole::RemoveRoleCommand 17 + addrole::AddRoleCommand, createrole::CreateRoleCommand, deleterole::DeleteRoleCommand, 18 + edit::EditCommand, ping::PingCommand, react::ReactCommand, remove_react::RemoveReactCommand, 19 + removerole::RemoveRoleCommand, 16 20 }; 17 21 18 - pub struct ColorbotDispatchHandler { 19 - pub bot: Arc<FluxerBot>, 20 - } 22 + pub struct ColorbotDispatchHandler {} 21 23 22 24 impl DispatchHandlerTrait for ColorbotDispatchHandler { 23 - async fn handle_message_create_dispatch(&self, data: MessageEventData) { 25 + async fn handle_message_create_dispatch( 26 + &self, 27 + data: MessageData, 28 + api: &FluxerApiHandler, 29 + ) -> Result<(), FluxerRsError> { 24 30 let mut cmd_handler = CommandHandler::init("!".to_string()); 25 31 26 - cmd_handler.register_command( 27 - "ping".to_string(), 28 - PingCommand { 29 - bot: self.bot.clone(), 30 - channel_id: data.channel_id.clone(), 31 - id: data.id.clone(), 32 - }, 33 - ); 34 - cmd_handler.register_command( 35 - "edit".to_string(), 36 - EditCommand { 37 - bot: self.bot.clone(), 38 - channel_id: data.channel_id.clone(), 39 - content: data.content.clone(), 40 - }, 41 - ); 42 - cmd_handler.register_command( 43 - "react".to_string(), 44 - ReactCommand { 45 - bot: self.bot.clone(), 46 - channel_id: data.channel_id.clone(), 47 - content: data.content.clone(), 48 - }, 49 - ); 50 - cmd_handler.register_command( 51 - "removereact".to_string(), 52 - RemoveReactCommand { 53 - bot: self.bot.clone(), 54 - channel_id: data.channel_id.clone(), 55 - content: data.content.clone(), 56 - }, 57 - ); 58 - cmd_handler.register_command( 59 - "addrole".to_string(), 60 - AddRoleCommand { 61 - bot: self.bot.clone(), 62 - channel_id: data.channel_id.clone(), 63 - content: data.content.clone(), 64 - }, 65 - ); 66 - cmd_handler.register_command( 67 - "removerole".to_string(), 68 - RemoveRoleCommand { 69 - bot: self.bot.clone(), 70 - channel_id: data.channel_id.clone(), 71 - content: data.content.clone(), 72 - }, 73 - ); 74 - cmd_handler.register_command( 75 - "createrole".to_string(), 76 - CreateRoleCommand { 77 - bot: self.bot.clone(), 78 - channel_id: data.channel_id.clone(), 79 - content: data.content.clone(), 80 - }, 81 - ); 82 - cmd_handler.register_command( 83 - "deleterole".to_string(), 84 - DeleteRoleCommand { 85 - bot: self.bot.clone(), 86 - channel_id: data.channel_id.clone(), 87 - content: data.content.clone(), 88 - }, 89 - ); 32 + register_commands!(cmd_handler,[ 33 + {"addrole", AddRoleCommand}, 34 + {"ping", PingCommand}, 35 + {"edit", EditCommand}, 36 + {"react", ReactCommand}, 37 + {"removereact", RemoveReactCommand}, 38 + {"removerole", RemoveRoleCommand}, 39 + {"createrole", CreateRoleCommand}, 40 + {"deleterole", DeleteRoleCommand}, 41 + ]); 90 42 91 - cmd_handler.handle(&data).await; 43 + cmd_handler.handle(&data, api).await 92 44 } 93 45 94 - async fn handle_guild_create_dispatch(&self, data: Box<GuildCreateData>) { 95 - if data.id == "1473686979970875591" { 96 - let _ = self.bot 97 - .api 46 + async fn handle_guild_create_dispatch( 47 + &self, 48 + data: GuildCreateData, 49 + api: &FluxerApiHandler, 50 + ) -> Result<(), FluxerRsError> { 51 + if data.id == "1477453554678525954" { 52 + api 98 53 .execute_call( 99 54 SendMessageBuilder::default() 100 55 .content("Mhyello i am online".to_string()) 101 - .channel_id("1474424011696861241".to_string()) 56 + .channel_id("1477938338794254343".to_string()) 102 57 .embeds(vec![ 103 58 EmbedBuilder::default() 104 59 .title("Added to Queue".to_string()) ··· 117 72 ] 118 73 ) 119 74 .message_reference(None) 120 - .build().unwrap()); 75 + .build().unwrap()).await?; 121 76 } 77 + Ok(()) 122 78 } 123 79 }
+11 -17
examples/examplebot/src/main.rs
··· 1 - use std::sync::Arc; 2 - 3 - use fluxer_rs::fluxerbot::FluxerBot; 1 + use fluxer_rs::{error::FluxerRsError, fluxerbot::FluxerBot}; 4 2 5 3 use crate::dispatch::ColorbotDispatchHandler; 6 4 ··· 8 6 pub mod dispatch; 9 7 10 8 #[tokio::main] 11 - async fn main() { 12 - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 9 + async fn main() -> Result<(), FluxerRsError> { 10 + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init(); 13 11 14 - let bot: FluxerBot = FluxerBot::init( 15 - "".to_string(), 16 - "wss://gateway.fluxer.app?v=1&encoding=json&compress=none".to_string(), 17 - "https://api.fluxer.app/v1".to_string(), 18 - ) 19 - .await; 12 + let bot = FluxerBot::init( 13 + "<your bot token here>", 14 + "wss://gateway.fluxer.app?v=1&encoding=json&compress=none", 15 + "https://api.fluxer.app/v1", 16 + )?; 20 17 21 - let bot_arc = Arc::new(bot); 22 - bot_arc 23 - .start(ColorbotDispatchHandler { 24 - bot: bot_arc.clone(), 25 - }) 26 - .await; 18 + bot.start(ColorbotDispatchHandler {}).await; 19 + 20 + Ok(()) 27 21 }
+14
macros/Cargo.toml
··· 1 + [package] 2 + name = "macros" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [lib] 7 + proc-macro = true 8 + 9 + [dependencies] 10 + syn = { version = "2", features = ["full"] } 11 + quote = "1" 12 + heck = "0.5.0" 13 + log = "0.4.29" 14 + proc-macro2 = "1.0.106"
+65
macros/src/command.rs
··· 1 + use proc_macro::TokenStream; 2 + use quote::quote; 3 + use syn::{ 4 + AngleBracketedGenericArguments, FnArg, GenericArgument, Ident, ItemFn, Lifetime, PathArguments, 5 + Type, parse_macro_input, parse_quote, parse_str, punctuated::Punctuated, 6 + }; 7 + 8 + pub fn command_call(args: TokenStream, item: TokenStream) -> TokenStream { 9 + let struct_ident = parse_macro_input!(args as Ident); 10 + let mut r#fn = parse_macro_input!(item as ItemFn); 11 + let fn_name = &r#fn.sig.ident; 12 + 13 + for arg in r#fn.sig.inputs.iter_mut() { 14 + if let FnArg::Typed(arg) = arg 15 + && let Type::Reference(r#ref) = &mut *arg.ty 16 + && let Type::Path(element) = &mut *r#ref.elem 17 + && let Some(segment) = element.path.segments.last_mut() 18 + { 19 + match segment.ident.to_string().as_str() { 20 + "CommandFeedback" => { 21 + segment.arguments = 22 + PathArguments::AngleBracketed(AngleBracketedGenericArguments { 23 + colon2_token: None, 24 + lt_token: Default::default(), 25 + args: Punctuated::from_iter([GenericArgument::Lifetime( 26 + Lifetime::new("'_", proc_macro2::Span::call_site()), 27 + )]), 28 + gt_token: Default::default(), 29 + }); 30 + let path: syn::Path = parse_quote!(fluxer_rs::high_level::command_handler); 31 + for seg in path.segments.into_iter().rev() { 32 + element.path.segments.insert(0, seg); 33 + } 34 + } 35 + "FluxerApiHandler" => { 36 + let path: syn::Path = parse_quote!(fluxer_rs::api); 37 + for seg in path.segments.into_iter().rev() { 38 + element.path.segments.insert(0, seg); 39 + } 40 + } 41 + _ => {} 42 + } 43 + } 44 + } 45 + 46 + r#fn.sig.output = syn::ReturnType::Type( 47 + Default::default(), 48 + Box::new(parse_str::<syn::Type>("Result<(), fluxer_rs::error::FluxerRsError>").unwrap()), 49 + ); 50 + 51 + let expanded_macro = quote! { 52 + #r#fn 53 + 54 + pub struct #struct_ident { } 55 + 56 + impl fluxer_rs::high_level::command_handler::CommandTrait for #struct_ident { 57 + async fn execute<'a>(&self, api: &'a fluxer_rs::api::FluxerApiHandler, feedback: &'a fluxer_rs::high_level::command_handler::CommandFeedback<'a>) -> Result<(), fluxer_rs::error::FluxerRsError> { 58 + let _ = #fn_name(api, feedback).await; 59 + Ok(()) 60 + } 61 + } 62 + }; 63 + 64 + expanded_macro.into() 65 + }
+202
macros/src/dispatch.rs
··· 1 + use heck::{ToSnakeCase, ToUpperCamelCase}; 2 + use proc_macro::TokenStream; 3 + use quote::{format_ident, quote}; 4 + use syn::{ 5 + LitStr, PathArguments, Token, Type, bracketed, parse::Parse, parse_macro_input, 6 + punctuated::Punctuated, 7 + }; 8 + 9 + struct MacroData { 10 + data_list: Vec<DispatchData>, 11 + } 12 + 13 + impl Parse for MacroData { 14 + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { 15 + let data_list: Vec<DispatchData> = 16 + Punctuated::<DispatchData, Token![,]>::parse_terminated(input) 17 + .unwrap() 18 + .into_iter() 19 + .collect(); 20 + 21 + Ok(MacroData { data_list }) 22 + } 23 + } 24 + 25 + struct DispatchData { 26 + event_name: LitStr, 27 + data_type: Type, 28 + } 29 + 30 + impl Parse for DispatchData { 31 + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { 32 + let data; 33 + bracketed!(data in input); 34 + 35 + let event_name = data.parse::<LitStr>().unwrap(); 36 + data.parse::<Token![,]>().unwrap(); 37 + let data_type = data.parse::<Type>().unwrap(); 38 + 39 + Ok(DispatchData { 40 + event_name, 41 + data_type, 42 + }) 43 + } 44 + } 45 + 46 + pub fn dispatch_call(stream: TokenStream) -> TokenStream { 47 + let dispatch: MacroData = parse_macro_input!(stream as MacroData); 48 + let count = dispatch.data_list.len(); 49 + let trait_fns = (0..count).map(|i| { 50 + let dispatch_data = dispatch.data_list.get(i).unwrap(); 51 + 52 + let event_name = dispatch_data.event_name.value(); 53 + let r#type = &dispatch_data.data_type; 54 + 55 + if event_name == "None" { 56 + return quote! {}; 57 + } 58 + 59 + let fn_name = format_ident!("handle_{}_dispatch", event_name.to_snake_case()); 60 + 61 + let info_message = format!("-> [DISPATCH::{}]", event_name); 62 + 63 + quote! { 64 + fn #fn_name( 65 + &self, 66 + _data: #r#type, 67 + _api: &crate::api::FluxerApiHandler 68 + ) -> impl Future<Output = Result<(), FluxerRsError>> + Send { 69 + async move { 70 + info!(#info_message); 71 + Ok(()) 72 + } 73 + } 74 + } 75 + }); 76 + 77 + let enum_elems = (0..count).map(|i| { 78 + let dispatch_data = dispatch.data_list.get(i).unwrap(); 79 + let event_name = dispatch_data.event_name.value(); 80 + let r#type = &dispatch_data.data_type; 81 + 82 + if event_name == "None" { 83 + let name_ident = format_ident!("None"); 84 + return quote! { 85 + #name_ident 86 + }; 87 + } 88 + 89 + let name_ident = format_ident!("{}", event_name.to_upper_camel_case()); 90 + 91 + quote! { 92 + #name_ident(#r#type) 93 + } 94 + }); 95 + 96 + let match_elems = (0..count).map(|i| { 97 + let dispatch_data = dispatch.data_list.get(i).unwrap(); 98 + let event_name = dispatch_data.event_name.value(); 99 + let match_ident = format_ident!("{}", event_name.to_upper_camel_case()); 100 + 101 + if event_name == "None" { 102 + return quote! { 103 + DispatchEvent::#match_ident => Ok(()), 104 + }; 105 + } 106 + 107 + let handler_ident = format_ident!("handle_{}_dispatch", event_name.to_snake_case()); 108 + 109 + quote! { 110 + DispatchEvent::#match_ident(data) => handler.#handler_ident(data,api).await, 111 + } 112 + }); 113 + 114 + let deserialize_elems = (0..count).map(|i| { 115 + let dispatch_data = dispatch.data_list.get(i).unwrap(); 116 + let event_name = &dispatch_data.event_name; 117 + let match_ident = format_ident!("{}", event_name.value().to_upper_camel_case()); 118 + 119 + let mut r#type = dispatch_data.data_type.clone(); 120 + let Type::Path(path) = &mut r#type else { 121 + return quote! {}; 122 + }; 123 + 124 + if path.path.segments.last().unwrap().ident == "Option" { 125 + return quote! {}; 126 + } 127 + 128 + if path.path.segments.last().unwrap().ident == "Vec" { 129 + path.path.segments.iter_mut().for_each(|a| { 130 + if let PathArguments::AngleBracketed(args) = &mut a.arguments { 131 + args.colon2_token = Some(Default::default()) 132 + } 133 + }); 134 + }; 135 + 136 + quote! { 137 + #event_name => DispatchEvent::#match_ident( 138 + #r#type::deserialize(&value["d"]).map_err(de::Error::custom)?, 139 + ), 140 + } 141 + }); 142 + 143 + let expanded_macro = quote! { 144 + use crate::{ 145 + serde::types::{ 146 + common::*, 147 + user::*, 148 + guild::*, 149 + message::*, 150 + }, 151 + api::FluxerApiHandler, 152 + error::FluxerRsError 153 + }; 154 + use async_trait::async_trait; 155 + use anyhow::Result; 156 + #[allow(unused_imports)] 157 + use log::{info,error}; 158 + use serde::{Deserialize, de}; 159 + 160 + 161 + pub enum DispatchEvent { 162 + #(#enum_elems,)* 163 + } 164 + 165 + pub trait DispatchHandlerTrait { 166 + #(#trait_fns)* 167 + } 168 + 169 + #[derive(Default)] 170 + pub struct DispatchHandler; 171 + #[async_trait] 172 + impl DispatchHandlerTrait for DispatchHandler {} 173 + 174 + pub async fn handle_dispatch_events<T: DispatchHandlerTrait + Send + Sync + 'static>( 175 + dispatch_event: Box<DispatchEvent>, 176 + handler: &T, 177 + api: &FluxerApiHandler 178 + ) -> Result<(), FluxerRsError> { 179 + match *dispatch_event { 180 + #(#match_elems)* 181 + } 182 + } 183 + 184 + pub fn dispatch_deserialize(value: &serde_json::value::Value) -> Result<DispatchEvent, serde_json::Error>{ 185 + Ok( 186 + match value["t"].as_str().ok_or_else(||de::Error::custom("Failed to find field t"))? { 187 + #(#deserialize_elems)* 188 + 189 + #[cfg(not(debug_assertions))] 190 + _ => { 191 + error!("Unimplemented dispatch event with data: {}", value); 192 + DispatchEvent::None 193 + } 194 + 195 + #[cfg(debug_assertions)] 196 + _ => panic!("Unimplemented dispatch event with data: {}", value), 197 + } 198 + ) 199 + } 200 + }; 201 + expanded_macro.into() 202 + }
+24
macros/src/lib.rs
··· 1 + mod command; 2 + mod dispatch; 3 + mod register_command; 4 + 5 + use proc_macro::TokenStream; 6 + 7 + use crate::{ 8 + command::command_call, dispatch::dispatch_call, register_command::register_command_call, 9 + }; 10 + 11 + #[proc_macro] 12 + pub fn dispatch(stream: TokenStream) -> TokenStream { 13 + dispatch_call(stream) 14 + } 15 + 16 + #[proc_macro_attribute] 17 + pub fn command(args: TokenStream, item: TokenStream) -> TokenStream { 18 + command_call(args, item) 19 + } 20 + 21 + #[proc_macro] 22 + pub fn register_commands(stream: TokenStream) -> TokenStream { 23 + register_command_call(stream) 24 + }
+66
macros/src/register_command.rs
··· 1 + use proc_macro::TokenStream; 2 + use quote::quote; 3 + use syn::{ 4 + Ident, LitStr, Token, Type, braced, bracketed, parse::Parse, parse_macro_input, 5 + punctuated::Punctuated, 6 + }; 7 + 8 + pub struct MacroData { 9 + handler: Ident, 10 + commands: Vec<CommandsData>, 11 + } 12 + 13 + impl Parse for MacroData { 14 + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { 15 + let handler = input.parse::<Ident>().unwrap(); 16 + input.parse::<Token![,]>().unwrap(); 17 + let bracketed_data; 18 + bracketed!(bracketed_data in input); 19 + 20 + let commands = Punctuated::<CommandsData, Token![,]>::parse_terminated(&bracketed_data) 21 + .unwrap() 22 + .into_iter() 23 + .collect::<Vec<CommandsData>>(); 24 + Ok(MacroData { handler, commands }) 25 + } 26 + } 27 + 28 + pub struct CommandsData { 29 + name: LitStr, 30 + expr: Type, 31 + } 32 + 33 + impl Parse for CommandsData { 34 + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { 35 + let braced_data; 36 + braced!(braced_data in input); 37 + 38 + let name = braced_data.parse::<LitStr>().unwrap(); 39 + braced_data.parse::<Token![,]>().unwrap(); 40 + let expr = braced_data.parse::<Type>().unwrap(); 41 + Ok(CommandsData { name, expr }) 42 + } 43 + } 44 + 45 + pub fn register_command_call(stream: TokenStream) -> TokenStream { 46 + let data = parse_macro_input!(stream as MacroData); 47 + let elems = (0..data.commands.len()).map(|i| { 48 + let commands_data = data.commands.get(i).unwrap(); 49 + let handler_ident = &data.handler; 50 + let lit_name = &commands_data.name; 51 + let expr = &commands_data.expr; 52 + 53 + quote! { 54 + #handler_ident.register_command( 55 + #lit_name.to_string(), 56 + #expr {}, 57 + ); 58 + } 59 + }); 60 + 61 + let expanded_macro = quote! { 62 + #(#elems)* 63 + }; 64 + 65 + expanded_macro.into() 66 + }
-59
src/api.rs
··· 1 - pub mod calls; 2 - pub mod data_structure; 3 - 4 - use log::info; 5 - use minreq::Response; 6 - 7 - pub enum FluxerApiCallType { 8 - Get, 9 - Post, 10 - Patch, 11 - Put, 12 - Delete, 13 - } 14 - 15 - pub trait ApiCall { 16 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request; 17 - fn get_info(&self) -> (String, FluxerApiCallType); 18 - } 19 - 20 - #[derive(Clone, Default)] 21 - pub struct FluxerApiHandler { 22 - pub token: String, 23 - pub api_endpoint: String, 24 - } 25 - 26 - impl FluxerApiHandler { 27 - pub fn execute_call<T: ApiCall>(&self, call: T) -> Result<Response, minreq::Error> { 28 - let (endpoint, call_type) = call.get_info(); 29 - let url = format!("{}{endpoint}", self.api_endpoint); 30 - 31 - let request = match call_type { 32 - FluxerApiCallType::Get => { 33 - let req = minreq::get(url); 34 - call.get_req(req, self.token.to_string()) 35 - } 36 - FluxerApiCallType::Post => { 37 - let req = minreq::post(url); 38 - call.get_req(req, self.token.to_string()) 39 - } 40 - FluxerApiCallType::Patch => { 41 - let req = minreq::patch(url); 42 - call.get_req(req, self.token.to_string()) 43 - } 44 - FluxerApiCallType::Put => { 45 - let req = minreq::put(url); 46 - call.get_req(req, self.token.to_string()) 47 - } 48 - FluxerApiCallType::Delete => { 49 - let req = minreq::delete(url); 50 - call.get_req(req, self.token.to_string()) 51 - } 52 - }; 53 - 54 - info!("REQ CHECK {:?}", request); 55 - let resp = request.send(); 56 - info!("RESP CHECK: {:?}", resp); 57 - resp 58 - } 59 - }
-2
src/api/calls.rs
··· 1 - pub mod channel; 2 - pub mod guilds;
-2
src/api/calls/channel.rs
··· 1 - pub mod message; 2 - pub mod reaction;
-3
src/api/calls/channel/message.rs
··· 1 - pub mod send; 2 - pub mod edit; 3 - pub mod fetch;
-37
src/api/calls/channel/message/edit.rs
··· 1 - use log::info; 2 - use serde::{Serialize, ser::SerializeMap}; 3 - 4 - use crate::api::{ApiCall, FluxerApiCallType, data_structure::message::EditMessage}; 5 - 6 - impl ApiCall for EditMessage { 7 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 8 - let body = serde_json::to_string(self).unwrap(); 9 - info!("BODY CHECK {body}"); 10 - req.with_body(body) 11 - .with_header("Authorization", format!("Bot {token}")) 12 - } 13 - 14 - fn get_info(&self) -> (String, FluxerApiCallType) { 15 - ( 16 - format!("/channels/{}/messages/{}", self.channel_id, self.message_id), 17 - FluxerApiCallType::Patch, 18 - ) 19 - } 20 - } 21 - 22 - impl Serialize for EditMessage { 23 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 24 - where 25 - S: serde::Serializer, 26 - { 27 - let mut state = serializer.serialize_map(None)?; 28 - state.serialize_entry("content", &self.content)?; 29 - if let Some(embed) = self.embeds.clone() { 30 - state.serialize_entry("embeds", &embed)?; 31 - }; 32 - if let Some(message_reference) = self.message_reference.clone() { 33 - state.serialize_entry("message_reference", &message_reference)?; 34 - } 35 - state.end() 36 - } 37 - }
-23
src/api/calls/channel/message/fetch.rs
··· 1 - use crate::{ 2 - api::{ApiCall, FluxerApiCallType, data_structure::message::FetchMessage}, 3 - gateway::data_structure::message::MessageEventData, 4 - }; 5 - 6 - impl FetchMessage { 7 - pub fn get_resp(body: &str) -> MessageEventData { 8 - serde_json::from_str::<MessageEventData>(body).unwrap() 9 - } 10 - } 11 - 12 - impl ApiCall for FetchMessage { 13 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 14 - req.with_header("Authorization", format!("Bot {token}")) 15 - } 16 - 17 - fn get_info(&self) -> (String, FluxerApiCallType) { 18 - ( 19 - format!("/channels/{}/messages/{}", self.channel_id, self.message_id), 20 - FluxerApiCallType::Get, 21 - ) 22 - } 23 - }
-37
src/api/calls/channel/message/send.rs
··· 1 - use log::info; 2 - use serde::{Serialize, ser::SerializeMap}; 3 - 4 - use crate::api::{ApiCall, FluxerApiCallType, data_structure::message::SendMessage}; 5 - 6 - impl ApiCall for SendMessage { 7 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 8 - let body = serde_json::to_string(self).unwrap(); 9 - info!("BODY CHECK {body}"); 10 - req.with_body(body) 11 - .with_header("Authorization", format!("Bot {token}")) 12 - } 13 - 14 - fn get_info(&self) -> (String, FluxerApiCallType) { 15 - ( 16 - format!("/channels/{}/messages", self.channel_id), 17 - FluxerApiCallType::Post, 18 - ) 19 - } 20 - } 21 - 22 - impl Serialize for SendMessage { 23 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 24 - where 25 - S: serde::Serializer, 26 - { 27 - let mut state = serializer.serialize_map(None)?; 28 - state.serialize_entry("content", &self.content)?; 29 - if let Some(embed) = self.embeds.clone() { 30 - state.serialize_entry("embeds", &embed)?; 31 - }; 32 - if let Some(message_reference) = self.message_reference.clone() { 33 - state.serialize_entry("message_reference", &message_reference)?; 34 - } 35 - state.end() 36 - } 37 - }
-2
src/api/calls/channel/reaction.rs
··· 1 - pub mod add_own; 2 - pub mod remove_all;
-17
src/api/calls/channel/reaction/add_own.rs
··· 1 - use crate::api::{ApiCall, FluxerApiCallType, data_structure::reaction::AddOwnReaciton}; 2 - 3 - impl ApiCall for AddOwnReaciton { 4 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 5 - req.with_header("Authorization", format!("Bot {token}")) 6 - } 7 - 8 - fn get_info(&self) -> (String, FluxerApiCallType) { 9 - ( 10 - format!( 11 - "/channels/{}/messages/{}/reactions/{}/@me", 12 - self.channel_id, self.message_id, self.emoji 13 - ), 14 - FluxerApiCallType::Put, 15 - ) 16 - } 17 - }
-17
src/api/calls/channel/reaction/remove_all.rs
··· 1 - use crate::api::{ApiCall, FluxerApiCallType, data_structure::reaction::RemoveAllReaction}; 2 - 3 - impl ApiCall for RemoveAllReaction { 4 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 5 - req.with_header("Authorization", format!("Bot {token}")) 6 - } 7 - 8 - fn get_info(&self) -> (String, FluxerApiCallType) { 9 - ( 10 - format!( 11 - "/channels/{}/messages/{}/reactions/{}", 12 - self.channel_id, self.message_id, self.emoji 13 - ), 14 - FluxerApiCallType::Delete, 15 - ) 16 - } 17 - }
-1
src/api/calls/guilds.rs
··· 1 - pub mod role;
-4
src/api/calls/guilds/role.rs
··· 1 - pub mod add_to_member; 2 - pub mod remove_from_member; 3 - pub mod create; 4 - pub mod delete;
-17
src/api/calls/guilds/role/add_to_member.rs
··· 1 - use crate::api::{ApiCall, FluxerApiCallType, data_structure::role::AddRoleToMember}; 2 - 3 - impl ApiCall for AddRoleToMember { 4 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 5 - req.with_header("Authorization", format!("Bot {token}")) 6 - } 7 - 8 - fn get_info(&self) -> (String, FluxerApiCallType) { 9 - ( 10 - format!( 11 - "/guilds/{}/members/{}/roles/{}", 12 - self.guild_id, self.user_id, self.role_id 13 - ), 14 - FluxerApiCallType::Put, 15 - ) 16 - } 17 - }
-33
src/api/calls/guilds/role/create.rs
··· 1 - use log::info; 2 - use serde::{Serialize, ser::SerializeMap}; 3 - 4 - use crate::api::{ApiCall, FluxerApiCallType, data_structure::role::CreateRole}; 5 - 6 - impl ApiCall for CreateRole { 7 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 8 - let body = serde_json::to_string(self).unwrap(); 9 - info!("BODY CHECK {body}"); 10 - req.with_body(body) 11 - .with_header("Authorization", format!("Bot {token}")) 12 - } 13 - 14 - fn get_info(&self) -> (String, FluxerApiCallType) { 15 - ( 16 - format!("/guilds/{}/roles", self.guild_id), 17 - FluxerApiCallType::Post, 18 - ) 19 - } 20 - } 21 - 22 - impl Serialize for CreateRole { 23 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 24 - where 25 - S: serde::Serializer, 26 - { 27 - let mut state = serializer.serialize_map(None)?; 28 - state.serialize_entry("name", &self.name)?; 29 - state.serialize_entry("color", &self.color)?; 30 - state.serialize_entry("permissions", &self.permission)?; 31 - state.end() 32 - } 33 - }
-14
src/api/calls/guilds/role/delete.rs
··· 1 - use crate::api::{ApiCall, FluxerApiCallType, data_structure::role::DeleteRole}; 2 - 3 - impl ApiCall for DeleteRole { 4 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 5 - req.with_header("Authorization", format!("Bot {token}")) 6 - } 7 - 8 - fn get_info(&self) -> (String, FluxerApiCallType) { 9 - ( 10 - format!("/guilds/{}/roles/{}", self.guild_id, self.role_id), 11 - FluxerApiCallType::Delete, 12 - ) 13 - } 14 - }
-17
src/api/calls/guilds/role/remove_from_member.rs
··· 1 - use crate::api::{ApiCall, FluxerApiCallType, data_structure::role::RemoveRoleFromMember}; 2 - 3 - impl ApiCall for RemoveRoleFromMember { 4 - fn get_req(&self, req: minreq::Request, token: String) -> minreq::Request { 5 - req.with_header("Authorization", format!("Bot {token}")) 6 - } 7 - 8 - fn get_info(&self) -> (String, FluxerApiCallType) { 9 - ( 10 - format!( 11 - "/guilds/{}/members/{}/roles/{}", 12 - self.guild_id, self.user_id, self.role_id 13 - ), 14 - FluxerApiCallType::Delete, 15 - ) 16 - } 17 - }
+160
src/api/channels/messages.rs
··· 1 + use crate::{ 2 + api::{ApiCall, FluxerApiCallType, FluxerRsError}, 3 + serde::types::message::{Embed, MessageData, MessageReference}, 4 + }; 5 + use anyhow::Result; 6 + use derive_builder::Builder; 7 + use serde::{Serialize, ser::SerializeMap}; 8 + 9 + /// 10 + /// Fetch message 11 + /// 12 + 13 + #[derive(Clone, Debug, Builder)] 14 + #[builder(try_setter, setter(into))] 15 + pub struct FetchMessage { 16 + pub channel_id: String, 17 + pub message_id: String, 18 + } 19 + 20 + impl ApiCall for FetchMessage { 21 + type ReturnType = MessageData; 22 + 23 + fn get_req( 24 + &self, 25 + req: reqwest::RequestBuilder, 26 + token: &str, 27 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 28 + Ok(req.header("Authorization", format!("Bot {token}"))) 29 + } 30 + 31 + fn get_info(&self) -> (String, FluxerApiCallType) { 32 + ( 33 + format!("/channels/{}/messages/{}", self.channel_id, self.message_id), 34 + FluxerApiCallType::Get, 35 + ) 36 + } 37 + 38 + fn get_data(&self, body: &str) -> Result<Self::ReturnType, FluxerRsError> { 39 + let value = serde_json::from_str::<MessageData>(body)?; 40 + Ok(value) 41 + } 42 + } 43 + 44 + /// 45 + /// Send message 46 + /// 47 + 48 + #[derive(Clone, Debug, Builder)] 49 + #[builder(try_setter, setter(into))] 50 + pub struct SendMessage { 51 + pub channel_id: String, 52 + 53 + pub content: String, 54 + #[builder(default)] 55 + pub embeds: Option<Vec<Embed>>, 56 + #[builder(default)] 57 + pub message_reference: Option<MessageReference>, 58 + } 59 + 60 + impl ApiCall for SendMessage { 61 + type ReturnType = MessageData; 62 + 63 + fn get_req( 64 + &self, 65 + req: reqwest::RequestBuilder, 66 + token: &str, 67 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 68 + let value = serde_json::to_string(self)?; 69 + 70 + Ok(req 71 + .body(value) 72 + .header("Authorization", format!("Bot {token}"))) 73 + } 74 + 75 + fn get_info(&self) -> (String, FluxerApiCallType) { 76 + ( 77 + format!("/channels/{}/messages", self.channel_id), 78 + FluxerApiCallType::Post, 79 + ) 80 + } 81 + 82 + fn get_data(&self, body: &str) -> Result<Self::ReturnType, FluxerRsError> { 83 + let value = serde_json::from_str::<MessageData>(body)?; 84 + Ok(value) 85 + } 86 + } 87 + 88 + impl Serialize for SendMessage { 89 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 90 + where 91 + S: serde::Serializer, 92 + { 93 + let mut state = serializer.serialize_map(None)?; 94 + state.serialize_entry("content", &self.content)?; 95 + if let Some(embed) = self.embeds.clone() { 96 + state.serialize_entry("embeds", &embed)?; 97 + }; 98 + if let Some(message_reference) = self.message_reference.clone() { 99 + state.serialize_entry("message_reference", &message_reference)?; 100 + } 101 + state.end() 102 + } 103 + } 104 + 105 + /// 106 + /// Edit message 107 + /// 108 + 109 + #[derive(Clone, Debug, Builder)] 110 + #[builder(try_setter, setter(into))] 111 + pub struct EditMessage { 112 + pub channel_id: String, 113 + pub message_id: String, 114 + 115 + pub content: String, 116 + #[builder(default)] 117 + pub embeds: Option<Vec<Embed>>, 118 + } 119 + 120 + impl ApiCall for EditMessage { 121 + type ReturnType = MessageData; 122 + 123 + fn get_req( 124 + &self, 125 + req: reqwest::RequestBuilder, 126 + token: &str, 127 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 128 + let value = serde_json::to_string(self)?; 129 + 130 + Ok(req 131 + .body(value) 132 + .header("Authorization", format!("Bot {token}"))) 133 + } 134 + 135 + fn get_info(&self) -> (String, FluxerApiCallType) { 136 + ( 137 + format!("/channels/{}/messages/{}", self.channel_id, self.message_id), 138 + FluxerApiCallType::Patch, 139 + ) 140 + } 141 + 142 + fn get_data(&self, body: &str) -> Result<Self::ReturnType, FluxerRsError> { 143 + let value = serde_json::from_str::<MessageData>(body)?; 144 + Ok(value) 145 + } 146 + } 147 + 148 + impl Serialize for EditMessage { 149 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 150 + where 151 + S: serde::Serializer, 152 + { 153 + let mut state = serializer.serialize_map(None)?; 154 + state.serialize_entry("content", &self.content)?; 155 + if let Some(embed) = self.embeds.clone() { 156 + state.serialize_entry("embeds", &embed)?; 157 + }; 158 + state.end() 159 + } 160 + }
+2
src/api/channels/mod.rs
··· 1 + pub mod messages; 2 + pub mod reactions;
+79
src/api/channels/reactions.rs
··· 1 + use anyhow::Result; 2 + use derive_builder::Builder; 3 + 4 + use crate::api::{ApiCall, FluxerApiCallType, FluxerRsError}; 5 + 6 + /// 7 + /// Add own reaction 8 + /// 9 + 10 + #[derive(Clone, Debug, Builder)] 11 + #[builder(try_setter, setter(into))] 12 + pub struct AddOwnReaction { 13 + pub channel_id: String, 14 + pub message_id: String, 15 + pub emoji: String, 16 + } 17 + 18 + impl ApiCall for AddOwnReaction { 19 + type ReturnType = (); 20 + fn get_req( 21 + &self, 22 + req: reqwest::RequestBuilder, 23 + token: &str, 24 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 25 + Ok(req.header("Authorization", format!("Bot {token}"))) 26 + } 27 + 28 + fn get_info(&self) -> (String, FluxerApiCallType) { 29 + ( 30 + format!( 31 + "/channels/{}/messages/{}/reactions/{}/@me", 32 + self.channel_id, self.message_id, self.emoji 33 + ), 34 + FluxerApiCallType::Put, 35 + ) 36 + } 37 + 38 + fn get_data(&self, _body: &str) -> Result<Self::ReturnType, FluxerRsError> { 39 + Ok(()) 40 + } 41 + } 42 + 43 + /// 44 + /// Remove all reactions 45 + /// 46 + 47 + #[derive(Clone, Debug, Builder)] 48 + #[builder(try_setter, setter(into))] 49 + pub struct RemoveAllEmojiReactions { 50 + pub channel_id: String, 51 + pub message_id: String, 52 + pub emoji: String, 53 + } 54 + 55 + impl ApiCall for RemoveAllEmojiReactions { 56 + type ReturnType = (); 57 + 58 + fn get_req( 59 + &self, 60 + req: reqwest::RequestBuilder, 61 + token: &str, 62 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 63 + Ok(req.header("Authorization", format!("Bot {token}"))) 64 + } 65 + 66 + fn get_info(&self) -> (String, FluxerApiCallType) { 67 + ( 68 + format!( 69 + "/channels/{}/messages/{}/reactions/{}", 70 + self.channel_id, self.message_id, self.emoji 71 + ), 72 + FluxerApiCallType::Delete, 73 + ) 74 + } 75 + 76 + fn get_data(&self, _body: &str) -> Result<Self::ReturnType, FluxerRsError> { 77 + Ok(()) 78 + } 79 + }
+259
src/api/common.rs
··· 1 + use crate::{ 2 + api::{ 3 + FluxerApiHandler, 4 + channels::{ 5 + messages::{EditMessageBuilder, FetchMessageBuilder, SendMessageBuilder}, 6 + reactions::{AddOwnReactionBuilder, RemoveAllEmojiReactionsBuilder}, 7 + }, 8 + guilds::roles::{ 9 + AddRoleToMemberBuilder, CreateRoleBuilder, DeleteRoleBuilder, 10 + RemoveRoleFromMemberBuilder, 11 + }, 12 + }, 13 + error::{ApiHandlerError, FluxerRsError}, 14 + serde::types::{ 15 + common::RoleData, 16 + message::{Embed, MessageData, MessageReferenceBuilder}, 17 + }, 18 + }; 19 + 20 + // 21 + // Message helper functions 22 + // 23 + 24 + /// Get a message by it's id 25 + /// Required fields: `channel_id` and `message_id` of the message that will be fetched 26 + pub async fn fetch_message( 27 + api: &FluxerApiHandler, 28 + channel_id: &str, 29 + message_id: &str, 30 + ) -> Result<MessageData, FluxerRsError> { 31 + let call = FetchMessageBuilder::default() 32 + .channel_id(channel_id) 33 + .message_id(message_id) 34 + .build() 35 + .map_err(ApiHandlerError::from)?; 36 + 37 + let result = api.execute_call(call).await?; 38 + 39 + Ok(result) 40 + } 41 + 42 + /// Send a message 43 + /// Required fields: `channel_id` and `content` of the message that will be sent 44 + pub async fn send_message( 45 + api: &FluxerApiHandler, 46 + channel_id: &str, 47 + content: &str, 48 + ) -> Result<MessageData, FluxerRsError> { 49 + let call = SendMessageBuilder::default() 50 + .channel_id(channel_id) 51 + .content(content) 52 + .build() 53 + .map_err(ApiHandlerError::from)?; 54 + 55 + let result = api.execute_call(call).await?; 56 + 57 + Ok(result) 58 + } 59 + 60 + /// Send a message reply 61 + /// Required fields: `channel_id` and `message_id` of the original message and `content` of the response 62 + pub async fn send_reply( 63 + api: &FluxerApiHandler, 64 + channel_id: &str, 65 + message_id: &str, 66 + content: &str, 67 + ) -> Result<MessageData, FluxerRsError> { 68 + let call = SendMessageBuilder::default() 69 + .channel_id(channel_id) 70 + .content(content) 71 + .message_reference( 72 + MessageReferenceBuilder::default() 73 + .message_id(message_id) 74 + .build() 75 + .map_err(ApiHandlerError::from)?, 76 + ) 77 + .build() 78 + .map_err(ApiHandlerError::from)?; 79 + 80 + let result = api.execute_call(call).await?; 81 + 82 + Ok(result) 83 + } 84 + 85 + /// Edit a message 86 + /// Required fields: `channel_id` and `message_id` of the original message and `content` of the edited message 87 + pub async fn edit_message( 88 + api: &FluxerApiHandler, 89 + channel_id: &str, 90 + message_id: &str, 91 + content: &str, 92 + ) -> Result<MessageData, FluxerRsError> { 93 + let call = EditMessageBuilder::default() 94 + .channel_id(channel_id) 95 + .message_id(message_id) 96 + .content(content) 97 + .build() 98 + .map_err(ApiHandlerError::from)?; 99 + 100 + let result = api.execute_call(call).await?; 101 + 102 + Ok(result) 103 + } 104 + 105 + /// Edit a message that has embeds 106 + /// Required fields: `channel_id` and `message_id` of the original message, `content` and `embeds` of the edited message 107 + pub async fn edit_message_with_embeds( 108 + api: &FluxerApiHandler, 109 + channel_id: &str, 110 + message_id: &str, 111 + content: &str, 112 + embeds: Vec<Embed>, 113 + ) -> Result<MessageData, FluxerRsError> { 114 + let call = EditMessageBuilder::default() 115 + .channel_id(channel_id) 116 + .message_id(message_id) 117 + .content(content) 118 + .embeds(embeds) 119 + .build() 120 + .map_err(ApiHandlerError::from)?; 121 + 122 + let result = api.execute_call(call).await?; 123 + 124 + Ok(result) 125 + } 126 + 127 + // 128 + // Role helper functions 129 + // 130 + 131 + /// Give a role to a member 132 + /// Required fields: `guild_id`, `role_id` and `user_id` 133 + pub async fn give_role( 134 + api: &FluxerApiHandler, 135 + guild_id: &str, 136 + role_id: &str, 137 + user_id: &str, 138 + ) -> Result<(), FluxerRsError> { 139 + let call = AddRoleToMemberBuilder::default() 140 + .guild_id(guild_id) 141 + .role_id(role_id) 142 + .user_id(user_id) 143 + .build() 144 + .map_err(ApiHandlerError::from)?; 145 + 146 + api.execute_call(call).await?; 147 + 148 + Ok(()) 149 + } 150 + 151 + /// Remove a role from a user 152 + /// Required fields: `guild_id`, `role_id` and `user_id` 153 + pub async fn remove_role( 154 + api: &FluxerApiHandler, 155 + guild_id: &str, 156 + role_id: &str, 157 + user_id: &str, 158 + ) -> Result<(), FluxerRsError> { 159 + let call = RemoveRoleFromMemberBuilder::default() 160 + .guild_id(guild_id) 161 + .role_id(role_id) 162 + .user_id(user_id) 163 + .build() 164 + .map_err(ApiHandlerError::from)?; 165 + 166 + api.execute_call(call).await?; 167 + 168 + Ok(()) 169 + } 170 + 171 + /// Create a new role 172 + /// Required fields: `guild_id`, `name`, `permission` and `color` 173 + pub async fn create_role( 174 + api: &FluxerApiHandler, 175 + guild_id: &str, 176 + name: &str, 177 + permission: &str, 178 + color: &str, 179 + ) -> Result<RoleData, FluxerRsError> { 180 + let call = CreateRoleBuilder::default() 181 + .guild_id(guild_id) 182 + .name(name) 183 + .permission(permission) 184 + .color(u32::from_str_radix(color, 16).map_err(FluxerRsError::from)?) 185 + .build() 186 + .map_err(ApiHandlerError::from)?; 187 + 188 + let result = api.execute_call(call).await?; 189 + 190 + Ok(result) 191 + } 192 + 193 + /// Delete an existing role 194 + /// Required fields: `guild_id` and `role_id` 195 + pub async fn delete_role( 196 + api: &FluxerApiHandler, 197 + guild_id: &str, 198 + role_id: &str, 199 + ) -> Result<(), FluxerRsError> { 200 + let call = DeleteRoleBuilder::default() 201 + .guild_id(guild_id) 202 + .role_id(role_id) 203 + .build() 204 + .map_err(ApiHandlerError::from)?; 205 + 206 + api.execute_call(call).await?; 207 + 208 + Ok(()) 209 + } 210 + 211 + // 212 + // Reaction helper functions 213 + // 214 + 215 + /// Add the bot's reaction to a message 216 + /// Required fields: `channel_id`, `message_id` and `emoji` 217 + pub async fn react( 218 + api: &FluxerApiHandler, 219 + channel_id: &str, 220 + message_id: &str, 221 + emoji: &str, 222 + ) -> Result<(), FluxerRsError> { 223 + let emoji = if emoji.starts_with("<") { 224 + emoji.trim_start_matches('<').trim_end_matches('>') 225 + } else { 226 + emoji 227 + }; 228 + 229 + let call = AddOwnReactionBuilder::default() 230 + .channel_id(channel_id) 231 + .message_id(message_id) 232 + .emoji(emoji) 233 + .build() 234 + .map_err(ApiHandlerError::from)?; 235 + 236 + api.execute_call(call).await?; 237 + 238 + Ok(()) 239 + } 240 + 241 + /// Remove all emoji reactions from a given message 242 + /// Required fields: `channel_id`, `message_id` and `emoji` 243 + pub async fn remove_all_emoji_reactions( 244 + api: &FluxerApiHandler, 245 + channel_id: &str, 246 + message_id: &str, 247 + emoji: &str, 248 + ) -> Result<(), FluxerRsError> { 249 + let call = RemoveAllEmojiReactionsBuilder::default() 250 + .channel_id(channel_id) 251 + .message_id(message_id) 252 + .emoji(emoji.trim_matches('<').trim_matches('>')) 253 + .build() 254 + .map_err(ApiHandlerError::from)?; 255 + 256 + api.execute_call(call).await?; 257 + 258 + Ok(()) 259 + }
-4
src/api/data_structure.rs
··· 1 - pub mod embed; 2 - pub mod message; 3 - pub mod reaction; 4 - pub mod role;
-180
src/api/data_structure/embed.rs
··· 1 - use std::mem::take; 2 - 3 - use derive_builder::Builder; 4 - use serde::{Deserialize, Serialize}; 5 - use serde_with::skip_serializing_none; 6 - 7 - #[derive(Serialize, Deserialize, Clone, Debug)] 8 - #[serde(rename_all = "lowercase")] 9 - pub enum EmbedType { 10 - Rich, 11 - Image, 12 - Video, 13 - GifV, 14 - Article, 15 - Link, 16 - } 17 - 18 - #[derive(Serialize, Clone)] 19 - pub enum EmbedMediaFlags { 20 - None, 21 - IsExplicit, 22 - IsAnimated, 23 - IsAnimatedAndExplicit, 24 - } 25 - 26 - #[skip_serializing_none] 27 - #[derive(Serialize, Deserialize, Clone, Default, Builder, Debug)] 28 - #[builder(try_setter, setter(into), default)] 29 - pub struct Embed { 30 - #[serde(rename = "type")] 31 - pub embed_type: Option<EmbedType>, 32 - pub url: Option<String>, 33 - pub title: Option<String>, 34 - pub color: Option<u32>, 35 - pub description: Option<String>, 36 - pub author: Option<EmbedAuthor>, 37 - pub image: Option<EmbedMedia>, 38 - pub thumbnail: Option<EmbedMedia>, 39 - pub footer: Option<EmbedFooter>, 40 - pub fields: Option<Vec<EmbedField>>, 41 - pub provider: Option<EmbedAuthor>, 42 - pub video: Option<EmbedMedia>, 43 - pub audio: Option<EmbedMedia>, 44 - pub nsfw: Option<bool>, 45 - pub children: Option<Vec<EmbedChild>>, 46 - } 47 - 48 - #[skip_serializing_none] 49 - #[derive(Serialize, Deserialize, Clone, Default, Builder, Debug)] 50 - #[builder(try_setter, setter(into), default)] 51 - pub struct EmbedChild { 52 - #[serde(rename = "type")] 53 - pub embed_type: Option<EmbedType>, 54 - pub url: Option<String>, 55 - pub title: Option<String>, 56 - pub color: Option<u32>, 57 - pub description: Option<String>, 58 - pub author: Option<EmbedAuthor>, 59 - pub image: Option<EmbedMedia>, 60 - pub thumbnail: Option<EmbedMedia>, 61 - pub footer: Option<EmbedFooter>, 62 - pub fields: Option<Vec<EmbedField>>, 63 - pub provider: Option<EmbedAuthor>, 64 - pub video: Option<EmbedMedia>, 65 - pub audio: Option<EmbedMedia>, 66 - pub nsfw: Option<bool>, 67 - } 68 - 69 - // Name is a required field even tho the js library makes it nullable 70 - 71 - #[skip_serializing_none] 72 - #[derive(Serialize, Deserialize, Clone, Default, Builder, Debug)] 73 - #[builder(try_setter, setter(into))] 74 - pub struct EmbedAuthor { 75 - pub name: String, 76 - #[builder(default)] 77 - pub url: Option<String>, 78 - #[builder(default)] 79 - pub icon_url: Option<String>, 80 - #[builder(default)] 81 - pub proxy_icon_url: Option<String>, 82 - } 83 - 84 - // Pretty sure the text should be a required field 85 - 86 - #[skip_serializing_none] 87 - #[derive(Serialize, Deserialize, Clone, Default, Builder, Debug)] 88 - #[builder(try_setter, setter(into))] 89 - pub struct EmbedFooter { 90 - pub text: Option<String>, 91 - #[builder(default)] 92 - pub icon_url: Option<String>, 93 - #[builder(default)] 94 - pub proxy_icon_url: Option<String>, 95 - } 96 - 97 - #[skip_serializing_none] 98 - #[derive(Serialize, Deserialize, Clone, Default, Builder, Debug)] 99 - #[builder(try_setter, setter(into))] 100 - pub struct EmbedField { 101 - pub name: String, 102 - pub value: String, 103 - #[builder(default)] 104 - pub inline: Option<bool>, 105 - } 106 - 107 - // WIll implement my own build method for EmbedMedia because i need to manually setup the width and height 108 - // So we don't put Default here as build_fn(skip) makes it so they won't be used 109 - 110 - #[skip_serializing_none] 111 - #[derive(Serialize, Deserialize, Clone, Builder, Debug)] 112 - #[builder(try_setter, setter(into), build_fn(skip))] 113 - pub struct EmbedMedia { 114 - pub url: String, 115 - pub proxy_url: Option<String>, 116 - pub content_type: Option<String>, 117 - pub content_hash: Option<String>, 118 - pub width: Option<u32>, 119 - pub height: Option<u32>, 120 - pub description: Option<String>, 121 - pub placeholder: Option<String>, 122 - pub duration: Option<u64>, 123 - pub flags: Option<u8>, 124 - } 125 - 126 - impl EmbedMediaBuilder { 127 - pub fn build(&mut self) -> Result<EmbedMedia, EmbedMediaBuilderError> { 128 - let media_builder = take(self); 129 - 130 - let url = if let Some(result) = media_builder.url { 131 - result 132 - } else { 133 - return Err(EmbedMediaBuilderError::UninitializedField( 134 - "The url field of the embed media must be used", 135 - )); 136 - }; 137 - 138 - let res_result: Option<(u32, u32)> = 139 - if media_builder.width.is_none() || media_builder.height.is_none() { 140 - let image = minreq::get(&url).send().unwrap(); 141 - let rust_img = image::load_from_memory(image.as_bytes()).unwrap(); 142 - Some((rust_img.width(), rust_img.width())) 143 - } else { 144 - None 145 - }; 146 - 147 - Ok(EmbedMedia { 148 - url, 149 - proxy_url: media_builder.proxy_url.unwrap_or_default(), 150 - content_type: media_builder.content_type.unwrap_or_default(), 151 - content_hash: media_builder.content_hash.unwrap_or_default(), 152 - width: if let Some(width) = media_builder.width { 153 - width 154 - } else { 155 - Some(res_result.unwrap().0) 156 - }, 157 - height: if let Some(height) = media_builder.height { 158 - height 159 - } else { 160 - Some(res_result.unwrap().1) 161 - }, 162 - description: media_builder.description.unwrap_or_default(), 163 - placeholder: media_builder.placeholder.unwrap_or_default(), 164 - duration: media_builder.duration.unwrap_or_default(), 165 - flags: media_builder.flags.unwrap_or(Some(0)), 166 - }) 167 - } 168 - } 169 - 170 - impl From<EmbedMediaFlags> for Option<u8> { 171 - fn from(value: EmbedMediaFlags) -> Self { 172 - let result = match value { 173 - EmbedMediaFlags::None => 0, 174 - EmbedMediaFlags::IsExplicit => 16, 175 - EmbedMediaFlags::IsAnimated => 32, 176 - EmbedMediaFlags::IsAnimatedAndExplicit => 48, 177 - }; 178 - Some(result) 179 - } 180 - }
-51
src/api/data_structure/message.rs
··· 1 - use derive_builder::Builder; 2 - use serde::Serialize; 3 - use serde_with::skip_serializing_none; 4 - 5 - use crate::Embed; 6 - 7 - #[derive(Clone, Builder)] 8 - #[builder(try_setter, setter(into))] 9 - pub struct FetchMessage { 10 - // Path params 11 - pub channel_id: String, 12 - pub message_id: String, 13 - } 14 - 15 - #[derive(Clone, Builder)] 16 - #[builder(try_setter, setter(into))] 17 - pub struct SendMessage { 18 - // Path params 19 - pub channel_id: String, 20 - 21 - pub content: String, 22 - #[builder(default)] 23 - pub embeds: Option<Vec<Embed>>, 24 - #[builder(default)] 25 - pub message_reference: Option<MessageReference>, 26 - } 27 - 28 - #[derive(Clone, Builder)] 29 - #[builder(try_setter, setter(into))] 30 - pub struct EditMessage { 31 - // Path params 32 - pub channel_id: String, 33 - pub message_id: String, 34 - 35 - pub content: String, 36 - #[builder(default)] 37 - pub embeds: Option<Vec<Embed>>, 38 - #[builder(default)] 39 - pub message_reference: Option<MessageReference>, 40 - } 41 - 42 - #[skip_serializing_none] 43 - #[derive(Clone, Serialize, Builder)] 44 - #[builder(try_setter, setter(into))] 45 - pub struct MessageReference { 46 - pub message_id: String, 47 - #[builder(default)] 48 - pub channel_id: Option<String>, 49 - #[builder(default)] 50 - pub guild_id: Option<String>, 51 - }
-17
src/api/data_structure/reaction.rs
··· 1 - use derive_builder::Builder; 2 - 3 - #[derive(Clone, Builder)] 4 - #[builder(try_setter, setter(into))] 5 - pub struct AddOwnReaciton { 6 - pub channel_id: String, 7 - pub message_id: String, 8 - pub emoji: String, 9 - } 10 - 11 - #[derive(Clone, Builder)] 12 - #[builder(try_setter, setter(into))] 13 - pub struct RemoveAllReaction { 14 - pub channel_id: String, 15 - pub message_id: String, 16 - pub emoji: String, 17 - }
-34
src/api/data_structure/role.rs
··· 1 - use derive_builder::Builder; 2 - 3 - #[derive(Clone, Builder)] 4 - #[builder(try_setter, setter(into))] 5 - pub struct AddRoleToMember { 6 - pub guild_id: String, 7 - pub user_id: String, 8 - pub role_id: String, 9 - } 10 - 11 - #[derive(Clone, Builder)] 12 - #[builder(try_setter, setter(into))] 13 - pub struct RemoveRoleFromMember { 14 - pub guild_id: String, 15 - pub user_id: String, 16 - pub role_id: String, 17 - } 18 - 19 - #[derive(Clone, Builder)] 20 - #[builder(try_setter, setter(into))] 21 - pub struct CreateRole { 22 - pub guild_id: String, 23 - 24 - pub name: String, 25 - pub color: u32, 26 - pub permission: String, 27 - } 28 - 29 - #[derive(Clone, Builder)] 30 - #[builder(try_setter, setter(into))] 31 - pub struct DeleteRole { 32 - pub guild_id: String, 33 - pub role_id: String, 34 - }
+1
src/api/guilds/mod.rs
··· 1 + pub mod roles;
+160
src/api/guilds/roles.rs
··· 1 + use anyhow::Result; 2 + use derive_builder::Builder; 3 + use serde::{Serialize, ser::SerializeMap}; 4 + 5 + use crate::{ 6 + api::{ApiCall, FluxerApiCallType, FluxerRsError}, 7 + serde::types::common::RoleData, 8 + }; 9 + 10 + #[derive(Clone, Debug, Builder)] 11 + #[builder(try_setter, setter(into))] 12 + pub struct AddRoleToMember { 13 + pub guild_id: String, 14 + pub user_id: String, 15 + pub role_id: String, 16 + } 17 + 18 + impl ApiCall for AddRoleToMember { 19 + type ReturnType = (); 20 + 21 + fn get_req( 22 + &self, 23 + req: reqwest::RequestBuilder, 24 + token: &str, 25 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 26 + Ok(req.header("Authorization", format!("Bot {token}"))) 27 + } 28 + 29 + fn get_info(&self) -> (String, FluxerApiCallType) { 30 + ( 31 + format!( 32 + "/guilds/{}/members/{}/roles/{}", 33 + self.guild_id, self.user_id, self.role_id 34 + ), 35 + FluxerApiCallType::Put, 36 + ) 37 + } 38 + 39 + fn get_data(&self, _body: &str) -> Result<Self::ReturnType, FluxerRsError> { 40 + Ok(()) 41 + } 42 + } 43 + 44 + #[derive(Clone, Debug, Builder)] 45 + #[builder(try_setter, setter(into))] 46 + pub struct RemoveRoleFromMember { 47 + // Path params 48 + pub guild_id: String, 49 + pub user_id: String, 50 + pub role_id: String, 51 + } 52 + 53 + impl ApiCall for RemoveRoleFromMember { 54 + type ReturnType = (); 55 + 56 + fn get_req( 57 + &self, 58 + req: reqwest::RequestBuilder, 59 + token: &str, 60 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 61 + Ok(req.header("Authorization", format!("Bot {token}"))) 62 + } 63 + 64 + fn get_info(&self) -> (String, FluxerApiCallType) { 65 + ( 66 + format!( 67 + "/guilds/{}/members/{}/roles/{}", 68 + self.guild_id, self.user_id, self.role_id 69 + ), 70 + FluxerApiCallType::Delete, 71 + ) 72 + } 73 + 74 + fn get_data(&self, _body: &str) -> Result<Self::ReturnType, FluxerRsError> { 75 + Ok(()) 76 + } 77 + } 78 + 79 + #[derive(Clone, Debug, Builder)] 80 + #[builder(try_setter, setter(into))] 81 + pub struct CreateRole { 82 + // Path params 83 + pub guild_id: String, 84 + 85 + pub name: String, 86 + pub color: u32, 87 + pub permission: String, 88 + } 89 + 90 + impl ApiCall for CreateRole { 91 + type ReturnType = RoleData; 92 + 93 + fn get_req( 94 + &self, 95 + req: reqwest::RequestBuilder, 96 + token: &str, 97 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 98 + let value = serde_json::to_string(self)?; 99 + 100 + Ok(req 101 + .body(value) 102 + .header("Authorization", format!("Bot {token}"))) 103 + } 104 + 105 + fn get_info(&self) -> (String, FluxerApiCallType) { 106 + ( 107 + format!("/guilds/{}/roles", self.guild_id), 108 + FluxerApiCallType::Post, 109 + ) 110 + } 111 + 112 + fn get_data(&self, body: &str) -> Result<Self::ReturnType, FluxerRsError> { 113 + let value = serde_json::from_str::<RoleData>(body)?; 114 + Ok(value) 115 + } 116 + } 117 + 118 + impl Serialize for CreateRole { 119 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 120 + where 121 + S: serde::Serializer, 122 + { 123 + let mut state = serializer.serialize_map(None)?; 124 + state.serialize_entry("name", &self.name)?; 125 + state.serialize_entry("color", &self.color)?; 126 + state.serialize_entry("permissions", &self.permission)?; 127 + state.end() 128 + } 129 + } 130 + 131 + #[derive(Clone, Debug, Builder)] 132 + #[builder(try_setter, setter(into))] 133 + pub struct DeleteRole { 134 + // Path params 135 + pub guild_id: String, 136 + pub role_id: String, 137 + } 138 + 139 + impl ApiCall for DeleteRole { 140 + type ReturnType = (); 141 + 142 + fn get_req( 143 + &self, 144 + req: reqwest::RequestBuilder, 145 + token: &str, 146 + ) -> Result<reqwest::RequestBuilder, FluxerRsError> { 147 + Ok(req.header("Authorization", format!("Bot {token}"))) 148 + } 149 + 150 + fn get_info(&self) -> (String, FluxerApiCallType) { 151 + ( 152 + format!("/guilds/{}/roles/{}", self.guild_id, self.role_id), 153 + FluxerApiCallType::Delete, 154 + ) 155 + } 156 + 157 + fn get_data(&self, _body: &str) -> Result<Self::ReturnType, FluxerRsError> { 158 + Ok(()) 159 + } 160 + }
+87
src/api/mod.rs
··· 1 + pub mod channels; 2 + pub mod common; 3 + pub mod guilds; 4 + 5 + use anyhow::Result; 6 + use derive_builder::Builder; 7 + use log::debug; 8 + 9 + use crate::error::FluxerRsError; 10 + 11 + pub enum FluxerApiCallType { 12 + Get, 13 + Post, 14 + Patch, 15 + Put, 16 + Delete, 17 + } 18 + 19 + /// Trait to use when implementing a new API call 20 + pub trait ApiCall { 21 + type ReturnType; 22 + 23 + fn get_req( 24 + &self, 25 + req: reqwest::RequestBuilder, 26 + token: &str, 27 + ) -> Result<reqwest::RequestBuilder, FluxerRsError>; 28 + fn get_info(&self) -> (String, FluxerApiCallType); 29 + fn get_data(&self, body: &str) -> Result<Self::ReturnType, FluxerRsError>; 30 + } 31 + 32 + #[derive(Clone, Default, Builder)] 33 + #[builder(try_setter, setter(into))] 34 + pub struct FluxerApiHandler { 35 + pub(crate) token: String, 36 + pub(crate) api_endpoint: String, 37 + pub(crate) http_client: reqwest::Client, 38 + } 39 + 40 + /// Implementation where all the api calls get called 41 + impl FluxerApiHandler { 42 + async fn call_api<T: ApiCall>(&self, call: &T) -> Result<reqwest::Response, FluxerRsError> { 43 + let (endpoint, call_type) = call.get_info(); 44 + let url = format!("{}{endpoint}", self.api_endpoint); 45 + 46 + let request = match call_type { 47 + FluxerApiCallType::Get => { 48 + let req = self.http_client.get(url); 49 + call.get_req(req, &self.token)? 50 + } 51 + FluxerApiCallType::Post => { 52 + let req = self.http_client.post(url); 53 + call.get_req(req, &self.token)? 54 + } 55 + FluxerApiCallType::Patch => { 56 + let req = self.http_client.patch(url); 57 + call.get_req(req, &self.token)? 58 + } 59 + FluxerApiCallType::Put => { 60 + let req = self.http_client.put(url); 61 + call.get_req(req, &self.token)? 62 + } 63 + FluxerApiCallType::Delete => { 64 + let req = self.http_client.delete(url); 65 + call.get_req(req, &self.token)? 66 + } 67 + }; 68 + 69 + let resp = request.send().await?; 70 + debug!("HTTPS RESPONSE: {:?}", resp); 71 + Ok(resp) 72 + } 73 + 74 + pub async fn execute_call<T: ApiCall>(&self, call: T) -> Result<T::ReturnType, FluxerRsError> { 75 + let resp = self.call_api(&call).await?; 76 + let text = &resp.text().await?; 77 + debug!("HTTPS RESPONSE CONTENT: {}", text); 78 + call.get_data(text) 79 + } 80 + 81 + pub async fn execute_call_resp<T: ApiCall>( 82 + &self, 83 + call: T, 84 + ) -> Result<reqwest::Response, FluxerRsError> { 85 + self.call_api(&call).await 86 + } 87 + }
+69
src/error.rs
··· 1 + use std::num::ParseIntError; 2 + 3 + use crate::{ 4 + api::{ 5 + FluxerApiHandlerBuilderError, 6 + channels::{ 7 + messages::{ 8 + EditMessageBuilderError, FetchMessageBuilderError, SendMessageBuilderError, 9 + }, 10 + reactions::{AddOwnReactionBuilderError, RemoveAllEmojiReactionsBuilderError}, 11 + }, 12 + guilds::roles::{ 13 + AddRoleToMemberBuilderError, CreateRoleBuilderError, DeleteRoleBuilderError, 14 + RemoveRoleFromMemberBuilderError, 15 + }, 16 + }, 17 + serde::types::message::MessageReferenceBuilderError, 18 + }; 19 + 20 + #[derive(Debug, thiserror::Error)] 21 + pub enum FluxerRsError { 22 + #[error("{0}")] 23 + ReqwestError(#[from] reqwest::Error), 24 + #[error("{0}")] 25 + SerdeError(#[from] serde_json::Error), 26 + #[error("{0}")] 27 + ParseIntError(#[from] ParseIntError), 28 + #[error("{0}")] 29 + SendError(String), 30 + // Custom 31 + #[error("{0}")] 32 + ApiHandlerError(#[from] ApiHandlerError), 33 + #[error("{0}")] 34 + CommandHandlerError(#[from] CommandHandlerError), 35 + } 36 + 37 + #[derive(Debug, thiserror::Error)] 38 + pub enum CommandHandlerError { 39 + #[error("The command {0} is unknown.")] 40 + UnknownCommand(String), 41 + #[error("{0}")] 42 + Custom(String), 43 + } 44 + 45 + #[derive(Debug, thiserror::Error)] 46 + pub enum ApiHandlerError { 47 + #[error("{0}")] 48 + FluxerApiHandlerBuilderError(#[from] FluxerApiHandlerBuilderError), 49 + #[error("{0}")] 50 + MessageReferenceBuilderError(#[from] MessageReferenceBuilderError), 51 + #[error("{0}")] 52 + FetchMessageBuilderError(#[from] FetchMessageBuilderError), 53 + #[error("{0}")] 54 + SendMessageBuilderError(#[from] SendMessageBuilderError), 55 + #[error("{0}")] 56 + EditMessageBuilderError(#[from] EditMessageBuilderError), 57 + #[error("{0}")] 58 + AddRoleToMemberBuilderError(#[from] AddRoleToMemberBuilderError), 59 + #[error("{0}")] 60 + RemoveRoleFromMemberBuilderError(#[from] RemoveRoleFromMemberBuilderError), 61 + #[error("{0}")] 62 + CreateRoleBuilderError(#[from] CreateRoleBuilderError), 63 + #[error("{0}")] 64 + DeleteRoleBuilderError(#[from] DeleteRoleBuilderError), 65 + #[error("{0}")] 66 + AddOwnReactionBuilderError(#[from] AddOwnReactionBuilderError), 67 + #[error("{0}")] 68 + RemoveAllEmojiReactionsBuilderError(#[from] RemoveAllEmojiReactionsBuilderError), 69 + }
+123 -108
src/fluxerbot.rs
··· 1 - use crate::api::FluxerApiHandler; 2 - use crate::gateway::op_handlers::dispatch::{DispatchHandlerTrait, handle_dispatch_events}; 3 - use crate::gateway::op_handlers::heartbeat::{heartbeat_ack_handler, heartbeat_handler}; 4 - use crate::gateway::op_handlers::identify::auth_handler; 5 - use crate::gateway::serde::deserialize::{ReceiveData, ReceiveDataType}; 6 - use async_trait::async_trait; 7 - use ezsockets::{Bytes, Client, ClientConfig, ClientExt, Error, Utf8Bytes, connect}; 8 - use log::{error, info}; 9 - 10 - //TODO: mark some modules as crate only as they are not used by crate users 11 - 12 - #[derive(Clone, Default)] 13 - pub struct FluxerBot { 14 - token: String, 15 - endpoint: String, 16 - pub api: FluxerApiHandler, 17 - } 18 - 19 - pub struct FluxerWebsocket<T: DispatchHandlerTrait + Send + Sync + 'static> { 20 - pub dispatch_handler: T, 21 - pub ws_handle: Client<FluxerWebsocket<T>>, 22 - bot_arc: FluxerBot, 23 - } 24 - 25 - impl FluxerBot { 26 - pub async fn init(token: String, wss_endpoint: String, api_endpoint: String) -> Self { 27 - FluxerBot { 28 - token: token.clone(), 29 - endpoint: wss_endpoint, 30 - api: FluxerApiHandler { 31 - token, 32 - api_endpoint, 33 - }, 34 - } 35 - } 36 - 37 - pub async fn start<T: DispatchHandlerTrait + Send + Sync + 'static>( 38 - &self, 39 - dispatch_handler: T, 40 - ) { 41 - info!("Init the bot"); 42 - 43 - let config: ClientConfig = ClientConfig::new(self.endpoint.as_str()); 44 - 45 - info!("Starting websocket"); 46 - let (_, future) = connect( 47 - |ws_handle| FluxerWebsocket { 48 - dispatch_handler, 49 - ws_handle, 50 - bot_arc: self.clone(), 51 - }, 52 - config, 53 - ) 54 - .await; 55 - let _ = future.await; 56 - } 57 - } 58 - 59 - impl<T> FluxerWebsocket<T> where T: Send + Sync + DispatchHandlerTrait + 'static {} 60 - 61 - #[async_trait] 62 - impl<T: DispatchHandlerTrait + Send + Sync + 'static> ClientExt for FluxerWebsocket<T> { 63 - type Call = (); 64 - 65 - async fn on_text(&mut self, text: Utf8Bytes) -> Result<(), Error> { 66 - let result: ReceiveData = match serde_json::from_slice(text.as_bytes()) { 67 - Ok(value) => value, 68 - Err(err) => { 69 - error!("Unhandled behavior: {err}"); 70 - error!("{}", text); 71 - panic!() 72 - } 73 - }; 74 - 75 - match result.d { 76 - ReceiveDataType::OP0(dispatch_event) => { 77 - handle_dispatch_events(dispatch_event, &self.dispatch_handler).await 78 - } 79 - ReceiveDataType::OP1(_op1_d) => heartbeat_handler(text, &self.ws_handle).await, 80 - ReceiveDataType::OP9(op9_d) => { 81 - if !op9_d { 82 - info!("-> {} Connection Invalid, Reauthenticating", text); 83 - auth_handler(&self.bot_arc.token, &self.ws_handle).await 84 - } else { 85 - //TODO: Implement session resume 86 - info!("-> {} Connection Invalid, Resuming", text); 87 - panic!() 88 - } 89 - } 90 - ReceiveDataType::OP10(_data) => heartbeat_handler(text, &self.ws_handle).await, 91 - ReceiveDataType::OP11 => heartbeat_ack_handler::<T>(text).await, 92 - } 93 - Ok(()) 94 - } 95 - 96 - async fn on_binary(&mut self, _bytes: Bytes) -> Result<(), Error> { 97 - Ok(()) 98 - } 99 - 100 - async fn on_call(&mut self, _call: Self::Call) -> Result<(), Error> { 101 - Ok(()) 102 - } 103 - 104 - async fn on_connect(&mut self) -> Result<(), Error> { 105 - auth_handler(&self.bot_arc.token, &self.ws_handle).await; 106 - Ok(()) 107 - } 108 - } 1 + use crate::api::{FluxerApiHandler, FluxerApiHandlerBuilder}; 2 + use crate::error::{ApiHandlerError, FluxerRsError}; 3 + use crate::gateway::dispatch::{DispatchHandlerTrait, handle_dispatch_events}; 4 + use crate::gateway::heartbeat::{heartbeat_ack_handler, heartbeat_handler}; 5 + use crate::gateway::identify::auth_handler; 6 + use crate::serde::types::gateway::{ReceiveData, ReceiveDataType}; 7 + use anyhow::Result; 8 + use async_trait::async_trait; 9 + use ezsockets::{Bytes, Client, ClientConfig, ClientExt, Error, Utf8Bytes, connect}; 10 + use log::{debug, error, info}; 11 + 12 + //TODO: mark some modules as crate only as they are not used by crate users 13 + 14 + #[derive(Clone, Default)] 15 + pub struct FluxerBot { 16 + wss_endpoint: String, 17 + pub api: FluxerApiHandler, 18 + } 19 + 20 + pub struct FluxerWebsocket<T: DispatchHandlerTrait + Send + Sync + 'static> { 21 + pub dispatch_handler: T, 22 + pub ws_handle: Client<FluxerWebsocket<T>>, 23 + bot: FluxerBot, 24 + } 25 + 26 + impl FluxerBot { 27 + pub fn init( 28 + token: impl Into<String>, 29 + wss_endpoint: impl Into<String>, 30 + api_endpoint: impl Into<String>, 31 + ) -> Result<Self, FluxerRsError> { 32 + let api = FluxerApiHandlerBuilder::default() 33 + .api_endpoint(api_endpoint) 34 + .token(token) 35 + .http_client(reqwest::Client::new()) 36 + .build() 37 + .map_err(ApiHandlerError::from)?; 38 + let wss_endpoint = wss_endpoint.into(); 39 + 40 + Ok(FluxerBot { wss_endpoint, api }) 41 + } 42 + 43 + pub async fn start<T: DispatchHandlerTrait + Send + Sync + 'static>(self, dispatch_handler: T) { 44 + info!("Initializing the bot"); 45 + 46 + let config: ClientConfig = ClientConfig::new(self.wss_endpoint.as_str()); 47 + 48 + info!("Starting websocket"); 49 + let (_, future) = connect( 50 + |ws_handle| FluxerWebsocket { 51 + dispatch_handler, 52 + ws_handle, 53 + bot: self, 54 + }, 55 + config, 56 + ) 57 + .await; 58 + match future.await { 59 + Ok(_) => { 60 + info!("Executing future") 61 + } 62 + Err(err) => { 63 + log::error!("{err}"); 64 + } 65 + } 66 + } 67 + } 68 + 69 + impl<T> FluxerWebsocket<T> where T: Send + Sync + DispatchHandlerTrait + 'static {} 70 + 71 + #[async_trait] 72 + impl<T: DispatchHandlerTrait + Send + Sync + 'static> ClientExt for FluxerWebsocket<T> { 73 + type Call = (); 74 + 75 + async fn on_text(&mut self, text: Utf8Bytes) -> Result<(), Error> { 76 + let result: Option<ReceiveData> = match serde_json::from_slice(text.as_bytes()) { 77 + Ok(value) => value, 78 + Err(err) => { 79 + error!("Unhandled behavior: {err}"); 80 + error!("{}", text); 81 + None 82 + } 83 + }; 84 + 85 + if let Some(result) = result { 86 + match result.d { 87 + ReceiveDataType::OP0(dispatch_event) => { 88 + handle_dispatch_events(dispatch_event, &self.dispatch_handler, &self.bot.api) 89 + .await? 90 + } 91 + ReceiveDataType::OP1(_op1_d) => heartbeat_handler(text, &self.ws_handle).await?, 92 + ReceiveDataType::OP9(op9_d) => { 93 + if !op9_d { 94 + debug!("-> {} Connection Invalid, Reauthenticating", text); 95 + 96 + auth_handler(&self.bot.api.token, &self.ws_handle).await? 97 + } else { 98 + //TODO: Implement proper session resume 99 + debug!("-> {} Connection Invalid, Resuming", text); 100 + panic!() 101 + } 102 + } 103 + ReceiveDataType::OP10(_data) => heartbeat_handler(text, &self.ws_handle).await?, 104 + ReceiveDataType::OP11 => heartbeat_ack_handler::<T>(text).await, 105 + } 106 + } 107 + 108 + Ok(()) 109 + } 110 + 111 + async fn on_binary(&mut self, _bytes: Bytes) -> Result<(), Error> { 112 + Ok(()) 113 + } 114 + 115 + async fn on_call(&mut self, _call: Self::Call) -> Result<(), Error> { 116 + Ok(()) 117 + } 118 + 119 + async fn on_connect(&mut self) -> Result<(), Error> { 120 + auth_handler(&self.bot.api.token, &self.ws_handle).await?; 121 + Ok(()) 122 + } 123 + }
-3
src/gateway.rs
··· 1 - pub mod op_handlers; 2 - pub mod serde; 3 - pub mod data_structure;
+3 -2
src/gateway/data_structure.rs src/serde/types.rs
··· 1 + pub mod common; 2 + pub mod gateway; 1 3 pub mod guild; 2 4 pub mod message; 3 - pub mod common; 4 - pub mod user; 5 + pub mod user;
+16 -7
src/gateway/data_structure/common.rs src/serde/types/common.rs
··· 1 1 use serde::Deserialize; 2 2 use serde_json::Value; 3 3 4 + use crate::serde::types::user::RtcRegion; 5 + 6 + /// Data for a channel, received from a `CHANNEL_UPDATE` dispatch event. <br> 4 7 #[derive(Deserialize, Debug)] 5 8 pub struct ChannelData { 6 9 pub guild_id: String, ··· 17 20 pub rate_limit_per_user: Option<i64>, 18 21 pub topic: Option<Value>, 19 22 pub bitrate: Option<i64>, 20 - pub rtc_region: Option<Value>, 23 + pub rtc_region: Option<RtcRegion>, 21 24 pub user_limit: Option<i64>, 22 25 } 23 26 27 + /// Data for a guild member, received from a `GUILD_MEMBER_UPDATE` dispatch event. <br> 24 28 #[derive(Deserialize, Debug)] 25 29 pub struct MemberData { 26 30 pub accent_color: Option<Value>, ··· 36 40 pub user: Option<UserData>, 37 41 } 38 42 43 + /// Data for a user, used in [`MemberData`] 39 44 #[derive(Deserialize, Debug)] 40 45 pub struct UserData { 41 46 pub avatar: Value, ··· 48 53 pub username: String, 49 54 } 50 55 56 + /// Data for a guild's properties, received from a `GUILD_UPDATE` dispatch event. 51 57 #[derive(Deserialize, Debug)] 52 58 pub struct GuildPropertiesData { 53 59 pub afk_channel_id: Value, ··· 75 81 pub splash_height: Value, 76 82 pub splash_width: Value, 77 83 pub system_channel_flags: i64, 78 - pub system_channel_id: String, 84 + pub system_channel_id: Option<String>, 79 85 pub vanity_url_code: Value, 80 86 pub verification_level: i64, 81 87 } 82 88 89 + /// Data for a role, returned as part of other data types 83 90 #[derive(Deserialize, Debug)] 84 91 pub struct RoleData { 92 + pub id: String, 93 + pub name: String, 85 94 pub color: i64, 95 + pub position: i64, 96 + pub hoist_position: Option<i64>, 97 + pub permissions: String, 86 98 pub hoist: bool, 87 - pub hoist_position: Value, 88 - pub id: String, 89 99 pub mentionable: bool, 90 - pub name: String, 91 - pub permissions: String, 92 - pub position: i64, 93 100 } 94 101 102 + /// Data for a message's author, used in [`MessageData`](crates::serde::types::message::MessageData) 95 103 #[derive(Deserialize, Debug)] 96 104 pub struct AuthorData { 97 105 pub avatar: Option<String>, ··· 103 111 pub username: String, 104 112 } 105 113 114 + /// Data for an emoji returned as part of other data types 106 115 #[derive(Deserialize, Debug)] 107 116 pub struct EmojiData { 108 117 pub name: String,
+10 -3
src/gateway/data_structure/guild.rs src/serde/types/guild.rs
··· 1 - use crate::gateway::data_structure::common::{ 1 + use serde::Deserialize; 2 + use serde_json::Value; 3 + 4 + use crate::serde::types::common::{ 2 5 ChannelData, EmojiData, GuildPropertiesData, MemberData, RoleData, 3 6 }; 4 - use serde::Deserialize; 5 - use serde_json::Value; 6 7 8 + /// Data returned by the `GUILD_DELETE` dispatch event 7 9 #[derive(Deserialize)] 8 10 pub struct GuildDeleteData { 9 11 #[serde(rename = "id")] ··· 11 13 pub unavailable: bool, 12 14 } 13 15 16 + /// Data returned by the `GUILD_CREATE` dispatch event 14 17 #[derive(Deserialize)] 15 18 pub struct GuildCreateData { 16 19 pub channels: Vec<ChannelData>, ··· 27 30 pub voice_states: Vec<Value>, 28 31 } 29 32 33 + /// Data returned by the `GUILD_EMOJIS_UPDATE` dispatch event 30 34 #[derive(Deserialize, Debug)] 31 35 pub struct GuildEmojisUpdateData { 32 36 pub guild_id: String, 33 37 pub emojis: Vec<EmojiData>, 34 38 } 35 39 40 + /// Data returned by the `GUILD_ROLE_CREATE` and `GUILD_ROLE_UPDATE` dispatch events 36 41 #[derive(Deserialize, Debug)] 37 42 pub struct GuildRoleCreateData { 38 43 pub guild_id: String, 39 44 pub role: RoleData, 40 45 } 41 46 47 + /// Data returned by the `GUILD_ROLE_UPDATE_BULK` dispatch event 42 48 #[derive(Deserialize, Debug)] 43 49 pub struct GuildRoleUpdateBulkData { 44 50 pub guild_id: String, 45 51 pub roles: Vec<RoleData>, 46 52 } 47 53 54 + /// Data returned by the `GUILD_ROLE_DELETE` dispatch event 48 55 #[derive(Deserialize, Debug)] 49 56 pub struct GuildRoleDeleteData { 50 57 pub guild_id: String,
-48
src/gateway/data_structure/message.rs
··· 1 - use serde::Deserialize; 2 - use serde_json::Value; 3 - 4 - use crate::{ 5 - Embed, 6 - gateway::data_structure::common::{AuthorData, EmojiData, MemberData}, 7 - }; 8 - 9 - #[derive(Deserialize, Debug)] 10 - pub struct MessageEventData { 11 - pub attachments: Option<Vec<Value>>, 12 - pub author: Option<AuthorData>, 13 - pub channel_id: String, 14 - pub channel_type: Option<i64>, 15 - pub content: String, 16 - pub edited_timestamp: Option<Value>, 17 - pub embeds: Option<Vec<Embed>>, 18 - pub flags: Option<i64>, 19 - pub guild_id: Option<String>, 20 - pub id: String, 21 - pub member: Option<MemberData>, 22 - pub mention_everyone: Option<bool>, 23 - pub nonce: Option<String>, 24 - pub pinned: Option<bool>, 25 - pub stickers: Option<Vec<Value>>, 26 - pub timestamp: Option<String>, 27 - #[serde(rename = "type")] 28 - pub message_type: Option<i64>, 29 - } 30 - 31 - #[derive(Deserialize, Debug)] 32 - pub struct TypingEventData { 33 - pub channel_id: String, 34 - pub guild_id: String, 35 - pub member: MemberData, 36 - pub timestamp: i64, 37 - pub user_id: String, 38 - } 39 - 40 - #[derive(Deserialize, Debug)] 41 - pub struct MessageReactData { 42 - pub channel_id: String, 43 - pub emoji: EmojiData, 44 - pub guild_id: String, 45 - pub member: Option<MemberData>, 46 - pub message_id: String, 47 - pub user_id: Option<String>, 48 - }
+10 -4
src/gateway/data_structure/user.rs src/serde/types/user.rs
··· 1 1 use serde::Deserialize; 2 2 use serde_json::Value; 3 3 4 + /// Data returned by the `READY` dispatch event 4 5 #[derive(Deserialize)] 5 6 pub struct ReadyData { 6 7 pub country_code: String, ··· 22 23 pub version: i64, 23 24 } 24 25 26 + /// Data for a session used in [`ReadyData`] 25 27 #[derive(Deserialize)] 26 28 pub struct Session { 27 29 pub afk: bool, ··· 30 32 pub status: String, 31 33 } 32 34 35 + /// Data for a logged in user used in [`ReadyData`] 33 36 #[derive(Deserialize)] 34 37 pub struct LoggedInUser { 35 38 pub accent_color: Value, ··· 65 68 pub premium_lifetime_sequence: Value, 66 69 pub premium_purchase_disabled: bool, 67 70 pub premium_since: Value, 68 - pub premium_type: i64, 71 + pub premium_type: Option<i64>, 69 72 pub premium_until: Value, 70 73 pub premium_will_cancel: bool, 71 74 pub pronouns: Value, ··· 77 80 pub verified: bool, 78 81 } 79 82 80 - #[derive(Deserialize)] 83 + /// Data for a rtc region used in [`ReadyData`] 84 + #[derive(Debug, Deserialize)] 81 85 pub struct RtcRegion { 82 86 pub emoji: String, 83 87 pub id: String, 84 88 pub name: String, 85 89 } 86 90 91 + /// Data for notes used in [`ReadyData`] 87 92 #[derive(Deserialize)] 88 93 pub struct Notes {} 89 94 95 + /// Data returned by the `SESSIONS_REPLACE` dispatch event 90 96 #[derive(Deserialize)] 91 - pub struct SessionReplaceData{ 97 + pub struct SessionReplaceData { 92 98 pub afk: bool, 93 99 pub mobile: bool, 94 100 pub session_id: String, 95 101 pub status: String, 96 - } 102 + }
+24
src/gateway/dispatch.rs
··· 1 + use macros::dispatch; 2 + 3 + dispatch!( 4 + ["READY", ReadyData], 5 + ["GUILD_DELETE", GuildDeleteData], 6 + ["GUILD_CREATE", GuildCreateData], 7 + ["GUILD_UPDATE", GuildPropertiesData], 8 + ["GUILD_EMOJIS_UPDATE", GuildEmojisUpdateData], 9 + ["GUILD_ROLE_CREATE", GuildRoleCreateData], 10 + ["GUILD_ROLE_UPDATE", GuildRoleCreateData], 11 + ["GUILD_ROLE_UPDATE_BULK", GuildRoleUpdateBulkData], 12 + ["GUILD_ROLE_DELETE", GuildRoleDeleteData], 13 + ["GUILD_MEMBER_UPDATE", MemberData], 14 + ["MESSAGE_CREATE", MessageData], 15 + ["MESSAGE_DELETE", MessageData], 16 + ["MESSAGE_UPDATE", MessageData], 17 + ["TYPING_START", TypingStartData], 18 + ["MESSAGE_REACTION_ADD", MessageReactionData], 19 + ["MESSAGE_REACTION_REMOVE", MessageReactionData], 20 + ["MESSAGE_REACTION_REMOVE_EMOJI", MessageReactionData], 21 + ["SESSIONS_REPLACE", Vec<SessionReplaceData>], 22 + ["CHANNEL_UPDATE", ChannelData], 23 + ["None", Option], 24 + );
+37
src/gateway/heartbeat.rs
··· 1 + use anyhow::Result; 2 + use ezsockets::{Client, Utf8Bytes}; 3 + use log::debug; 4 + 5 + use crate::error::FluxerRsError; 6 + use crate::fluxerbot::FluxerWebsocket; 7 + use crate::{ 8 + gateway::dispatch::DispatchHandlerTrait, 9 + serde::types::gateway::{SendData, SendDataType}, 10 + }; 11 + 12 + pub async fn heartbeat_handler<T: DispatchHandlerTrait + Send + Sync + 'static>( 13 + text: Utf8Bytes, 14 + client_handle: &Client<FluxerWebsocket<T>>, 15 + ) -> Result<(), FluxerRsError> { 16 + debug!("-> {}", text); 17 + 18 + let heartbeat = SendData { 19 + op: 1, 20 + d: SendDataType::OP1(None), 21 + }; 22 + 23 + let heartbeat_string = serde_json::to_string(&heartbeat)?; 24 + client_handle 25 + .text(&heartbeat_string) 26 + .map_err(|err| FluxerRsError::SendError(err.to_string()))?; 27 + 28 + debug!("<- {}", heartbeat_string); 29 + 30 + Ok(()) 31 + } 32 + 33 + pub async fn heartbeat_ack_handler<T: DispatchHandlerTrait + Send + Sync + 'static>( 34 + text: Utf8Bytes, 35 + ) { 36 + debug!("-> {} Heartbeat acknowledged", text); 37 + }
+2 -2
src/gateway/op_handlers.rs src/gateway/mod.rs
··· 1 - pub mod heartbeat; 2 1 pub mod dispatch; 3 - pub mod identify; 2 + pub mod heartbeat; 3 + pub mod identify;
-228
src/gateway/op_handlers/dispatch.rs
··· 1 - use async_trait::async_trait; 2 - use log::info; 3 - 4 - use crate::gateway::data_structure::common::{ChannelData, GuildPropertiesData, MemberData}; 5 - use crate::gateway::data_structure::guild::{ 6 - GuildEmojisUpdateData, GuildRoleCreateData, GuildRoleDeleteData, GuildRoleUpdateBulkData, 7 - }; 8 - use crate::gateway::data_structure::message::MessageReactData; 9 - use crate::gateway::data_structure::user::ReadyData; 10 - use crate::gateway::data_structure::user::SessionReplaceData; 11 - use crate::gateway::data_structure::{ 12 - guild::{GuildCreateData, GuildDeleteData}, 13 - message::{MessageEventData, TypingEventData}, 14 - }; 15 - 16 - pub enum DispatchEvent { 17 - Ready(Box<ReadyData>), 18 - GuildDelete(GuildDeleteData), 19 - GuildCreate(Box<GuildCreateData>), 20 - GuildUpdate(GuildPropertiesData), 21 - GuildEmojisUpdate(GuildEmojisUpdateData), 22 - GuildRoleCreate(GuildRoleCreateData), 23 - GuildRoleUpdate(GuildRoleCreateData), 24 - GuildRoleUpdateBulk(GuildRoleUpdateBulkData), 25 - GuildRoleDelete(GuildRoleDeleteData), 26 - GuildMemberUpdate(MemberData), 27 - MessageCreate(MessageEventData), 28 - MessageDelete(MessageEventData), 29 - MessageUpdate(MessageEventData), 30 - TypingStart(TypingEventData), 31 - TypingStop(TypingEventData), 32 - MessageReactionAdd(MessageReactData), 33 - MessageReactionRemove(MessageReactData), 34 - MessageReactionRemoveEmoji(MessageReactData), 35 - SessionReplace(Vec<SessionReplaceData>), 36 - ChannelUpdate(ChannelData), 37 - } 38 - 39 - pub trait DispatchHandlerTrait { 40 - fn handle_ready_dispatch(&self, _data: Box<ReadyData>) -> impl Future<Output = ()> + Send { 41 - async move { info!("-> [DISPATCH::READY]") } 42 - } 43 - 44 - fn handle_guild_delete_dispatch( 45 - &self, 46 - _data: GuildDeleteData, 47 - ) -> impl Future<Output = ()> + Send { 48 - async move { info!("-> [DISPATCH::GUILD_DELETE]") } 49 - } 50 - 51 - fn handle_guild_create_dispatch( 52 - &self, 53 - _data: Box<GuildCreateData>, 54 - ) -> impl Future<Output = ()> + Send { 55 - async move { info!("-> [DISPATCH::GUILD_CREATE]") } 56 - } 57 - 58 - fn handle_guild_update_dispatch( 59 - &self, 60 - _data: GuildPropertiesData, 61 - ) -> impl Future<Output = ()> + Send { 62 - async move { info!("-> [DISPATCH::GUILD_UPDATE]") } 63 - } 64 - 65 - fn handle_guild_emojis_update_dispatch( 66 - &self, 67 - _data: GuildEmojisUpdateData, 68 - ) -> impl Future<Output = ()> + Send { 69 - async move { info!("-> [DISPATCH::GUILD_EMOJIS_UPDATE]") } 70 - } 71 - 72 - fn handle_guild_role_create_dispatch( 73 - &self, 74 - _data: GuildRoleCreateData, 75 - ) -> impl Future<Output = ()> + Send { 76 - async move { info!("-> [DISPATCH::GUILD_ROLE_CREATE]") } 77 - } 78 - 79 - fn handle_guild_role_update_dispatch( 80 - &self, 81 - _data: GuildRoleCreateData, 82 - ) -> impl Future<Output = ()> + Send { 83 - async move { info!("-> [DISPATCH::GUILD_ROLE_UPDATE]") } 84 - } 85 - 86 - fn handle_guild_role_update_bulk_dispatch( 87 - &self, 88 - _data: GuildRoleUpdateBulkData, 89 - ) -> impl Future<Output = ()> + Send { 90 - async move { info!("-> [DISPATCH::GUILD_ROLE_UPDATE_BULK]") } 91 - } 92 - 93 - fn handle_guild_role_delete_dispatch( 94 - &self, 95 - _data: GuildRoleDeleteData, 96 - ) -> impl Future<Output = ()> + Send { 97 - async move { info!("-> [DISPATCH::GUILD_ROLE_DELETE]") } 98 - } 99 - 100 - fn handle_guild_member_update_dispatch( 101 - &self, 102 - _data: MemberData, 103 - ) -> impl Future<Output = ()> + Send { 104 - async move { info!("-> [DISPATCH::GUILD_MEMBER_UPDATE]") } 105 - } 106 - 107 - fn handle_message_create_dispatch( 108 - &self, 109 - _data: MessageEventData, 110 - ) -> impl Future<Output = ()> + Send { 111 - async move { info!("-> [DISPATCH::MESSAGE_CREATE]") } 112 - } 113 - 114 - fn handle_message_delete_dispatch( 115 - &self, 116 - _data: MessageEventData, 117 - ) -> impl Future<Output = ()> + Send { 118 - async move { info!("-> [DISPATCH::MESSAGE_DELETE]") } 119 - } 120 - 121 - fn handle_message_update_dispatch( 122 - &self, 123 - _data: MessageEventData, 124 - ) -> impl Future<Output = ()> + Send { 125 - async move { info!("-> [DISPATCH::MESSAGE_UPDATE]") } 126 - } 127 - 128 - fn handle_typing_start_dispatch( 129 - &self, 130 - _data: TypingEventData, 131 - ) -> impl Future<Output = ()> + Send { 132 - async move { info!("-> [DISPATCH::TYPING_START]") } 133 - } 134 - 135 - fn handle_typing_stop_dispatch( 136 - &self, 137 - _data: TypingEventData, 138 - ) -> impl Future<Output = ()> + Send { 139 - async move { info!("-> [DISPATCH::TYPING_STOP]") } 140 - } 141 - 142 - fn handle_message_reaction_add_dispatch( 143 - &self, 144 - _data: MessageReactData, 145 - ) -> impl Future<Output = ()> + Send { 146 - async move { info!("-> [DISPATCH::MESSAGE_REACTION_ADD]") } 147 - } 148 - 149 - fn handle_message_reaction_remove_dispatch( 150 - &self, 151 - _data: MessageReactData, 152 - ) -> impl Future<Output = ()> + Send { 153 - async move { info!("-> [DISPATCH::MESSAGE_REACTION_REMOVE]") } 154 - } 155 - fn handle_message_reaction_remove_emoji_dispatch( 156 - &self, 157 - _data: MessageReactData, 158 - ) -> impl Future<Output = ()> + Send { 159 - async move { info!("-> [DISPATCH::MESSAGE_REACTION_REMOVE_EMOJI]") } 160 - } 161 - 162 - fn handle_session_replace_dispatch( 163 - &self, 164 - _data: Vec<SessionReplaceData>, 165 - ) -> impl Future<Output = ()> + Send { 166 - async move { info!("-> [DISPATCH::SESSION_REPLACE]",) } 167 - } 168 - 169 - fn handle_channel_update_dispatch( 170 - &self, 171 - _data: ChannelData, 172 - ) -> impl Future<Output = ()> + Send { 173 - async move { info!("-> [DISPATCH::CHANNEL_UPDATE]",) } 174 - } 175 - } 176 - 177 - #[derive(Default)] 178 - pub struct DispatchHandler; 179 - #[async_trait] 180 - impl DispatchHandlerTrait for DispatchHandler {} 181 - 182 - pub async fn handle_dispatch_events<T: DispatchHandlerTrait + Send + Sync + 'static>( 183 - dispatch_event: Box<DispatchEvent>, 184 - handler: &T, 185 - ) { 186 - match *dispatch_event { 187 - DispatchEvent::Ready(data) => handler.handle_ready_dispatch(data).await, 188 - DispatchEvent::GuildDelete(data) => handler.handle_guild_delete_dispatch(data).await, 189 - DispatchEvent::GuildCreate(data) => handler.handle_guild_create_dispatch(data).await, 190 - DispatchEvent::GuildUpdate(data) => handler.handle_guild_update_dispatch(data).await, 191 - DispatchEvent::GuildEmojisUpdate(data) => { 192 - handler.handle_guild_emojis_update_dispatch(data).await 193 - } 194 - DispatchEvent::GuildRoleCreate(data) => { 195 - handler.handle_guild_role_create_dispatch(data).await 196 - } 197 - DispatchEvent::GuildRoleUpdate(data) => { 198 - handler.handle_guild_role_update_dispatch(data).await 199 - } 200 - DispatchEvent::GuildRoleUpdateBulk(data) => { 201 - handler.handle_guild_role_update_bulk_dispatch(data).await 202 - } 203 - DispatchEvent::GuildRoleDelete(data) => { 204 - handler.handle_guild_role_delete_dispatch(data).await 205 - } 206 - DispatchEvent::GuildMemberUpdate(data) => { 207 - handler.handle_guild_member_update_dispatch(data).await 208 - } 209 - DispatchEvent::MessageCreate(data) => handler.handle_message_create_dispatch(data).await, 210 - DispatchEvent::MessageDelete(data) => handler.handle_message_delete_dispatch(data).await, 211 - DispatchEvent::MessageUpdate(data) => handler.handle_message_update_dispatch(data).await, 212 - DispatchEvent::MessageReactionRemoveEmoji(data) => { 213 - handler 214 - .handle_message_reaction_remove_emoji_dispatch(data) 215 - .await 216 - } 217 - DispatchEvent::TypingStart(data) => handler.handle_typing_start_dispatch(data).await, 218 - DispatchEvent::TypingStop(data) => handler.handle_typing_stop_dispatch(data).await, 219 - DispatchEvent::MessageReactionAdd(data) => { 220 - handler.handle_message_reaction_add_dispatch(data).await 221 - } 222 - DispatchEvent::MessageReactionRemove(data) => { 223 - handler.handle_message_reaction_remove_dispatch(data).await 224 - } 225 - DispatchEvent::SessionReplace(data) => handler.handle_session_replace_dispatch(data).await, 226 - DispatchEvent::ChannelUpdate(data) => handler.handle_channel_update_dispatch(data).await, 227 - } 228 - }
-31
src/gateway/op_handlers/heartbeat.rs
··· 1 - use ezsockets::{Client, Utf8Bytes}; 2 - use log::info; 3 - 4 - use crate::fluxerbot::FluxerWebsocket; 5 - use crate::gateway::{ 6 - op_handlers::dispatch::DispatchHandlerTrait, 7 - serde::serialize::{SendData, SendDataType}, 8 - }; 9 - 10 - pub async fn heartbeat_handler<T: DispatchHandlerTrait + Send + Sync + 'static>( 11 - text: Utf8Bytes, 12 - client_handle: &Client<FluxerWebsocket<T>>, 13 - ) { 14 - info!("-> {}", text); 15 - 16 - let heartbeat = SendData { 17 - op: 1, 18 - d: SendDataType::OP1(None), 19 - }; 20 - 21 - let heartbeat_string = serde_json::to_string(&heartbeat).unwrap(); 22 - client_handle.text(&heartbeat_string).unwrap(); 23 - 24 - info!("<- {}", heartbeat_string); 25 - } 26 - 27 - pub async fn heartbeat_ack_handler<T: DispatchHandlerTrait + Send + Sync + 'static>( 28 - text: Utf8Bytes, 29 - ) { 30 - info!("-> {} Heartbeat acknowledged", text); 31 - }
+33 -29
src/gateway/op_handlers/identify.rs src/gateway/identify.rs
··· 1 - use crate::fluxerbot::FluxerWebsocket; 2 - use crate::gateway::op_handlers::dispatch::DispatchHandlerTrait; 3 - use crate::gateway::serde::serialize::{OP2D, OP2DProps, SendData, SendDataType}; 4 - use ezsockets::Client; 5 - 6 - pub async fn auth_handler<T: DispatchHandlerTrait + Send + Sync + 'static>( 7 - token: &str, 8 - client_handle: &Client<FluxerWebsocket<T>>, 9 - ) { 10 - let auth_string = serde_json::to_string(&SendData { 11 - d: SendDataType::OP2(OP2D { 12 - token: token.to_string(), 13 - properties: OP2DProps { 14 - os: "Linux".to_string(), 15 - browser: "Fluxer-rs".to_string(), 16 - device: "x64".to_string(), 17 - }, 18 - }), 19 - op: 2, 20 - }) 21 - .unwrap(); 22 - 23 - match client_handle.text(auth_string) { 24 - Ok(_) => {} 25 - Err(err) => { 26 - panic!("{err}") 27 - } 28 - }; 29 - } 1 + use crate::error::FluxerRsError; 2 + use crate::fluxerbot::FluxerWebsocket; 3 + use crate::gateway::dispatch::DispatchHandlerTrait; 4 + use crate::serde::types::gateway::{OP2D, OP2DProps, SendData, SendDataType}; 5 + use anyhow::Result; 6 + use ezsockets::Client; 7 + use log::error; 8 + 9 + pub async fn auth_handler<T: DispatchHandlerTrait + Send + Sync + 'static>( 10 + token: &str, 11 + client_handle: &Client<FluxerWebsocket<T>>, 12 + ) -> Result<(), FluxerRsError> { 13 + let auth_string = serde_json::to_string(&SendData { 14 + d: SendDataType::OP2(OP2D { 15 + token: token.to_string(), 16 + properties: OP2DProps { 17 + os: "Linux".to_string(), 18 + browser: "Fluxer-rs".to_string(), 19 + device: "x64".to_string(), 20 + }, 21 + }), 22 + op: 2, 23 + })?; 24 + 25 + match client_handle.text(auth_string) { 26 + Ok(_) => {} 27 + Err(err) => { 28 + error!("{err}") 29 + } 30 + }; 31 + 32 + Ok(()) 33 + }
-2
src/gateway/serde.rs
··· 1 - pub mod deserialize; 2 - pub mod serialize;
-139
src/gateway/serde/deserialize.rs
··· 1 - #[cfg(not(debug_assertions))] 2 - use log::{error, info}; 3 - use serde::{Deserialize, Serialize, de}; 4 - use serde_json::Value; 5 - 6 - use crate::gateway::data_structure::common::{ChannelData, GuildPropertiesData, MemberData}; 7 - use crate::gateway::data_structure::guild::{ 8 - GuildEmojisUpdateData, GuildRoleCreateData, GuildRoleDeleteData, GuildRoleUpdateBulkData, 9 - }; 10 - use crate::gateway::data_structure::message::MessageReactData; 11 - use crate::gateway::data_structure::user::ReadyData; 12 - use crate::gateway::data_structure::user::SessionReplaceData; 13 - use crate::gateway::{ 14 - data_structure::{ 15 - guild::{GuildCreateData, GuildDeleteData}, 16 - message::{MessageEventData, TypingEventData}, 17 - }, 18 - op_handlers::dispatch::DispatchEvent, 19 - }; 20 - 21 - pub struct ReceiveData { 22 - pub d: ReceiveDataType, 23 - pub op: u8, 24 - } 25 - 26 - pub enum ReceiveDataType { 27 - OP0(Box<DispatchEvent>), 28 - OP1(Option<u32>), 29 - OP9(bool), 30 - OP10(OP10D), 31 - OP11, 32 - } 33 - 34 - #[derive(Serialize, Deserialize)] 35 - pub struct OP10D { 36 - pub heartbeat_interval: u32, 37 - } 38 - 39 - impl<'de> Deserialize<'de> for ReceiveData { 40 - fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { 41 - let value = Value::deserialize(deserializer)?; 42 - let op = value["op"] 43 - .as_u64() 44 - .ok_or_else(|| de::Error::missing_field("op"))? as u8; 45 - 46 - let d = match op { 47 - 0 => { 48 - let dispatch_event = match value["t"].as_str().unwrap() { 49 - "READY" => DispatchEvent::Ready(Box::new( 50 - ReadyData::deserialize(&value["d"]).map_err(de::Error::custom)?, 51 - )), 52 - "GUILD_DELETE" => DispatchEvent::GuildDelete( 53 - GuildDeleteData::deserialize(&value["d"]).map_err(de::Error::custom)?, 54 - ), 55 - "GUILD_CREATE" => DispatchEvent::GuildCreate(Box::new( 56 - GuildCreateData::deserialize(&value["d"]).map_err(de::Error::custom)?, 57 - )), 58 - "GUILD_UPDATE" => DispatchEvent::GuildUpdate( 59 - GuildPropertiesData::deserialize(&value["d"]).map_err(de::Error::custom)?, 60 - ), 61 - "GUILD_EMOJIS_UPDATE" => DispatchEvent::GuildEmojisUpdate( 62 - GuildEmojisUpdateData::deserialize(&value["d"]) 63 - .map_err(de::Error::custom)?, 64 - ), 65 - "GUILD_ROLE_CREATE" => DispatchEvent::GuildRoleCreate( 66 - GuildRoleCreateData::deserialize(&value["d"]).map_err(de::Error::custom)?, 67 - ), 68 - "GUILD_ROLE_UPDATE" => DispatchEvent::GuildRoleUpdate( 69 - GuildRoleCreateData::deserialize(&value["d"]).map_err(de::Error::custom)?, 70 - ), 71 - "GUILD_ROLE_UPDATE_BULK" => DispatchEvent::GuildRoleUpdateBulk( 72 - GuildRoleUpdateBulkData::deserialize(&value["d"]) 73 - .map_err(de::Error::custom)?, 74 - ), 75 - "GUILD_ROLE_DELETE" => DispatchEvent::GuildRoleDelete( 76 - GuildRoleDeleteData::deserialize(&value["d"]).map_err(de::Error::custom)?, 77 - ), 78 - "GUILD_MEMBER_UPDATE" => DispatchEvent::GuildMemberUpdate( 79 - MemberData::deserialize(&value["d"]).map_err(de::Error::custom)?, 80 - ), 81 - "MESSAGE_CREATE" => DispatchEvent::MessageCreate( 82 - MessageEventData::deserialize(&value["d"]).map_err(de::Error::custom)?, 83 - ), 84 - "MESSAGE_DELETE" => DispatchEvent::MessageDelete( 85 - MessageEventData::deserialize(&value["d"]).map_err(de::Error::custom)?, 86 - ), 87 - "MESSAGE_UPDATE" => DispatchEvent::MessageUpdate( 88 - MessageEventData::deserialize(&value["d"]).map_err(de::Error::custom)?, 89 - ), 90 - "TYPING_START" => DispatchEvent::TypingStart( 91 - TypingEventData::deserialize(&value["d"]).map_err(de::Error::custom)?, 92 - ), 93 - "TYPING_STOP" => DispatchEvent::TypingStop( 94 - TypingEventData::deserialize(&value["d"]).map_err(de::Error::custom)?, 95 - ), 96 - "MESSAGE_REACTION_ADD" => DispatchEvent::MessageReactionAdd( 97 - MessageReactData::deserialize(&value["d"]).map_err(de::Error::custom)?, 98 - ), 99 - "MESSAGE_REACTION_REMOVE" => DispatchEvent::MessageReactionRemove( 100 - MessageReactData::deserialize(&value["d"]).map_err(de::Error::custom)?, 101 - ), 102 - "MESSAGE_REACTION_REMOVE_EMOJI" => DispatchEvent::MessageReactionRemoveEmoji( 103 - MessageReactData::deserialize(&value["d"]).map_err(de::Error::custom)?, 104 - ), 105 - "SESSIONS_REPLACE" => DispatchEvent::SessionReplace( 106 - Vec::<SessionReplaceData>::deserialize(&value["d"]) 107 - .map_err(de::Error::custom)?, 108 - ), 109 - "CHANNEL_UPDATE" => DispatchEvent::ChannelUpdate( 110 - ChannelData::deserialize(&value["d"]).map_err(de::Error::custom)?, 111 - ), 112 - #[cfg(not(debug_assertions))] 113 - _ => error!("Unimplemented dispatch event: {}", value), 114 - 115 - #[cfg(debug_assertions)] 116 - _ => panic!("Unimplemented dispatch event: {}", value), 117 - }; 118 - ReceiveDataType::OP0(Box::new(dispatch_event)) 119 - } 120 - 1 => { 121 - let inner: Option<u32> = 122 - Option::deserialize(&value["d"]).map_err(de::Error::custom)?; 123 - ReceiveDataType::OP1(inner) 124 - } 125 - 9 => { 126 - let inner = bool::deserialize(&value["d"]).map_err(de::Error::custom)?; 127 - ReceiveDataType::OP9(inner) 128 - } 129 - 10 => { 130 - let inner = OP10D::deserialize(&value["d"]).map_err(de::Error::custom)?; 131 - ReceiveDataType::OP10(inner) 132 - } 133 - 11 => ReceiveDataType::OP11, 134 - _ => return Err(de::Error::custom(format!("unknown op: {}", op))), 135 - }; 136 - 137 - Ok(ReceiveData { d, op }) 138 - } 139 - }
-39
src/gateway/serde/serialize.rs
··· 1 - use serde::{Serialize, ser::SerializeMap}; 2 - 3 - pub enum SendDataType { 4 - OP1(Option<u32>), 5 - OP2(OP2D), 6 - } 7 - 8 - pub struct SendData { 9 - pub d: SendDataType, 10 - pub op: u8, 11 - } 12 - // TODO: make impl Serialize implementation consistent across all places 13 - impl Serialize for SendData { 14 - fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { 15 - let mut serializer_map = serializer.serialize_map(None)?; 16 - 17 - serializer_map.serialize_entry("op", &self.op)?; 18 - 19 - match &self.d { 20 - SendDataType::OP1(op1_d) => serializer_map.serialize_entry("d", &op1_d)?, 21 - SendDataType::OP2(op2_d) => serializer_map.serialize_entry("d", &op2_d)?, 22 - } 23 - 24 - serializer_map.end() 25 - } 26 - } 27 - 28 - #[derive(Serialize)] 29 - pub struct OP2D { 30 - pub token: String, 31 - pub properties: OP2DProps, 32 - } 33 - 34 - #[derive(Serialize)] 35 - pub struct OP2DProps { 36 - pub os: String, 37 - pub browser: String, 38 - pub device: String, 39 - }
+1 -1
src/high_level.rs src/high_level/mod.rs
··· 1 - pub mod command_handler; 1 + pub mod command_handler;
+79 -25
src/high_level/command_handler.rs
··· 1 1 use std::{collections::HashMap, pin::Pin}; 2 2 3 - use log::info; 3 + use anyhow::Result; 4 4 5 - use crate::gateway::data_structure::message::MessageEventData; 5 + use crate::{ 6 + api::FluxerApiHandler, 7 + error::{CommandHandlerError, FluxerRsError}, 8 + serde::types::message::MessageData, 9 + }; 6 10 7 11 pub type Command = Box<dyn BoxedCommandTrait + Send + Sync>; 8 12 9 13 pub trait CommandTrait: Send + Sync + 'static { 10 - fn execute(&self) -> impl Future<Output = ()> + Send; 14 + fn execute<'a>( 15 + &'a self, 16 + api: &'a FluxerApiHandler, 17 + feedback: &'a CommandFeedback<'a>, 18 + ) -> impl Future<Output = Result<(), FluxerRsError>> + Send + 'a; 11 19 } 12 20 13 21 pub trait BoxedCommandTrait { 14 - fn execute(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>; 22 + fn execute<'a>( 23 + &'a self, 24 + api: &'a FluxerApiHandler, 25 + feedback: &'a CommandFeedback<'a>, 26 + ) -> Pin<Box<dyn Future<Output = Result<(), FluxerRsError>> + Send + 'a>>; 15 27 } 16 28 17 29 impl<C: CommandTrait> BoxedCommandTrait for C { 18 - fn execute(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { 19 - Box::pin(CommandTrait::execute(self)) 30 + fn execute<'a>( 31 + &'a self, 32 + api: &'a FluxerApiHandler, 33 + feedback: &'a CommandFeedback<'a>, 34 + ) -> Pin<Box<dyn Future<Output = Result<(), FluxerRsError>> + Send + 'a>> { 35 + Box::pin(CommandTrait::execute(self, api, feedback)) 20 36 } 21 37 } 22 38 23 39 pub struct CommandHandler { 24 40 prefix: String, 25 41 map: HashMap<String, Command>, 42 + } 43 + 44 + pub struct CommandFeedback<'a> { 45 + pub data: &'a MessageData, 46 + pub args: Vec<&'a str>, 26 47 } 27 48 28 49 impl CommandHandler { 29 - pub fn init(prefix: String) -> Self { 50 + pub fn init(prefix: impl Into<String>) -> Self { 30 51 CommandHandler { 31 - prefix, 52 + prefix: prefix.into(), 32 53 map: HashMap::new(), 33 54 } 34 55 } 35 56 36 - pub fn register_command(&mut self, command_name: String, handler: impl CommandTrait + 'static) { 37 - self.map.insert(command_name, Box::new(handler)); 57 + pub fn register_command( 58 + &mut self, 59 + command_name: impl Into<String>, 60 + handler: impl CommandTrait + 'static, 61 + ) { 62 + self.map.insert(command_name.into(), Box::new(handler)); 38 63 } 39 64 40 - pub async fn handle(&self, data: &MessageEventData) { 41 - let msg_no_pfx = Self::remove_pfx(&self.prefix, &data.content).await; 42 - if let Some((command, _)) = msg_no_pfx { 43 - match self.map.get(&command) { 44 - Some(handler) => handler.execute().await, 45 - None => info!("Unknown command"), 65 + pub async fn handle( 66 + &self, 67 + data: &MessageData, 68 + api: &FluxerApiHandler, 69 + ) -> Result<(), FluxerRsError> { 70 + // Checking whether the message content really starts with the prefix and if it's not just the prefix itself 71 + if !data.content.starts_with(&self.prefix) || data.content.len() < 2 { 72 + return Ok(()); 73 + } 74 + 75 + let (cmd, args) = Self::command_data(&self.prefix, &data.content)?; 76 + 77 + let split_args = Self::split_args(args); 78 + 79 + match self.map.get(cmd) { 80 + Some(handler) => { 81 + handler 82 + .execute( 83 + api, 84 + &CommandFeedback { 85 + data, 86 + args: split_args, 87 + }, 88 + ) 89 + .await 46 90 } 91 + None => Err(FluxerRsError::CommandHandlerError( 92 + crate::error::CommandHandlerError::UnknownCommand(args.to_string()), 93 + )), 47 94 } 48 95 } 49 96 50 - pub async fn remove_pfx(prefix: &str, content: &str) -> Option<(String, String)> { 51 - #[allow(clippy::manual_map)] 52 - match content.split_once(prefix) { 53 - Some(content) => match content.1.split_once(" ") { 54 - Some((command, body)) => Some((command.to_string(), body.to_string())), 55 - None => Some((content.1.to_string(), "".to_string())), 56 - }, 57 - None => None, 58 - } 97 + pub fn command_data<'a>( 98 + prefix: &str, 99 + content: &'a str, 100 + ) -> Result<(&'a str, &'a str), FluxerRsError> { 101 + content 102 + .strip_prefix(prefix) 103 + .ok_or_else(|| CommandHandlerError::Custom("Failed to remove command prefix".into())) 104 + .map(|stripped_body| match stripped_body.split_once(" ") { 105 + Some((cmd, args)) => (cmd, args), 106 + None => (stripped_body, ""), 107 + }) 108 + .map_err(FluxerRsError::from) 109 + } 110 + 111 + pub fn split_args(body: &str) -> Vec<&str> { 112 + body.split(" ").collect::<Vec<&str>>() 59 113 } 60 114 }
+8 -3
src/lib.rs
··· 1 - pub mod gateway; 2 - pub mod fluxerbot; 3 1 pub mod api; 2 + pub mod error; 3 + pub mod fluxerbot; 4 + pub mod gateway; 4 5 pub mod high_level; 5 - pub use crate::api::data_structure::embed::*; 6 + pub mod serde; 7 + pub mod util; 8 + 9 + pub use macros::command; 10 + pub use macros::register_commands;
+58
src/serde/gateway.rs
··· 1 + #[cfg(not(debug_assertions))] 2 + use log::error; 3 + 4 + use anyhow::Result; 5 + use serde::ser::SerializeMap; 6 + use serde::{Deserialize, Serialize, de}; 7 + use serde_json::Value; 8 + 9 + use crate::gateway::dispatch::dispatch_deserialize; 10 + use crate::serde::types::gateway::{OP10D, ReceiveData, ReceiveDataType, SendData, SendDataType}; 11 + 12 + // TODO: make impl Serialize implementation consistent across all places 13 + impl Serialize for SendData { 14 + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { 15 + let mut serializer_map = serializer.serialize_map(None)?; 16 + 17 + serializer_map.serialize_entry("op", &self.op)?; 18 + 19 + match &self.d { 20 + SendDataType::OP1(op1_d) => serializer_map.serialize_entry("d", &op1_d)?, 21 + SendDataType::OP2(op2_d) => serializer_map.serialize_entry("d", &op2_d)?, 22 + } 23 + 24 + serializer_map.end() 25 + } 26 + } 27 + 28 + impl<'de> Deserialize<'de> for ReceiveData { 29 + fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { 30 + let value = Value::deserialize(deserializer)?; 31 + let op = value["op"] 32 + .as_u64() 33 + .ok_or_else(|| de::Error::missing_field("op"))? as u8; 34 + 35 + let d = match op { 36 + 0 => ReceiveDataType::OP0(Box::new( 37 + dispatch_deserialize(&value).map_err(de::Error::custom)?, 38 + )), 39 + 1 => { 40 + let inner: Option<u32> = 41 + Option::deserialize(&value["d"]).map_err(de::Error::custom)?; 42 + ReceiveDataType::OP1(inner) 43 + } 44 + 9 => { 45 + let inner = bool::deserialize(&value["d"]).map_err(de::Error::custom)?; 46 + ReceiveDataType::OP9(inner) 47 + } 48 + 10 => { 49 + let inner = OP10D::deserialize(&value["d"]).map_err(de::Error::custom)?; 50 + ReceiveDataType::OP10(inner) 51 + } 52 + 11 => ReceiveDataType::OP11, 53 + _ => return Err(de::Error::custom(format!("unknown op: {}", op))), 54 + }; 55 + 56 + Ok(ReceiveData { d, op }) 57 + } 58 + }
+2
src/serde/mod.rs
··· 1 + pub mod gateway; 2 + pub mod types;
+44
src/serde/types/gateway.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + use crate::gateway::dispatch::DispatchEvent; 4 + 5 + pub struct ReceiveData { 6 + pub d: ReceiveDataType, 7 + pub op: u8, 8 + } 9 + 10 + pub struct SendData { 11 + pub d: SendDataType, 12 + pub op: u8, 13 + } 14 + 15 + pub enum ReceiveDataType { 16 + OP0(Box<DispatchEvent>), 17 + OP1(Option<u32>), 18 + OP9(bool), 19 + OP10(OP10D), 20 + OP11, 21 + } 22 + 23 + pub enum SendDataType { 24 + OP1(Option<u32>), 25 + OP2(OP2D), 26 + } 27 + 28 + #[derive(Serialize)] 29 + pub struct OP2D { 30 + pub token: String, 31 + pub properties: OP2DProps, 32 + } 33 + 34 + #[derive(Serialize)] 35 + pub struct OP2DProps { 36 + pub os: String, 37 + pub browser: String, 38 + pub device: String, 39 + } 40 + 41 + #[derive(Serialize, Deserialize)] 42 + pub struct OP10D { 43 + pub heartbeat_interval: u32, 44 + }
+207
src/serde/types/message.rs
··· 1 + use derive_builder::Builder; 2 + use serde::{Deserialize, Serialize}; 3 + use serde_json::Value; 4 + use serde_with::skip_serializing_none; 5 + 6 + use crate::serde::types::common::{AuthorData, EmojiData, MemberData}; 7 + 8 + /// Data returned from dispatch events of types `MESSAGE_CREATE` `MESSAGE_UPDATE` and `MESSAGE_DELETE` 9 + #[derive(Deserialize, Debug)] 10 + pub struct MessageData { 11 + pub attachments: Option<Vec<Value>>, 12 + pub author: Option<AuthorData>, 13 + pub channel_id: String, 14 + pub channel_type: Option<i64>, 15 + pub content: String, 16 + pub edited_timestamp: Option<Value>, 17 + pub embeds: Option<Vec<Embed>>, 18 + pub flags: Option<i64>, 19 + pub guild_id: Option<String>, 20 + pub id: String, 21 + pub member: Option<MemberData>, 22 + pub mention_everyone: Option<bool>, 23 + pub nonce: Option<String>, 24 + pub pinned: Option<bool>, 25 + pub stickers: Option<Vec<Value>>, 26 + pub timestamp: Option<String>, 27 + #[serde(rename = "type")] 28 + pub message_type: Option<i64>, 29 + } 30 + 31 + /// Data for when a user starts typing, returned from dispatch events of type `TYPING_START` 32 + #[derive(Deserialize, Debug)] 33 + pub struct TypingStartData { 34 + pub channel_id: String, 35 + pub guild_id: String, 36 + pub member: MemberData, 37 + pub timestamp: i64, 38 + pub user_id: String, 39 + } 40 + 41 + /// Data for a message reaction, returned from dispatch events of type `MESSAGE_REACTION_ADD`, `MESSAGE_REACTION_REMOVE` and `MESSAGE_REACTION_REMOVE_EMOJI` 42 + #[derive(Deserialize, Debug)] 43 + pub struct MessageReactionData { 44 + pub channel_id: String, 45 + pub emoji: EmojiData, 46 + pub guild_id: String, 47 + pub member: Option<MemberData>, 48 + pub message_id: String, 49 + pub user_id: Option<String>, 50 + } 51 + 52 + /// Data for a message reference used when replying to another message, it's used as a part of [`SendMessage`](crate::api::channels::messages) api call. 53 + #[skip_serializing_none] 54 + #[derive(Clone, Debug, Builder, Serialize)] 55 + #[builder(try_setter, setter(into))] 56 + pub struct MessageReference { 57 + pub message_id: String, 58 + #[builder(default)] 59 + pub channel_id: Option<String>, 60 + #[builder(default)] 61 + pub guild_id: Option<String>, 62 + } 63 + 64 + /// Data for an embed received or sent as part of a message. <br> 65 + #[skip_serializing_none] 66 + #[derive(Clone, Debug, Default, Builder, Serialize, Deserialize)] 67 + #[builder(try_setter, setter(into), default)] 68 + pub struct Embed { 69 + #[serde(rename = "type")] 70 + pub embed_type: Option<EmbedType>, 71 + pub url: Option<String>, 72 + pub title: Option<String>, 73 + pub color: Option<u32>, 74 + pub description: Option<String>, 75 + pub author: Option<EmbedAuthor>, 76 + pub image: Option<EmbedMedia>, 77 + pub thumbnail: Option<EmbedMedia>, 78 + pub footer: Option<EmbedFooter>, 79 + pub fields: Option<Vec<EmbedField>>, 80 + pub provider: Option<EmbedAuthor>, 81 + pub video: Option<EmbedMedia>, 82 + pub audio: Option<EmbedMedia>, 83 + pub nsfw: Option<bool>, 84 + pub children: Option<Vec<EmbedChild>>, 85 + } 86 + 87 + /// The same type as [`Embed`] with the only difference that it cannot have any embed children and it's used only in [`Embed`] 88 + #[skip_serializing_none] 89 + #[derive(Clone, Debug, Default, Builder, Serialize, Deserialize)] 90 + #[builder(try_setter, setter(into), default)] 91 + pub struct EmbedChild { 92 + #[serde(rename = "type")] 93 + pub embed_type: Option<EmbedType>, 94 + pub url: Option<String>, 95 + pub title: Option<String>, 96 + pub color: Option<u32>, 97 + pub description: Option<String>, 98 + pub author: Option<EmbedAuthor>, 99 + pub image: Option<EmbedMedia>, 100 + pub thumbnail: Option<EmbedMedia>, 101 + pub footer: Option<EmbedFooter>, 102 + pub fields: Option<Vec<EmbedField>>, 103 + pub provider: Option<EmbedAuthor>, 104 + pub video: Option<EmbedMedia>, 105 + pub audio: Option<EmbedMedia>, 106 + pub nsfw: Option<bool>, 107 + } 108 + 109 + /// Data for an author of an embed used in [`Embed`] <br> 110 + /// The `name` field is required 111 + #[skip_serializing_none] 112 + #[derive(Clone, Debug, Builder, Serialize, Deserialize)] 113 + #[builder(try_setter, setter(into))] 114 + pub struct EmbedAuthor { 115 + pub name: String, 116 + #[builder(default)] 117 + pub url: Option<String>, 118 + #[builder(default)] 119 + pub icon_url: Option<String>, 120 + #[builder(default)] 121 + pub proxy_icon_url: Option<String>, 122 + } 123 + 124 + /// Data for a footer of an embed used in [`Embed`] <br> 125 + /// The `text` field is required 126 + #[skip_serializing_none] 127 + #[derive(Clone, Debug, Builder, Serialize, Deserialize)] 128 + #[builder(try_setter, setter(into))] 129 + pub struct EmbedFooter { 130 + pub text: Option<String>, 131 + #[builder(default)] 132 + pub icon_url: Option<String>, 133 + #[builder(default)] 134 + pub proxy_icon_url: Option<String>, 135 + } 136 + 137 + /// Data for a footer of an embed used in [`Embed`] <br> 138 + /// The `name` and `value` fields are required 139 + #[skip_serializing_none] 140 + #[derive(Clone, Debug, Builder, Serialize, Deserialize)] 141 + #[builder(try_setter, setter(into))] 142 + pub struct EmbedField { 143 + pub name: String, 144 + pub value: String, 145 + #[builder(default)] 146 + pub inline: Option<bool>, 147 + } 148 + 149 + /// The type of an embed used in [`Embed`] <br> 150 + #[derive(Clone, Debug, Serialize, Deserialize)] 151 + #[serde(rename_all = "lowercase")] 152 + pub enum EmbedType { 153 + Rich, 154 + Image, 155 + Video, 156 + GifV, 157 + Article, 158 + Link, 159 + } 160 + 161 + /// A media flag to mark an embed media as explicit/animated used in [`Embed`] <br> 162 + #[derive(Clone, Debug, Serialize)] 163 + pub enum EmbedMediaFlags { 164 + None, 165 + IsExplicit, 166 + IsAnimated, 167 + IsAnimatedAndExplicit, 168 + } 169 + 170 + /// Data for an emded media used in [`Embed`] <br> 171 + /// The `url` field is required 172 + #[skip_serializing_none] 173 + #[derive(Clone, Debug, Builder, Serialize, Deserialize)] 174 + #[builder(try_setter, setter(into))] 175 + pub struct EmbedMedia { 176 + pub url: String, 177 + #[builder(default)] 178 + pub proxy_url: Option<String>, 179 + #[builder(default)] 180 + pub content_type: Option<String>, 181 + #[builder(default)] 182 + pub content_hash: Option<String>, 183 + #[builder(default)] 184 + pub width: Option<u32>, 185 + #[builder(default)] 186 + pub height: Option<u32>, 187 + #[builder(default)] 188 + pub description: Option<String>, 189 + #[builder(default)] 190 + pub placeholder: Option<String>, 191 + #[builder(default)] 192 + pub duration: Option<u64>, 193 + #[builder(default)] 194 + pub flags: Option<u8>, 195 + } 196 + 197 + impl From<EmbedMediaFlags> for Option<u8> { 198 + fn from(value: EmbedMediaFlags) -> Self { 199 + let result = match value { 200 + EmbedMediaFlags::None => 0, 201 + EmbedMediaFlags::IsExplicit => 16, 202 + EmbedMediaFlags::IsAnimated => 32, 203 + EmbedMediaFlags::IsAnimatedAndExplicit => 48, 204 + }; 205 + Some(result) 206 + } 207 + }
+8
src/util.rs
··· 1 + pub fn get_emoji(emoji: &str) -> String { 2 + if emoji.starts_with(":") && emoji.ends_with(":") { 3 + let unicode = emojis::get_by_shortcode(emoji.trim_matches(':')).unwrap(); 4 + return unicode.as_str().into(); 5 + } 6 + 7 + emoji.into() 8 + }