diff --git a/.forgejo/workflows/check_lfs.yaml b/.forgejo/workflows/check_lfs.yaml deleted file mode 100644 index aaa2117..0000000 --- a/.forgejo/workflows/check_lfs.yaml +++ /dev/null @@ -1,10 +0,0 @@ -on: - - push - - workflow_dispatch - -jobs: - check_lfs: - # nix/docker - runs-on: nix - steps: - - uses: https://github.com/MPLew-is/lfs-check-action@1 \ No newline at end of file diff --git a/.forgejo/workflows/on_pr.yaml b/.forgejo/workflows/on_pr.yaml deleted file mode 100644 index d17dc47..0000000 --- a/.forgejo/workflows/on_pr.yaml +++ /dev/null @@ -1,51 +0,0 @@ -on: - - pull_request - -jobs: - check_lfs: - # nix/docker - runs-on: nix - steps: - - uses: https://github.com/MPLew-is/lfs-check-action@1 - - # rust code must be formatted for standardisation - lint_fmt: - # build it using teh base nixos system, helps with caching - runs-on: nix - steps: - # get the repo first - - uses: https://code.forgejo.org/actions/checkout@v4 - - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8 - with: - server_url: ${{ gitea.server_url }} - repository: ${{ gitea.repository }} - - run: nix build .#fmt --verbose - - # clippy is incredibly useful for making yer code better - lint_clippy: - # build it using teh base nixos system, helps with caching - runs-on: nix - permissions: - checks: write - steps: - # get the repo first - - uses: https://code.forgejo.org/actions/checkout@v4 - - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8 - with: - server_url: ${{ gitea.server_url }} - repository: ${{ gitea.repository }} - - run: nix build .#clippy --verbose - - build: - # build it using teh base nixos system, helps with caching - runs-on: nix - needs: [ lint_fmt, lint_clippy ] - steps: - # get the repo first - - uses: https://code.forgejo.org/actions/checkout@v4 - - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8 - with: - server_url: ${{ gitea.server_url }} - repository: ${{ gitea.repository }} - - name: "Build it locally" - run: nix build --verbose diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index a338a00..0000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1,2 +0,0 @@ -# Fix typos -7e90f451965b0edbd331765ad295a02f31d2bf24 diff --git a/.rustfmt.toml b/.rustfmt.toml index 2b0831a..b8ae8dd 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -6,5 +6,4 @@ fn_params_layout = "Compressed" #brace_style = "PreferSameLine" struct_lit_width = 0 tab_spaces = 2 -use_small_heuristics = "Max" -imports_granularity = "Crate" \ No newline at end of file +use_small_heuristics = "Max" \ No newline at end of file diff --git a/.server-icons.toml b/.server-icons.toml deleted file mode 100644 index eeb302c..0000000 --- a/.server-icons.toml +++ /dev/null @@ -1,6 +0,0 @@ -# this file controls the - -[source] -repo = "https://forgejo.skynet.ie/Computer_Society/open-goverance" -directory = "Resources/Logo_Variants" -file = "_festivals.toml" \ No newline at end of file diff --git a/.taplo.toml b/.taplo.toml deleted file mode 100644 index 3b409e9..0000000 --- a/.taplo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[formatting] -column_width = 120 diff --git a/Cargo.lock b/Cargo.lock index 66e67f1..416c3c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -110,12 +110,6 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.6" @@ -389,12 +383,6 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" -[[package]] -name = "bytemuck" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" - [[package]] name = "byteorder" version = "1.5.0" @@ -461,39 +449,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -682,12 +637,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" -[[package]] -name = "data-url" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" - [[package]] name = "der" version = "0.7.9" @@ -841,16 +790,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -866,15 +805,6 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - [[package]] name = "flate2" version = "1.0.33" @@ -885,12 +815,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - [[package]] name = "flume" version = "0.9.2" @@ -925,27 +849,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" -[[package]] -name = "fontconfig-parser" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" -dependencies = [ - "roxmltree 0.20.0", -] - -[[package]] -name = "fontdb" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff20bef7942a72af07104346154a70a70b089c572e454b41bef6eb6cb10e9c06" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2", - "ttf-parser", -] - [[package]] name = "foreign-types" version = "0.3.2" @@ -1160,16 +1063,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gif" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gimli" version = "0.31.0" @@ -1693,6 +1586,16 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "1.0.3" @@ -1714,18 +1617,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "imagesize" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" version = "2.5.0" @@ -1786,12 +1677,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - [[package]] name = "js-sys" version = "0.3.70" @@ -1801,24 +1686,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kurbo" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "kurbo" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" -dependencies = [ - "arrayvec", -] - [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1851,7 +1718,7 @@ dependencies = [ "futures-util", "hostname", "httpdate", - "idna", + "idna 1.0.3", "mime", "native-tls", "nom", @@ -1976,15 +1843,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - [[package]] name = "mime" version = "0.3.17" @@ -2014,7 +1872,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", - "simd-adler32", ] [[package]] @@ -2174,12 +2031,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "owo-colors" -version = "4.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" - [[package]] name = "parking" version = "2.2.1" @@ -2224,12 +2075,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - [[package]] name = "pin-project" version = "1.1.7" @@ -2300,19 +2145,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - [[package]] name = "polling" version = "3.7.4" @@ -2507,12 +2339,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rctree" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" - [[package]] name = "redox_syscall" version = "0.5.4" @@ -2609,34 +2435,6 @@ dependencies = [ "windows-registry", ] -[[package]] -name = "resvg" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76888219c0881e22b0ceab06fddcfe83163cd81642bd60c7842387f9c968a72e" -dependencies = [ - "gif", - "jpeg-decoder", - "log", - "pico-args", - "png", - "rgb", - "svgfilters", - "svgtypes 0.10.0", - "tiny-skia", - "usvg", - "usvg-text-layout", -] - -[[package]] -name = "rgb" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] - [[package]] name = "ring" version = "0.17.8" @@ -2652,34 +2450,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rosvgtree" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdc23d1ace03d6b8153c7d16f0708cd80b61ee8e80304954803354e67e40d150" -dependencies = [ - "log", - "roxmltree 0.18.1", - "simplecss", - "siphasher", - "svgtypes 0.9.0", -] - -[[package]] -name = "roxmltree" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302" -dependencies = [ - "xmlparser", -] - -[[package]] -name = "roxmltree" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" - [[package]] name = "rsa" version = "0.9.6" @@ -2812,22 +2582,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustybuzz" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162bdf42e261bee271b3957691018634488084ef577dddeb6420a9684cab2a6a" -dependencies = [ - "bitflags 1.3.2", - "bytemuck", - "smallvec", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-general-category", - "unicode-script", -] - [[package]] name = "ryu" version = "1.0.18" @@ -2959,15 +2713,6 @@ dependencies = [ "thiserror 1.0.63", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3061,15 +2806,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shlex" version = "1.3.0" @@ -3095,49 +2831,21 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simplecss" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "skynet_discord_bot" version = "0.1.0" dependencies = [ "chrono", - "color-eyre", "dotenvy", - "eyre", "lettre", "maud", "rand 0.9.0", - "resvg", "serde", "serde_json", "serenity", "sqlx", "surf", - "tiny-skia", "tokio", - "toml", - "usvg", - "usvg-text-layout", "wolves_oxidised", ] @@ -3472,15 +3180,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] - [[package]] name = "stringprep" version = "0.1.5" @@ -3521,36 +3220,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "svgfilters" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "639abcebc15fdc2df179f37d6f5463d660c1c79cd552c12343a4600827a04bce" -dependencies = [ - "float-cmp", - "rgb", -] - -[[package]] -name = "svgtypes" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ee29c1407a5b18ccfe5f6ac82ac11bab3b14407e09c209a6c1a32098b19734" -dependencies = [ - "kurbo 0.8.3", - "siphasher", -] - -[[package]] -name = "svgtypes" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ffacedcdcf1da6579c907279b4f3c5492fbce99fbbf227f5ed270a589c2765" -dependencies = [ - "kurbo 0.9.5", - "siphasher", -] - [[package]] name = "syn" version = "1.0.109" @@ -3694,16 +3363,6 @@ dependencies = [ "syn 2.0.89", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "time" version = "0.2.27" @@ -3773,31 +3432,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "tiny-skia" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "png", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - [[package]] name = "tinystr" version = "0.7.6" @@ -3947,47 +3581,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tower-service" version = "0.3.3" @@ -4024,17 +3617,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", ] [[package]] @@ -4047,29 +3629,12 @@ dependencies = [ "tracing", ] -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttf-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" - [[package]] name = "tungstenite" version = "0.21.0" @@ -4118,24 +3683,6 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" -[[package]] -name = "unicode-bidi-mirroring" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" - -[[package]] -name = "unicode-ccc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" - -[[package]] -name = "unicode-general-category" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" - [[package]] name = "unicode-ident" version = "1.0.13" @@ -4157,18 +3704,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" -[[package]] -name = "unicode-script" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - [[package]] name = "universal-hash" version = "0.4.0" @@ -4187,49 +3722,16 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", "serde", ] -[[package]] -name = "usvg" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b6bb4e62619d9f68aa2d8a823fea2bff302340a1f2d45c264d5b0be170832e" -dependencies = [ - "base64 0.21.7", - "data-url", - "flate2", - "imagesize", - "kurbo 0.9.5", - "log", - "rctree", - "rosvgtree", - "strict-num", -] - -[[package]] -name = "usvg-text-layout" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195386e01bc35f860db024de275a76e7a31afdf975d18beb6d0e44764118b4db" -dependencies = [ - "fontdb", - "kurbo 0.9.5", - "log", - "rustybuzz", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "usvg", -] - [[package]] name = "utf-8" version = "0.7.6" @@ -4248,12 +3750,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "value-bag" version = "1.10.0" @@ -4419,12 +3915,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "weezl" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" - [[package]] name = "whoami" version = "1.5.2" @@ -4654,15 +4144,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" -dependencies = [ - "memchr", -] - [[package]] name = "winreg" version = "0.50.0" @@ -4685,7 +4166,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#265c8c81d1eb870a6149da5ce72556d44f57937f" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#74f00e3dcfd52744583b0ded08efb8bb27fdcec8" dependencies = [ "reqwest 0.12.9", "serde", @@ -4705,12 +4186,6 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" -[[package]] -name = "xmlparser" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" - [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index 832eb09..aa0cd6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,21 +4,16 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [[bin]] name = "update_data" [[bin]] -name = "update_committee" +name = "update_users" [[bin]] name = "update_minecraft" -[[bin]] -name = "update_server-icon" - -[[bin]] -name = "cleanup_committee" - [dependencies] # discord library serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } @@ -34,7 +29,7 @@ surf = "2.3" dotenvy = "0.15" # For sqlite -sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "migrate"] } +sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite", "migrate" ] } serde_json = { version = "1.0", features = ["raw_value"] } # create random strings @@ -47,13 +42,4 @@ chrono = "0.4" lettre = "0.11" maud = "0.27" -toml = "0.8.23" -serde = "1.0" - -# for image conversion -eyre = "0.6.8" -color-eyre = "0.6.2" -usvg-text-layout = "0.29.0" -usvg = "0.29.0" -resvg = "0.29.0" -tiny-skia = "0.8.3" \ No newline at end of file +serde = "1.0" \ No newline at end of file diff --git a/README.md b/README.md index 1f49dc1..01543e6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Skynet Discord Bot The Skynet bot is designed to manage users on Discord. It allows users to link their UL Wolves account with Wolves in a GDPR compliant manner. -Skynet (bot) is hosted by the Computer Society on Skynet (computer cluster). +Skynet (bot) is hosted is hosted by the Computer Society on Skynet (computer cluster). ## Documentation We have split up the documentation into different segments depending on who the user is. diff --git a/db/migrations/10_member_committee-roles.sql b/db/migrations/10_member_committee-roles.sql deleted file mode 100644 index 03264cd..0000000 --- a/db/migrations/10_member_committee-roles.sql +++ /dev/null @@ -1,10 +0,0 @@ - -CREATE TABLE IF NOT EXISTS committee_roles ( - id_wolves integer PRIMARY KEY, - id_role integer DEFAULT 1, - id_channel integer DEFAULT 1, - -- not strictly required but for readability and debugging - name_role text NOT NULL DEFAULT '', - name_channel text NOT NULL DEFAULT '', - count integer DEFAULT 0 -); diff --git a/db/migrations/11_server-icons.sql b/db/migrations/11_server-icons.sql deleted file mode 100644 index 20fb472..0000000 --- a/db/migrations/11_server-icons.sql +++ /dev/null @@ -1,9 +0,0 @@ - -CREATE TABLE IF NOT EXISTS server_icons ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL, - path TEXT NOT NULL, - date TEXT NOT NULL -); - -CREATE INDEX IF NOT EXISTS index_name ON server_icons (name); \ No newline at end of file diff --git a/db/migrations/9_member_committee-id.sql b/db/migrations/9_member_committee-id.sql deleted file mode 100644 index 77552ce..0000000 --- a/db/migrations/9_member_committee-id.sql +++ /dev/null @@ -1,6 +0,0 @@ --- No need for this col since it is goign to be in "committees" anyways -ALTER TABLE servers DROP COLUMN server_name; - --- we do care about teh ID of the club/soc though -ALTER TABLE servers ADD COLUMN wolves_id integer DEFAULT 0; - diff --git a/doc/Committee.md b/doc/Committee.md index a400536..cfe718e 100644 --- a/doc/Committee.md +++ b/doc/Committee.md @@ -7,7 +7,7 @@ For example is a user links on the CompSoc Discord then they will also get their ## Setup - Committee You need admin access to run any of the commands in this section. -Either the server owner or a user with the ``Manage Server`` permission. +Either the server owner or a user with the ``Administrator`` permission. ### Get the API Key The ``api_key`` is used by the Bot in order to request information, it will be used later in the process. @@ -38,9 +38,9 @@ The reason for both roles is ye have one for active members while the second is ### Setup Bot This is where the bot is configured. You will need the ``api_key`` from the start of the process. -You (personally) will need a role with ``Manage Server`` permission to be able to do this. +You (personally) will need a role with ``Administrator`` permission to be able to do this. -1. Use the command ``/committee add`` and a list of options will pop up. +1. Use the command ``/add`` and a list of options will pop up. 2. ``api_key`` is the key you got from Keith earlier. 3. ``role_current`` is the ``member-current`` that you created earlier. 4. ``role_past`` (optional) is the role for all current and past members. diff --git a/doc/User.md b/doc/User.md index 570877c..fec6abe 100644 --- a/doc/User.md +++ b/doc/User.md @@ -8,13 +8,13 @@ This is to link your Discord account with your UL Wolves account. **You will only need to do this once**. ### Setup -1. In a Discord server with the Skynet Bot enter ``/wolves link YOUR_WOLVES_CONTACT_EMAIL`` +1. In a Discord server with the Skynet Bot enter ``/link_wolves YOUR_WOLVES_CONTACT_EMAIL`` link process start * Your ``YOUR_WOLVES_CONTACT_EMAIL`` is the email in the Contact Email here: * This is most likely your student mail 2. An email will be sent to you with a verification code. signup email -3. Verify the code using ``/wolves verify CODE_FROM_EMAIL`` in Discord. +3. Verify the code using ``/verify CODE_FROM_EMAIL`` in Discord. verify in discord 4. Once complete your Wolves and Discord accounts will be linked. @@ -23,4 +23,4 @@ You will get member roles on any Discord that is using the bot that you are a me ### Minecraft You can link your Minecraft username to grant you access to any Minecraft server run by UL Computer Society. -``/wolves link_minecraft MINECRAFT_USERNAME`` +``/link_minecraft MINECRAFT_USERNAME`` diff --git a/flake.lock b/flake.lock index 2c4a9bc..9f7289a 100644 --- a/flake.lock +++ b/flake.lock @@ -32,22 +32,6 @@ "type": "indirect" } }, - "nixpkgs-mozilla": { - "flake": false, - "locked": { - "lastModified": 1744624473, - "narHash": "sha256-S6zT/w5SyAkJ//dYdjbrXgm+6Vkd/k7qqUl4WgZ6jjk=", - "owner": "mozilla", - "repo": "nixpkgs-mozilla", - "rev": "2292d4b35aa854e312ad2e95c4bb5c293656f21a", - "type": "github" - }, - "original": { - "owner": "mozilla", - "repo": "nixpkgs-mozilla", - "type": "github" - } - }, "nixpkgs_2": { "locked": { "lastModified": 1722995383, @@ -67,7 +51,6 @@ "inputs": { "naersk": "naersk", "nixpkgs": "nixpkgs_2", - "nixpkgs-mozilla": "nixpkgs-mozilla", "utils": "utils" } }, diff --git a/flake.nix b/flake.nix index 3a6032f..12ae36e 100644 --- a/flake.nix +++ b/flake.nix @@ -4,10 +4,6 @@ inputs = { nixpkgs.url = "nixpkgs/nixos-unstable"; naersk.url = "github:nix-community/naersk"; - nixpkgs-mozilla = { - url = "github:mozilla/nixpkgs-mozilla"; - flake = false; - }; utils.url = "github:numtide/flake-utils"; }; @@ -21,33 +17,15 @@ nixpkgs, utils, naersk, - nixpkgs-mozilla, }: utils.lib.eachDefaultSystem ( system: let - overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml)); - pkgs = (import nixpkgs) { - inherit system; - - overlays = [ - (import nixpkgs-mozilla) - ]; - }; - toolchain = (pkgs.rustChannelOf { - rustToolchain = ./rust-toolchain.toml; - sha256 = "sha256-KUm16pHj+cRedf8vxs/Hd2YWxpOrWZ7UOrwhILdSJBU="; - }).rust; - - naersk' = pkgs.callPackage naersk { - cargo = toolchain; - rustc = toolchain; - }; + pkgs = (import nixpkgs) {inherit system;}; + naersk' = pkgs.callPackage naersk {}; package_name = "skynet_discord_bot"; desc = "Skynet Discord Bot"; buildInputs = with pkgs; [ openssl - glib - gdk-pixbuf pkg-config rustfmt ]; @@ -58,10 +36,6 @@ pname = "${package_name}"; src = ./.; buildInputs = buildInputs; - postInstall = '' - mkdir $out/config - cp .server-icons.toml $out/config - ''; }; # Run `nix build .#fmt` to run tests fmt = naersk'.buildPackage { @@ -88,15 +62,7 @@ # `nix develop` devShell = pkgs.mkShell { - nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook]; - # libraries here - buildInputs = buildInputs; - RUSTC_VERSION = overrides.toolchain.channel; - # https://github.com/rust-lang/rust-bindgen#environment-variables - shellHook = '' - export PATH="''${CARGO_HOME:-~/.cargo}/bin":"$PATH" - export PATH="''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-${pkgs.stdenv.hostPlatform.rust.rustcTarget}/bin":"$PATH" - ''; + nativeBuildInputs = with pkgs; [rustc cargo pkg-config openssl rustfmt]; }; nixosModule = { @@ -122,14 +88,12 @@ wantedBy = []; after = ["network-online.target"]; environment = environment_config; - path = with pkgs; [ git git-lfs ]; + serviceConfig = { Type = "oneshot"; User = "${cfg.user}"; Group = "${cfg.user}"; ExecStart = "${self.defaultPackage."${system}"}/bin/${script}"; - # kill each service if its ran for 9 min - TimeoutStartSec=540; EnvironmentFile = [ "${cfg.env.discord}" "${cfg.env.mail}" @@ -154,17 +118,12 @@ # modify these scripts = { - # every 10 min + # every 20 min "update_data" = "*:0,10,20,30,40,50"; # groups are updated every hour, offset from teh ldap - "update_users" = "*:5,15,25,35,45,55"; - # Committee server has its own timer - "update_committee" = "*:5,15,25,35,45,55"; + "update_users" = "*:05:00"; # minecraft stuff is updated at 5am - # this service does not depend on teh discord cache "update_minecraft" = "5:10:00"; - # server icon gets updated daily at midnight - "update_server-icon" = "0:01:00"; }; in { options.services."${package_name}" = { @@ -224,7 +183,6 @@ after = ["network-online.target"]; wants = []; environment = environment_config; - path = with pkgs; [ git git-lfs ]; serviceConfig = { User = "${cfg.user}"; diff --git a/media/setup_user_01.png b/media/setup_user_01.png index c8ac1a8..b5e207b 100644 --- a/media/setup_user_01.png +++ b/media/setup_user_01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:684bcaa532d75d90a63512c648c44d9cd12a6e34fce6c2a55bf1d5d4f7446371 -size 42969 +oid sha256:f6440b2d302c5a7e46493687bb0bbf941c29c5552c71869303397da3c4f0a52b +size 72163 diff --git a/media/setup_user_03.png b/media/setup_user_03.png index f9f4e3a..fda3bda 100644 --- a/media/setup_user_03.png +++ b/media/setup_user_03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3952b8c2a55604b88a0034f68dd29abd3a72b3e1ced8074bacb358b96f14c7b1 -size 48708 +oid sha256:f19d24686bb9da8857263d1f146e29413fd6bd316981281c18ef2b4ec3621596 +size 51740 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0837c1f..8cca5be 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.87" +channel = "1.80" diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs deleted file mode 100644 index 141af89..0000000 --- a/src/bin/cleanup_committee.rs +++ /dev/null @@ -1,137 +0,0 @@ -use serenity::{ - all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}, - async_trait, - client::{Context, EventHandler}, - model::gateway::GatewayIntents, - Client, -}; -use skynet_discord_bot::{ - common::{ - database::{db_init, DataBase}, - set_roles::committee::db_roles_get, - }, - get_config, Config, -}; -use sqlx::{Pool, Sqlite}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; - -/// Cleanup teh Committee server -/// -/// This removes any invalid roles/channels which have been set up accidentally -/// DO NOT run this locally unless you have a fresh copy of the live database handy. -#[tokio::main] -async fn main() { - let config = get_config(); - let db = match db_init(&config).await { - Ok(x) => x, - Err(_) => return, - }; - - // Intents are a bitflag, bitwise operations can be used to dictate which intents to use - let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; - // Build our client. - let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) - .cache_settings(serenity::cache::Settings::default()) - .await - .expect("Error creating client"); - - { - let mut data = client.data.write().await; - - data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(db)); - } - - if let Err(why) = client.start().await { - println!("Client error: {why:?}"); - } -} - -struct Handler; -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, _guilds: Vec) { - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config_global = config_lock.read().await; - - let server = config_global.committee_server; - - ctx.shard.chunk_guild(server, Some(2000), false, ChunkGuildFilter::None, None); - - println!("Cache loaded"); - } - - async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { - if (chunk.chunk_index + 1) == chunk.chunk_count { - println!("Cache built successfully!"); - let db = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - cleanup(&db, &ctx, &config).await; - // finish up - process::exit(0); - } - } -} - -async fn cleanup(db: &Pool, ctx: &Context, config: &Config) { - let server = config.committee_server; - let committees = db_roles_get(db).await; - - if let Ok(channels) = server.channels(ctx).await { - for (id, channel) in &channels { - let name = &channel.name; - let committee_tmp = committees.iter().filter(|x| &x.name_channel == name).collect::>(); - let committee = match committee_tmp.first() { - // if there are no committees which match then this is not a channel we care about - None => { - continue; - } - Some(x) => x, - }; - - // if the id of the channel does not match then remove it - if id != &committee.id_channel { - println!("Deleting Channel - ID: {} Name: {}", id, &channel.name); - if let Err(e) = channel.delete(ctx).await { - dbg!(e); - } - } - } - } - - if let Ok(mut roles) = server.roles(ctx).await { - for (id, role) in &mut roles { - let name = &role.name; - let committee_tmp = committees.iter().filter(|x| &x.name_role == name).collect::>(); - let committee = match committee_tmp.first() { - // if there are no committees which match then this is not a channel we care about - None => { - continue; - } - Some(x) => x, - }; - - // if the id of the role does not match then remove it - if id != &committee.id_role { - println!("Deleting Role - ID: {} Name: {}", id, &role.name); - if let Err(e) = role.delete(ctx).await { - dbg!(e); - } - } - } - } -} diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs deleted file mode 100644 index d90cbac..0000000 --- a/src/bin/update_committee.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serenity::{ - all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}, - async_trait, - client::{Context, EventHandler}, - model::gateway::GatewayIntents, - Client, -}; -use skynet_discord_bot::{ - common::{ - database::{db_init, DataBase}, - set_roles::committee, - }, - get_config, Config, -}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; - -#[tokio::main] -async fn main() { - let config = get_config(); - let db = match db_init(&config).await { - Ok(x) => x, - Err(_) => return, - }; - - // Intents are a bitflag, bitwise operations can be used to dictate which intents to use - let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; - // Build our client. - let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) - .cache_settings(serenity::cache::Settings::default()) - .await - .expect("Error creating client"); - - { - let mut data = client.data.write().await; - - data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(db)); - } - - if let Err(why) = client.start().await { - println!("Client error: {why:?}"); - } -} - -struct Handler; -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, _guilds: Vec) { - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config_global = config_lock.read().await; - - let server = config_global.committee_server; - - ctx.shard.chunk_guild(server, Some(2000), false, ChunkGuildFilter::None, None); - - println!("Cache loaded"); - } - - async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { - if (chunk.chunk_index + 1) == chunk.chunk_count { - println!("Cache built successfully!"); - // u[date committee server - committee::check_committee(&ctx).await; - - // finish up - process::exit(0); - } - } -} diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index fe4138f..2d36892 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -4,13 +4,10 @@ use serenity::{ model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::{ - common::{ - database::{db_init, DataBase}, - wolves::{cns::get_wolves, committees::get_cns}, - }, - get_config, Config, -}; +use skynet_discord_bot::common::database::{db_init, DataBase}; +use skynet_discord_bot::common::wolves::cns::get_wolves; +use skynet_discord_bot::common::wolves::committees::get_cns; +use skynet_discord_bot::{get_config, Config}; use std::{process, sync::Arc}; use tokio::sync::RwLock; @@ -30,7 +27,6 @@ async fn main() { // Build our client. let mut client = Client::builder(&config.discord_token, intents) .event_handler(Handler {}) - .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); @@ -38,11 +34,11 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(db)); + data.insert::(Arc::new(RwLock::new(db))); } if let Err(why) = client.start().await { - println!("Client error: {why:?}"); + println!("Client error: {:?}", why); } } diff --git a/src/bin/update_minecraft.rs b/src/bin/update_minecraft.rs index f7c24e0..f5d3634 100644 --- a/src/bin/update_minecraft.rs +++ b/src/bin/update_minecraft.rs @@ -1,10 +1,6 @@ -use skynet_discord_bot::{ - common::{ - database::db_init, - minecraft::{get_minecraft_config, update_server, whitelist_wipe}, - }, - get_config, -}; +use skynet_discord_bot::common::database::db_init; +use skynet_discord_bot::common::minecraft::{get_minecraft_config, update_server, whitelist_wipe}; +use skynet_discord_bot::get_config; use std::collections::HashSet; #[tokio::main] diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs deleted file mode 100644 index c4f9eca..0000000 --- a/src/bin/update_server-icon.rs +++ /dev/null @@ -1,71 +0,0 @@ -use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, -}; -use skynet_discord_bot::{ - common::{ - database::{db_init, DataBase}, - server_icon::{get_config_icons, update_icon}, - }, - get_config, Config, -}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; - -#[tokio::main] -async fn main() { - let config = get_config(); - let db = match db_init(&config).await { - Ok(x) => x, - Err(_) => return, - }; - - // Intents are a bitflag, bitwise operations can be used to dictate which intents to use - let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; - // Build our client. - let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) - .cache_settings(serenity::cache::Settings::default()) - .await - .expect("Error creating client"); - - { - let mut data = client.data.write().await; - - data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(db)); - } - - if let Err(why) = client.start().await { - println!("Client error: {why:?}"); - } -} - -struct Handler; -#[async_trait] -impl EventHandler for Handler { - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); - - let db = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let config_global = config_lock.read().await; - let config_toml = get_config_icons::minimal(); - - update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await; - - // finish up - process::exit(0); - } -} diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 3150bcf..e3170eb 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -1,24 +1,13 @@ use serenity::{ - all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}, async_trait, client::{Context, EventHandler}, - model::gateway::GatewayIntents, + model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::{ - common::{ - database::{db_init, get_server_config_bulk, DataBase}, - set_roles::normal, - }, - get_config, Config, -}; -use std::{ - process, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, -}; +use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; +use skynet_discord_bot::common::set_roles::{committee, normal}; +use skynet_discord_bot::{get_config, Config}; +use std::{process, sync::Arc}; use tokio::sync::RwLock; #[tokio::main] @@ -33,11 +22,7 @@ async fn main() { let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; // Build our client. let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler { - server_count: Default::default(), - server_cached: Default::default(), - }) - .cache_settings(serenity::cache::Settings::default()) + .event_handler(Handler {}) .await .expect("Error creating client"); @@ -45,51 +30,41 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(db)); + data.insert::(Arc::new(RwLock::new(db))); } if let Err(why) = client.start().await { - println!("Client error: {why:?}"); + println!("Client error: {:?}", why); } } -struct Handler { - server_count: AtomicUsize, - server_cached: AtomicUsize, -} +struct Handler; #[async_trait] impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - self.server_count.swap(guilds.len(), Ordering::SeqCst); - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache loaded {}", &self.server_count.load(Ordering::SeqCst)); - } + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); - async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { - if (chunk.chunk_index + 1) == chunk.chunk_count { - self.server_cached.fetch_add(1, Ordering::SeqCst); - if (self.server_cached.load(Ordering::SeqCst) + 1) == self.server_count.load(Ordering::SeqCst) { - println!("Cache built successfully!"); + // this goes into each server and sets roles for each wolves member + check_bulk(Arc::clone(&ctx)).await; - // this goes into each server and sets roles for each wolves member - check_bulk(&ctx).await; + // u[date committee server + committee::check_committee(Arc::clone(&ctx)).await; - // finish up - process::exit(0); - } - } + // finish up + process::exit(0); } } -async fn check_bulk(ctx: &Context) { - let db = { +async fn check_bulk(ctx: Arc) { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; + let db = db_lock.read().await; + for server_config in get_server_config_bulk(&db).await { - normal::update_server(ctx, &server_config, &[], &[]).await; + normal::update_server(&ctx, &server_config, &[], &[]).await; } } diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index eaf0971..fdc0f23 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -1,88 +1,94 @@ -use serenity::{ - all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}, - client::Context, -}; -use skynet_discord_bot::common::{ - database::{get_server_config, DataBase, Servers}, - set_roles::normal::update_server, - wolves::cns::get_wolves, -}; +use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; +use serenity::{builder::CreateCommand, client::Context}; +use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; +use skynet_discord_bot::common::set_roles::normal::update_server; +use skynet_discord_bot::common::wolves::cns::get_wolves; +use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), + // check if user has high enough permisssions + if let Some(msg) = is_admin(command, ctx).await { + return msg; + } + + let wolves_api = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(key), .. }) = command.data.options.first() { - options - } else { - return "Please provide sub options".to_string(); - }; - - let wolves_api = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::String(key) => key.to_string(), - _ => return "Please provide a wolves API key".to_string(), - } + key.to_string() } else { return "Please provide a wolves API key".to_string(); }; - let role_current = if let Some(x) = sub_options.get(1) { - match &x.value { - CommandDataOptionValue::Role(role) => role.to_owned(), - _ => return "Please provide a valid role for ``Role Current``".to_string(), - } + let role_current = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.get(1) + { + role.to_owned() } else { return "Please provide a valid role for ``Role Current``".to_string(); }; - let role_past = if let Some(x) = sub_options.get(5) { - match &x.value { - CommandDataOptionValue::Role(role) => Some(role.to_owned()), - _ => None, - } + let role_past = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.get(5) + { + Some(role.to_owned()) } else { None }; - let bot_channel_id = if let Some(x) = sub_options.get(2) { - match &x.value { - CommandDataOptionValue::Channel(channel) => channel.to_owned(), - _ => return "Please provide a valid channel for ``Bot Channel``".to_string(), - } + let bot_channel_id = if let Some(CommandDataOption { + value: CommandDataOptionValue::Channel(channel), + .. + }) = command.data.options.get(2) + { + channel.to_owned() } else { return "Please provide a valid channel for ``Bot Channel``".to_string(); }; - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; + let db = db_lock.read().await; let server_data = Servers { server: command.guild_id.unwrap_or_default(), wolves_api, - wolves_id: 0, role_past, role_current, member_past: 0, member_current: 0, bot_channel_id, + server_name: "".to_string(), }; match add_server(&db, ctx, &server_data).await { Ok(_) => {} Err(e) => { - println!("{e:?}"); - return format!("Failure to insert into Servers {server_data:?}"); + println!("{:?}", e); + return format!("Failure to insert into Servers {:?}", server_data); } } "Added/Updated server info".to_string() } +pub fn register() -> CreateCommand { + CreateCommand::new("add") + .description("Enable the bot for this discord") + .add_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false)) +} + async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Result, Error> { let existing = get_server_config(db, &server.server).await; let role_past = server.role_past.map(|x| x.get() as i64); @@ -101,7 +107,7 @@ async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Resul .fetch_optional(db) .await; - // if the entry does not exist already then do a user update + // if the entry does not exist already tehn do a user update let (update, current_remove, current_role, past_remove, past_role) = match &existing { None => (true, false, None, false, None), Some(x) => { diff --git a/src/commands/committee.rs b/src/commands/committee.rs deleted file mode 100644 index 944fbc0..0000000 --- a/src/commands/committee.rs +++ /dev/null @@ -1,27 +0,0 @@ -use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption}; - -pub fn register() -> CreateCommand { - CreateCommand::new("committee") - .description("Commands related to what committees can do") - .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "add", "Enable the bot for this discord") - .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true)) - .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true)) - .add_sub_option( - CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true), - ) - .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false)), - ) - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "roles_adder", "Combine roles together to an new one") - .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true)) - .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true)) - .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true)) - .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)), - ) - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommandGroup, "icon", "Committee commands for the server icon") - .add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "change", "Change the server icon.")), - ) -} diff --git a/src/commands/count.rs b/src/commands/count.rs deleted file mode 100644 index 41b9d81..0000000 --- a/src/commands/count.rs +++ /dev/null @@ -1,259 +0,0 @@ -pub mod committee { - - // get the list of all the current clubs/socs members - - use serenity::all::{ - CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, - }; - use skynet_discord_bot::common::{database::DataBase, set_roles::committee::db_roles_get}; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(key), - .. - }) = command.data.options.first() - { - key - } else { - return "Please provide a wolves API key".to_string(); - }; - - let all = if let Some(x) = sub_options.first() { - match x.value { - CommandDataOptionValue::Boolean(y) => y, - _ => false, - } - } else { - false - }; - - let db = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - - let mut cs = vec![]; - // pull it from a DB - for committee in db_roles_get(&db).await { - if !all && committee.count == 0 { - continue; - } - cs.push((committee.count, committee.name_role.to_owned())); - } - - cs.sort_by_key(|(count, _)| *count); - cs.reverse(); - - // msg can be a max 2000 chars long - let mut limit = 2000 - 3; - - let mut response = vec!["```".to_string()]; - for (count, name) in cs { - let leading = if count < 10 { " " } else { "" }; - - let line = format!("{leading}{count} {name}"); - - let length = line.len() + 1; - - if length < (limit + 3) { - response.push(line); - limit -= length; - } else { - break; - } - } - response.push("```".to_string()); - - response.join("\n") - } - - pub fn register() -> CreateCommand { - CreateCommand::new("count") - .description("Count Committee Members") - // All Committee members are able to add reactions to posts - .default_member_permissions(serenity::model::Permissions::ADD_REACTIONS) - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "committee", "List out the Committee Roles Numbers") - .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)), - ) - } -} - -pub mod servers { - // get the list of all the current clubs/socs - use serde::{Deserialize, Serialize}; - use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption}; - use skynet_discord_bot::{ - common::{ - database::{get_server_config_bulk, DataBase}, - set_roles::committee::get_committees, - }, - get_now_iso, - }; - use sqlx::{Pool, Sqlite}; - use std::collections::HashMap; - - pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { - let db = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - - let mut committees = HashMap::new(); - if let Some(x) = get_committees(&db).await { - for committee in x { - committees.insert(committee.id, committee.to_owned()); - } - } - - let mut cs = vec![]; - // pull it from a DB - for server_config in get_server_config_bulk(&db).await { - if let Some(x) = committees.get(&server_config.wolves_id) { - cs.push((server_config.member_current, server_config.member_past, x.name_full.to_owned())); - } - } - - // get all members - let (wolves_current, wolves_past, total) = get_wolves_total(&db).await; - cs.push((total, total, String::from("Skynet Network"))); - cs.push((wolves_current, wolves_past, String::from("Clubs/Socs Servers"))); - - // treat teh committee server as its own thing - let committee_current = get_wolves_committee(&db).await; - cs.push((committee_current, committee_current, String::from("Committee Server"))); - - cs.sort_by_key(|(current, _, _)| *current); - cs.reverse(); - - // msg can be a max 2000 chars long - let mut limit = 2000 - 3; - - let mut response = vec!["```".to_string()]; - for (current, past, name) in cs { - let current_leading = if current < 10 { - " " - } else if current < 100 { - " " - } else { - "" - }; - let past_leading = if past < 10 { - " " - } else if past < 100 { - " " - } else { - "" - }; - - let line = format!("{current_leading}{current} {past_leading}{past} {name}"); - - let length = line.len() + 1; - - // +3 is to account for the closing fence - if length < (limit + 3) { - response.push(line); - limit -= length; - } else { - break; - } - } - response.push("```".to_string()); - - response.join("\n") - } - - #[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)] - pub struct Count { - pub count: i64, - } - - async fn get_wolves_total(db: &Pool) -> (i64, i64, i64) { - let current = match sqlx::query_as::<_, Count>( - r#" - SELECT COUNT(DISTINCT id_wolves) as count - FROM server_members - JOIN wolves USING (id_wolves) - WHERE ( - wolves.discord IS NOT NULL - AND server_members.expiry > ? - ) - "#, - ) - .bind(get_now_iso(true)) - .fetch_one(db) - .await - { - Ok(res) => res.count, - Err(e) => { - dbg!(e); - 0 - } - }; - - let cns = match sqlx::query_as::<_, Count>( - r#" - SELECT COUNT(DISTINCT id_wolves) as count - FROM server_members - JOIN wolves USING (id_wolves) - WHERE wolves.discord IS NOT NULL - "#, - ) - .bind(get_now_iso(true)) - .fetch_one(db) - .await - { - Ok(res) => res.count, - Err(e) => { - dbg!(e); - 0 - } - }; - - let total = match sqlx::query_as::<_, Count>( - r#" - SELECT COUNT(DISTINCT id_wolves) as count - FROM wolves - WHERE discord IS NOT NULL - "#, - ) - .fetch_one(db) - .await - { - Ok(res) => res.count, - Err(e) => { - dbg!(e); - 0 - } - }; - - (current, cns, total) - } - - async fn get_wolves_committee(db: &Pool) -> i64 { - // expiry - match sqlx::query_as::<_, Count>( - " - SELECT count - FROM committee_roles - WHERE id_wolves = '0' - ", - ) - .fetch_one(db) - .await - { - Ok(res) => res.count, - Err(e) => { - dbg!(e); - 0 - } - } - } - - pub fn register() -> CreateCommand { - CreateCommand::new("count") - .description("Count the servers") - .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) - .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "servers", "List out all servers using the skynet bot")) - } -} diff --git a/src/commands/wolves.rs b/src/commands/link_email.rs similarity index 56% rename from src/commands/wolves.rs rename to src/commands/link_email.rs index 843ca70..750e0ee 100644 --- a/src/commands/wolves.rs +++ b/src/commands/link_email.rs @@ -4,27 +4,22 @@ use lettre::{ Message, SmtpTransport, Transport, }; use maud::html; -use serenity::{ - all::CommandOptionType, - builder::{CreateCommand, CreateCommandOption}, - client::Context, - model::id::UserId, -}; -use skynet_discord_bot::{ - common::database::{DataBase, Wolves, WolvesVerify}, - get_now_iso, random_string, Config, -}; +use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; +use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; +use skynet_discord_bot::{get_now_iso, random_string, Config}; use sqlx::{Pool, Sqlite}; pub mod link { use super::*; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; + use serde::{Deserialize, Serialize}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; + let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; @@ -42,23 +37,14 @@ pub mod link { return "Linking already in process, please check email.".to_string(); } - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), + let email = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(email), .. }) = command.data.options.first() { - options + email.trim() } else { - return "Please provide sub options".to_string(); - }; - - let email = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::String(email) => email.trim(), - _ => return "Please provide a valid email".to_string(), - } - } else { - return "Please provide a valid email".to_string(); + return "Please provide a valid user".to_string(); }; // check if email exists @@ -100,9 +86,9 @@ pub mod link { return "Email already verified".to_string(); } - // generate an auth key + // generate a auth key let auth = random_string(20); - match send_mail(&config, &details.email, &auth, &command.user.name) { + match send_mail(&config, &details, &auth, &command.user.name) { Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { Ok(_) => {} Err(e) => { @@ -114,7 +100,13 @@ pub mod link { } } - format!("Verification email sent to {email}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.") + format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) + } + + pub fn register() -> CreateCommand { + CreateCommand::new("link_wolves") + .description("Set Wolves Email") + .add_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)) } pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { @@ -145,35 +137,33 @@ pub mod link { .ok() } - fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { - let discord = "https://computer.discord.skynet.ie"; + fn send_mail(config: &Config, email: &Wolves, auth: &str, user: &str) -> Result { + let mail = &email.email; + let discord = "https://discord.skynet.ie"; let sender = format!("UL Computer Society <{}>", &config.mail_user); // Create the html we want to send. let html = html! { head { - title { "UL Wolves Discord Linker" } + title { "Hello from Skynet!" } style type="text/css" { "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" } } div { - h2 { "UL Wolves Discord Linker" } - - h3 { "Link your UL Wolves Account to Discord" } + h2 { "Hello from Skynet!" } // Substitute in the name of our recipient. p { "Hi " (user) "," } p { - "Please paste this line into Discord (and press enter) to verify your discord account:" - br; - pre { "/wolves verify code: " (auth)} + "Please use " pre { "/verify code: " (auth)} " to verify your discord account." } - hr; - h3 { "Help & Support" } p { - "If you have issues please refer to our Computer Society Discord Server:" + "If you have issues please refer to our Discord server:" br; a href=(discord) { (discord) } + } + p { + "Skynet Team" br; "UL Computer Society" } @@ -182,23 +172,15 @@ pub mod link { let body_text = format!( r#" - UL Wolves Discord Linker - Link your UL Wolves Account to Discord - - Link your Account + Hi {user} - Hi {user}, - - Please paste this line into Discord (and press enter) to verify your Discord account: - /wolves verify code: {auth} + Please use "/verify code: {auth}" to verify your discord account. - ------------------------------------------------------------------------- - - Help & Support - - If you have issues please refer to our Computer Society Discord Server: - {discord} - UL Computer Society + If you have issues please refer to our Discord server: + {discord} + + Skynet Team + UL Computer Society "# ); @@ -206,10 +188,10 @@ pub mod link { let email = Message::builder() .from(sender.parse().unwrap()) .to(mail.parse().unwrap()) - .subject("Skynet: Link Discord to Wolves.") + .subject("Skynet-Discord: Link Wolves.") .multipart( // This is composed of two parts. - // also helps not trip spam settings (uneven number of urls) + // also helps not trip spam settings (uneven number of url's MultiPart::alternative() .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text)) .singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())), @@ -268,6 +250,19 @@ pub mod link { .await } + #[derive(Serialize, Deserialize, Debug)] + #[serde(untagged)] + pub enum WolvesResultUserResult { + B(bool), + S(String), + } + + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResultUser { + success: i64, + result: WolvesResultUserResult, + } + async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { sqlx::query_as::<_, Wolves>( " @@ -283,70 +278,42 @@ pub mod link { } } -pub mod link_docs { - use super::*; - pub mod users { - use super::*; - use serenity::all::CommandInteraction; - - pub async fn run(_command: &CommandInteraction, _ctx: &Context) -> String { - "https://forgejo.skynet.ie/Skynet/discord-bot/src/branch/main/doc/User.md".to_string() - } - } - - // pub mod committee { - // - // } -} - pub mod verify { use super::*; - use crate::commands::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; - use serenity::{ - all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}, - model::user::User, - }; - use skynet_discord_bot::common::{ - database::{get_server_config, ServerMembersWolves, Servers}, - wolves::committees::Committees, - }; + use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; + use serenity::model::user::User; + use skynet_discord_bot::common::database::get_server_config; + use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; use sqlx::Error; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db = { + let db_lock = { let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() + data_read.get::().expect("Expected Databse in TypeMap.").clone() }; + let db = db_lock.read().await; // check if user has used /link_wolves let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await { x } else { - return "Please use ''/wolves link'' first".to_string(); + return "Please use /link_wolves first".to_string(); }; - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), + let code = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(code), .. }) = command.data.options.first() { - options - } else { - return "Please provide sub options".to_string(); - }; - - let code = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::String(y) => y.trim(), - _ => return "Please provide a verification code".to_string(), - } + code } else { return "Please provide a verification code".to_string(); }; db_pending_clear_expired(&db).await; - if details.auth_code != code { + if &details.auth_code != code { return "Invalid verification code".to_string(); } @@ -356,24 +323,26 @@ pub mod verify { Ok(_) => { // get teh right roles for the user set_server_roles(&db, &command.user, ctx).await; - - // check if they are a committee member, and on that server - set_server_roles_committee(&db, &command.user, ctx).await; - "Discord username linked to Wolves".to_string() } Err(e) => { - println!("{e:?}"); + println!("{:?}", e); "Failed to save, please try /link_wolves again".to_string() } }; } - Err(e) => println!("{e:?}"), + Err(e) => println!("{:?}", e), } "Failed to verify".to_string() } + pub fn register() -> CreateCommand { + CreateCommand::new("verify") + .description("Verify Wolves Email") + .add_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)) + } + async fn db_pending_clear_successful(pool: &Pool, user: &UserId) -> Result, Error> { sqlx::query_as::<_, WolvesVerify>( r#" @@ -425,7 +394,7 @@ pub mod verify { } if let Err(e) = member.add_roles(&ctx, &roles).await { - println!("{e:?}"); + println!("{:?}", e); } } } @@ -433,44 +402,6 @@ pub mod verify { } } - async fn get_committees_id(db: &Pool, wolves_id: i64) -> Vec { - sqlx::query_as::<_, Committees>( - r#" - SELECT * - FROM committees - WHERE committee LIKE ?1 - "#, - ) - .bind(format!("%{wolves_id}%")) - .fetch_all(db) - .await - .unwrap_or_else(|e| { - dbg!(e); - vec![] - }) - } - - async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - if let Some(x) = get_server_member_discord(db, &discord.id).await { - // if they are a member of one or more committees, and in teh committee server then give them the general committee role - // they will get teh more specific vanity role later - if !get_committees_id(db, x.id_wolves).await.is_empty() { - let server = config.committee_server; - let committee_member = config.committee_role; - - if let Ok(member) = server.member(ctx, &discord.id).await { - member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); - } - } - } - } - async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { sqlx::query_as::<_, ServerMembersWolves>( " @@ -485,63 +416,3 @@ pub mod verify { .await } } - -pub mod unlink { - use serenity::all::{CommandInteraction, Context, UserId}; - use skynet_discord_bot::common::database::{DataBase, Wolves}; - use sqlx::{Pool, Sqlite}; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - - // doesn't matter if there is one or not, it will be removed regardless - delete_link(&db, &command.user.id).await; - - "Discord link removed".to_string() - } - - async fn delete_link(db: &Pool, user: &UserId) { - match sqlx::query_as::<_, Wolves>( - " - UPDATE wolves - SET discord = NULL - WHERE discord = ?1; - ", - ) - .bind(user.get() as i64) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - dbg!(e); - } - } - } -} - -pub fn register() -> CreateCommand { - CreateCommand::new("wolves") - .description("Commands related to UL Wolves") - // link - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "link", "Link your Wolves account to your Discord") - .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)), - ) - // verify - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "verify", "Verify Wolves Email") - .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)), - ) - // unlink - .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord")) - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "link_minecraft", "Link your minecraft account") - .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true)) - .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false)), - ) - .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "docs", "Link to where the documentation can be found.")) -} diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 5098f11..54bf35c 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -1,4 +1,4 @@ -use serenity::client::Context; +use serenity::{builder::CreateCommand, client::Context}; use skynet_discord_bot::common::database::DataBase; use sqlx::{Pool, Sqlite}; @@ -7,26 +7,28 @@ pub(crate) mod user { use super::*; pub(crate) mod add { use super::*; - use crate::commands::wolves::link::get_server_member_discord; + use crate::commands::link_email::link::get_server_member_discord; use serde::{Deserialize, Serialize}; - use serenity::{ - all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}, - model::id::UserId, - }; - use skynet_discord_bot::{ - common::{ - database::Wolves, - minecraft::{whitelist_update, Minecraft}, - }, - Config, - }; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; + use serenity::model::id::UserId; + use skynet_discord_bot::common::database::Wolves; + use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; + use skynet_discord_bot::Config; use sqlx::Error; + pub fn register() -> CreateCommand { + CreateCommand::new("link_minecraft") + .description("Link your minecraft account") + .add_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false)) + } + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; + let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; @@ -36,33 +38,26 @@ pub(crate) mod user { // user has to have previously linked with wolves if get_server_member_discord(&db, &command.user.id).await.is_none() { - return "Not linked with wolves, please use ``/wolves link`` with your wolves email.".to_string(); + return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string(); } - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), + let username = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(username), .. }) = command.data.options.first() { - options - } else { - return "Please provide sub options".to_string(); - }; - - let username = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::String(username) => username.trim(), - _ => return "Please provide a valid username".to_string(), - } + username.trim() } else { return "Please provide a valid username".to_string(); }; - let java = if let Some(x) = sub_options.get(1) { - match &x.value { - CommandDataOptionValue::Boolean(z) => !z, - _ => true, - } + // this is always true unless they state its not + let java = if let Some(CommandDataOption { + value: CommandDataOptionValue::Boolean(z), + .. + }) = command.data.options.get(1) + { + !z } else { true }; @@ -74,14 +69,14 @@ pub(crate) mod user { Ok(_) => {} Err(e) => { dbg!("{:?}", e); - return format!("Failure to minecraft username {username:?}"); + return format!("Failure to minecraft username {:?}", username); } } username_mc = username.to_string(); } else { match get_minecraft_bedrock(username, &config.minecraft_mcprofile).await { None => { - return format!("No UID found for {username:?}"); + return format!("No UID found for {:?}", username); } Some(x) => { match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await { @@ -190,29 +185,26 @@ pub(crate) mod server { use super::*; pub(crate) mod add { - use serenity::{ - all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}, - model::id::GuildId, - }; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; + use serenity::model::id::GuildId; use sqlx::Error; - // this is to manage the server side of commands related to minecraft + // this is to managfe the server side of commands related to minecraft use super::*; - use skynet_discord_bot::{ - common::minecraft::{update_server, Minecraft}, - Config, - }; + use skynet_discord_bot::common::minecraft::update_server; + use skynet_discord_bot::common::minecraft::Minecraft; + use skynet_discord_bot::{is_admin, Config}; pub fn register() -> CreateCommand { - CreateCommand::new("minecraft_add") - .description("Add a minecraft server") - .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) - .add_option( - CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society") - .required(true), - ) + CreateCommand::new("minecraft_add").description("Add a minecraft server").add_option( + CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society").required(true), + ) } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + // check if user has high enough permisssions + if let Some(msg) = is_admin(command, ctx).await { + return msg; + } let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, @@ -228,15 +220,16 @@ pub(crate) mod server { return String::from("Expected Server ID"); }; - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; + let db = db_lock.read().await; match add_server(&db, &g_id, &server_minecraft).await { Ok(_) => {} Err(e) => { - println!("{e:?}"); + println!("{:?}", e); return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft); } } @@ -267,31 +260,31 @@ pub(crate) mod server { } pub(crate) mod list { - use serenity::{all::CommandInteraction, builder::CreateCommand, client::Context}; - use skynet_discord_bot::{ - common::{ - database::DataBase, - minecraft::{get_minecraft_config_server, server_information}, - }, - Config, - }; + use serenity::all::CommandInteraction; + use serenity::builder::CreateCommand; + use serenity::client::Context; + use skynet_discord_bot::common::database::DataBase; + use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information}; + use skynet_discord_bot::{is_admin, Config}; pub fn register() -> CreateCommand { - CreateCommand::new("minecraft_list") - .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) - .description("List your minecraft servers") + CreateCommand::new("minecraft_list").description("List your minecraft servers") } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + if let Some(msg) = is_admin(command, ctx).await { + return msg; + } let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, }; - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; + let db = db_lock.read().await; let servers = get_minecraft_config_server(&db, g_id).await; @@ -328,26 +321,26 @@ pub(crate) mod server { } pub(crate) mod delete { - use serenity::{ - all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}, - builder::CreateCommand, - client::Context, - model::id::GuildId, - }; - use skynet_discord_bot::common::{database::DataBase, minecraft::Minecraft}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; + use serenity::builder::CreateCommand; + use serenity::client::Context; + use serenity::model::id::GuildId; + use skynet_discord_bot::common::database::DataBase; + use skynet_discord_bot::common::minecraft::Minecraft; + use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; pub fn register() -> CreateCommand { - CreateCommand::new("minecraft_delete") - .description("Delete a minecraft server") - .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) - .add_option( - CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society") - .required(true), - ) + CreateCommand::new("minecraft_delete").description("Delete a minecraft server").add_option( + CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society").required(true), + ) } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + // check if user has high enough permisssions + if let Some(msg) = is_admin(command, ctx).await { + return msg; + } let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, @@ -363,15 +356,16 @@ pub(crate) mod server { return String::from("Expected Server ID"); }; - let db = { - let data = ctx.data.read().await; - data.get::().expect("Expected Databse in TypeMap.").clone() + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() }; + let db = db_lock.read().await; match server_remove(&db, &g_id, &server_minecraft).await { Ok(_) => {} Err(e) => { - println!("{e:?}"); + println!("{:?}", e); return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft); } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 5815541..9e4170a 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,7 +1,4 @@ pub mod add_server; -pub mod committee; -pub mod count; +pub mod link_email; pub mod minecraft; pub mod role_adder; -pub mod server_icon; -pub mod wolves; diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index 0573b37..182eeb9 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -1,48 +1,47 @@ use serenity::client::Context; use skynet_discord_bot::common::database::{DataBase, RoleAdder}; +use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; pub mod edit { use super::*; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), + // check if user has high enough permisssions + if let Some(msg) = is_admin(command, ctx).await { + return msg; + } + + let role_a = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), .. }) = command.data.options.first() { - options + role.to_owned() } else { - return "Please provide sub options".to_string(); + return "Please provide a valid role for ``Role Current``".to_string(); }; - let role_a = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::Role(role) => role.to_owned(), - _ => return "Please provide a valid role for ``Role A``".to_string(), - } + let role_b = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.get(1) + { + role.to_owned() } else { - return "Please provide a valid role for ``Role A``".to_string(); + return "Please provide a valid role for ``Role Current``".to_string(); }; - let role_b = if let Some(x) = sub_options.get(1) { - match &x.value { - CommandDataOptionValue::Role(role) => role.to_owned(), - _ => return "Please provide a valid role for ``Role B``".to_string(), - } + let role_c = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.get(2) + { + role.to_owned() } else { - return "Please provide a valid role for ``Role B``".to_string(); - }; - - let role_c = if let Some(x) = sub_options.get(2) { - match &x.value { - CommandDataOptionValue::Role(role) => role.to_owned(), - _ => return "Please provide a valid role for ``Role C``".to_string(), - } - } else { - return "Please provide a valid role for ``Role C``".to_string(); + return "Please provide a valid role for ``Role Current``".to_string(); }; if role_a == role_b { @@ -53,19 +52,21 @@ pub mod edit { return "Role C cannot be same as A or B".to_string(); } - let delete = if let Some(x) = sub_options.get(3) { - match &x.value { - CommandDataOptionValue::Boolean(z) => *z, - _ => false, - } + let delete = if let Some(CommandDataOption { + value: CommandDataOptionValue::Boolean(z), + .. + }) = command.data.options.get(3) + { + *z } else { false }; - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; + let db = db_lock.read().await; let server = command.guild_id.unwrap_or_default(); let server_data = RoleAdder { @@ -78,8 +79,8 @@ pub mod edit { match add_server(&db, &server_data, delete).await { Ok(_) => {} Err(e) => { - println!("{e:?}"); - return format!("Failure to insert into Servers {server_data:?}"); + println!("{:?}", e); + return format!("Failure to insert into Servers {:?}", server_data); } } @@ -100,12 +101,21 @@ pub mod edit { } if delete { - format!("Removed {role_a_name} + {role_b_name} = {role_c_name}") + format!("Removed {} + {} = {}", role_a_name, role_b_name, role_c_name) } else { - format!("Added {role_a_name} + {role_b_name} = {role_c_name}") + format!("Added {} + {} = {}", role_a_name, role_b_name, role_c_name) } } + pub fn register() -> CreateCommand { + CreateCommand::new("roles_adder") + .description("Combine roles together to an new one") + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)) + } + async fn add_server(db: &Pool, server: &RoleAdder, delete: bool) -> Result, Error> { if delete { sqlx::query_as::<_, RoleAdder>( @@ -141,12 +151,13 @@ pub mod edit { pub mod list {} pub mod tools { - use serenity::{client::Context, model::guild::Member}; + use serenity::client::Context; + use serenity::model::guild::Member; use skynet_discord_bot::common::database::RoleAdder; use sqlx::{Pool, Sqlite}; pub async fn on_role_change(db: &Pool, ctx: &Context, new_data: Member) { - // check if the role changed is part of the ones for this server + // check if the role changed is part of the oens for this server if let Ok(role_adders) = sqlx::query_as::<_, RoleAdder>( r#" SELECT * @@ -162,7 +173,7 @@ pub mod tools { let mut roles_remove = vec![]; for role_adder in role_adders { - // if the user has both A and B give them C + // if the user has both A dnd B give them C if new_data.roles.contains(&role_adder.role_a) && new_data.roles.contains(&role_adder.role_b) && !new_data.roles.contains(&role_adder.role_c) { roles_add.push(role_adder.role_c); @@ -178,13 +189,13 @@ pub mod tools { if !roles_add.is_empty() { if let Err(e) = new_data.add_roles(&ctx, &roles_add).await { - println!("{e:?}"); + println!("{:?}", e); } } if !roles_remove.is_empty() { if let Err(e) = new_data.remove_roles(&ctx, &roles_remove).await { - println!("{e:?}"); + println!("{:?}", e); } } } diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs deleted file mode 100644 index d4a78d5..0000000 --- a/src/commands/server_icon.rs +++ /dev/null @@ -1,223 +0,0 @@ -use serenity::all::{CommandInteraction, Context}; -use skynet_discord_bot::{ - common::{ - database::DataBase, - server_icon::{get_config_icons, update_icon::update_icon_main, ServerIcons}, - }, - Config, -}; - -use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption}; - -// commands that server mods are able to use -pub(crate) mod admin { - use super::*; - - // Moderators can force a icon change - pub(crate) mod change { - use super::*; - - pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { - let db = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config_global = config_lock.read().await; - - let config_toml = get_config_icons::minimal(); - update_icon_main(ctx, &db, &config_global, &config_toml).await; - - "Changed server Icon".to_string() - } - } -} - -// commands for general users -pub(crate) mod user { - use super::*; - use skynet_discord_bot::common::server_icon::get_config_icons::ConfigTomlLocal; - - pub fn register() -> CreateCommand { - CreateCommand::new("icon") - .description("Commands related to the Server Icon") - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommandGroup, "current", "Information on current items.") - .add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "icon", "Information on current icon.")) - .add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "festival", "Information on current festival.")), - ) - .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "stats", "How many times the particular icon has been used")) - } - - fn get_logo_url(config_toml: &ConfigTomlLocal, logo_name: &str) -> String { - format!("{}/src/branch/main/{}/{}", &config_toml.source.repo, &config_toml.source.directory, logo_name) - } - - /// Regular users can get teh link to teh current icon - pub(crate) mod current { - use super::*; - - pub(crate) mod icon { - use super::*; - use serenity::all::{CreateAttachment, EditInteractionResponse}; - - use sqlx::{Pool, Sqlite}; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - - let config_toml = get_config_icons::minimal(); - - if let Some(logo) = get_current_icon(&db).await { - if let Ok(attachment) = CreateAttachment::path(&logo.path).await { - match command.edit_response(&ctx.http, EditInteractionResponse::new().new_attachment(attachment)).await { - Ok(_) => {} - Err(e) => { - dbg!(e); - } - } - } - - format!("[{}]({})", &logo.name, get_logo_url(&config_toml, &logo.name)) - } else { - "Could not find current icon".to_string() - } - } - - pub async fn get_current_icon(db: &Pool) -> Option { - match sqlx::query_as::<_, ServerIcons>( - " - SELECT * from server_icons ORDER BY id DESC LIMIT 1 - ", - ) - .fetch_one(db) - .await - { - Ok(res) => Some(res), - Err(e) => { - dbg!(e); - None - } - } - } - } - - pub(crate) mod festival { - use serenity::all::{CommandInteraction, Context}; - use skynet_discord_bot::{ - common::server_icon::{get_config_icons, update_icon::get_festival}, - Config, - }; - - // use this to return what current festivals are active? - pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - let config_toml = get_config_icons::full(&config); - - let response = get_festival(&config_toml).current; - - if response.is_empty() { - "No festival currently active".to_string() - } else { - format!("Festivals active: {}", response.join(", ")) - } - } - } - } - - /// Get the statistics of the icons - pub(crate) mod stats { - use super::*; - use sqlx::{Pool, Sqlite}; - - pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { - let db = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - - let config_toml = get_config_icons::minimal(); - - let totals = get_totals(&db).await; - - fmt_msg(&config_toml, &totals) - } - - #[derive(Debug, Clone, sqlx::FromRow)] - pub struct CountResult { - pub name: String, - pub times: i64, - } - - async fn get_totals(db: &Pool) -> Vec { - sqlx::query_as::<_, CountResult>( - " - SELECT - DISTINCT name, - COUNT(*) OVER(PARTITION BY name) AS times - FROM server_icons - ", - ) - .fetch_all(db) - .await - .unwrap_or_else(|e| { - dbg!(e); - vec![] - }) - } - - fn fmt_msg(config_toml: &ConfigTomlLocal, totals: &[CountResult]) -> String { - let mut totals_local = totals.to_owned(); - totals_local.sort_by_key(|x| x.times); - totals_local.reverse(); - - // msg can be a max 2000 chars long - let mut limit = 2000 - 3; - - let mut response = vec![]; - for CountResult { - name, - times, - } in &totals_local - { - let current_leading = if times < &10 { - "00" - } else if times < &100 { - "0" - } else { - "" - }; - - let url = get_logo_url(config_toml, name); - - // the `` is so that the numbers will be rendered in monospaced font - // the <> is to suppress the URL embed - let line = format!("``{current_leading}{times}`` [{name}](<{url}>)"); - - let length = line.len() + 1; - - // +3 is to account for the closing fence - if length < (limit + 3) { - response.push(line); - limit -= length; - } else { - break; - } - } - - response.join("\n") - } - } -} diff --git a/src/common/database.rs b/src/common/database.rs index 2eaf5df..596b411 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -1,21 +1,17 @@ use crate::Config; use serde::{Deserialize, Serialize}; -use serenity::{ - model::{ - guild, - id::{ChannelId, GuildId, RoleId, UserId}, - }, - prelude::TypeMapKey, -}; -use sqlx::{ - sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}, - Error, FromRow, Pool, Row, Sqlite, -}; -use std::{str::FromStr, sync::Arc}; +use serenity::model::guild; +use serenity::model::id::{ChannelId, GuildId, RoleId, UserId}; +use serenity::prelude::TypeMapKey; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}; +use sqlx::{Error, FromRow, Pool, Row, Sqlite}; +use std::str::FromStr; +use std::sync::Arc; +use tokio::sync::RwLock; pub struct DataBase; impl TypeMapKey for DataBase { - type Value = Arc>; + type Value = Arc>>; } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -125,12 +121,12 @@ impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { pub struct Servers { pub server: GuildId, pub wolves_api: String, - pub wolves_id: i64, pub role_past: Option, pub role_current: RoleId, pub member_past: i64, pub member_current: i64, pub bot_channel_id: ChannelId, + pub server_name: String, } impl<'r> FromRow<'r, SqliteRow> for Servers { @@ -162,12 +158,12 @@ impl<'r> FromRow<'r, SqliteRow> for Servers { Ok(Self { server, wolves_api: row.try_get("wolves_api")?, - wolves_id: row.try_get("wolves_id").unwrap_or(0), role_past, role_current, member_past: row.try_get("member_past")?, member_current: row.try_get("member_current")?, bot_channel_id, + server_name: row.try_get("server_name")?, }) } } @@ -194,28 +190,14 @@ impl<'r> FromRow<'r, SqliteRow> for RoleAdder { } } -pub(crate) fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { - let id = match row.try_get(col) { +fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { + match row.try_get(col) { Ok(x) => { let tmp: i64 = x; - tmp as u64 + RoleId::new(tmp as u64) } - _ => 0, - }; - - RoleId::from(id) -} - -pub(crate) fn get_channel_from_row(row: &SqliteRow, col: &str) -> ChannelId { - let id = match row.try_get(col) { - Ok(x) => { - let tmp: i64 = x; - tmp as u64 - } - _ => 0, - }; - - ChannelId::from(id) + _ => RoleId::from(0u64), + } } pub async fn db_init(config: &Config) -> Result, Error> { @@ -224,7 +206,7 @@ pub async fn db_init(config: &Config) -> Result, Error> { let pool = SqlitePoolOptions::new() .max_connections(5) .connect_with( - SqliteConnectOptions::from_str(&format!("sqlite://{database}"))? + SqliteConnectOptions::from_str(&format!("sqlite://{}", database))? .foreign_keys(true) .create_if_missing(true), ) diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 77cd754..4faa8ed 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -1,7 +1,10 @@ -use crate::{common::set_roles::normal::get_server_member_bulk, Config}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use crate::common::set_roles::normal::get_server_member_bulk; +use crate::Config; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; use serenity::model::id::GuildId; -use sqlx::{sqlite::SqliteRow, Error, FromRow, Pool, Row, Sqlite}; +use sqlx::sqlite::SqliteRow; +use sqlx::{Error, FromRow, Pool, Row, Sqlite}; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Minecraft { @@ -24,7 +27,7 @@ impl<'r> FromRow<'r, SqliteRow> for Minecraft { /** loop through all members of server get a list of folks with mc accounts that are members -and a list that aren't members +and a list that arent members */ pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, config: &Config) { let mut usernames = vec![]; @@ -109,7 +112,7 @@ pub async fn whitelist_wipe(server: &str, token: &str) { }; post(&format!("{url_base}/files/delete"), &bearer, &deletion).await; - // recreate the file, passing in the type here so the compiler knows what type of Vec it is + // recreate teh file, passing in the type here so the compiler knows what type of vec it is post::>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await; // reload the whitelist @@ -152,7 +155,7 @@ pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Ve } pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { - println!("Update whitelist for {server}"); + println!("Update whitelist for {}", server); let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); diff --git a/src/common/mod.rs b/src/common/mod.rs index 9e1745e..38f457a 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -2,6 +2,3 @@ pub mod database; pub mod minecraft; pub mod set_roles; pub mod wolves; - -pub mod renderer; -pub mod server_icon; diff --git a/src/common/renderer.rs b/src/common/renderer.rs deleted file mode 100644 index f2c50cc..0000000 --- a/src/common/renderer.rs +++ /dev/null @@ -1,193 +0,0 @@ -// this code is taken from https://github.com/MCorange99/svg2colored-png/tree/main -// I was unable to figure out how to use usvg myself so yoinked it from here. - -use std::{ - ffi::OsStr, - path::{Path, PathBuf}, -}; - -use color_eyre::{eyre::bail, Result}; -use usvg_text_layout::TreeTextToPath; - -#[derive(Debug, Clone)] -pub struct Args { - pub input: PathBuf, - - /// Output folder where the PNG's will be placed - pub output: PathBuf, - - /// Comma separated colors that will be used in HEX Eg. 000000,ffffff - /// Can be like an object: black:000000,white:ffffff - pub colors: String, - - /// Width of the generated PNG's - pub width: u32, - - /// Height of the generated PNG's - pub height: u32, -} - -#[derive(Debug, Clone)] -enum ColorType { - Array(Vec), - Object(Vec<(String, String)>), - None, -} - -#[derive(Debug, Clone)] -pub struct Renderer { - fontdb: usvg_text_layout::fontdb::Database, - colors: ColorType, - size: (u32, u32), - pub count: u64, -} - -impl Renderer { - pub fn new(args: &Args) -> Result { - let mut db = usvg_text_layout::fontdb::Database::new(); - db.load_system_fonts(); - - let mut this = Self { - fontdb: db, - colors: ColorType::None, - size: (args.width, args.height), - count: 0, - }; - - let colors = if args.colors.contains(':') { - //? object - let obj = args - .colors - .split(',') - .map(|s| { - let s = s.split(':').collect::>(); - - if s.len() < 2 { - dbg!("Invalid color object, try checking help"); - return None; - } - - Some((s[0].to_string(), s[1].to_string())) - }) - .collect::>>(); - - let mut colors = Vec::new(); - - for c in obj.into_iter().flatten() { - std::fs::create_dir_all(args.output.join(&c.0))?; - - colors.push(c); - } - - ColorType::Object(colors) - } else { - //? list - // let colors = args.colors.split(",").map(|s| { - // s.to_string() - // }) - // .collect::>(); - - let mut colors = Vec::new(); - - for color in args.colors.split(',') { - std::fs::create_dir_all(args.output.join(color))?; - colors.push(color.to_string()) - } - - ColorType::Array(colors) - }; - - this.colors = colors; - Ok(this) - } - - pub fn render(&mut self, fi: &Path, args: &Args) -> Result<()> { - match fi.extension() { - Some(e) if e.to_str() == Some("svg") => {} - Some(_) | None => { - dbg!("Filer {:?} is not of type SVG", fi); - // util::logger::warning(format!("File '{}' is not of SVG type", fi.clone().to_str().unwrap())); - bail!("Failed to render"); - } - }; - - match self.colors.clone() { - ColorType::Array(c) => { - for color in c { - // log::info!("Rendering the color {color:?}"); - let fo = self.get_out_file(fi, &color, args); - self.render_one(fi, &fo, &color)?; - } - } - ColorType::Object(c) => { - for o in c { - // log::info!("Rendering the color {:?}", o); - let fo = self.get_out_file(fi, &o.0, args); - self.render_one(fi, &fo, &o.1)?; - } - } - ColorType::None => unreachable!(), - } - - Ok(()) - } - - fn render_one(&mut self, fi: &Path, fo: &Path, color: &String) -> Result<()> { - if fo.exists() { - dbg!("File {fo:?} exists, skipping"); - return Ok(()); - } - - let svg = self.set_color(&self.get_svg_data(fi)?, color); - - let opt = usvg::Options { - // Get file's absolute directory. - resources_dir: std::fs::canonicalize(fi).ok().and_then(|p| p.parent().map(|p| p.to_path_buf())), - ..Default::default() - }; - - let mut tree = match usvg::Tree::from_data(svg.as_bytes(), &opt) { - Ok(v) => v, - Err(_) => { - dbg!("Failed to parse {fi:?}"); - bail!(""); - } - }; - - tree.convert_text(&self.fontdb); - - let mut pixmap = tiny_skia::Pixmap::new(self.size.0, self.size.1).unwrap(); - - // log::info!("Rendering {fo:?}"); - - //? maybe handle this and possibly throw error if its none - let _ = resvg::render(&tree, usvg::FitTo::Size(self.size.0, self.size.1), tiny_skia::Transform::default(), pixmap.as_mut()); - - pixmap.save_png(fo)?; - self.count += 1; - Ok(()) - } - - #[inline] - fn get_out_file(&mut self, fi: &Path, _sub_folder: &str, args: &Args) -> PathBuf { - let mut fo: std::path::PathBuf = args.output.clone(); - // fo.push(sub_folder); - fo.push(fi.file_name().unwrap_or(OsStr::new("default")).to_str().unwrap_or("default").replace(".svg", "")); - fo.set_extension("png"); - fo - } - - fn set_color(&self, svg: &str, color: &String) -> String { - svg.replace("fill=\"currentColor\"", &format!("fill=\"#{color}\"")) - } - - fn get_svg_data(&self, fi: &Path) -> Result { - match std::fs::read_to_string(fi) { - Ok(d) => Ok(d), - Err(_) => { - dbg!("File {fi:?} does not exist"); - bail!("File {fi:?} does not exist"); - } - } - } -} diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs deleted file mode 100644 index 1a7c2ef..0000000 --- a/src/common/server_icon.rs +++ /dev/null @@ -1,404 +0,0 @@ -use serde::Deserialize; -use std::{ffi::OsString, fs, path::PathBuf}; - -pub mod get_config_icons { - use super::*; - use crate::Config; - - #[derive(Deserialize)] - pub struct ConfigToml { - pub source: ConfigTomlSource, - pub festivals: Vec, - } - - #[derive(Deserialize)] - pub struct ConfigTomlLocal { - pub source: ConfigTomlSource, - } - #[derive(Deserialize)] - pub struct ConfigTomlRemote { - pub festivals: Vec, - } - - #[derive(Deserialize, Debug)] - pub struct ConfigTomlSource { - pub repo: String, - pub directory: String, - pub file: String, - } - - #[derive(Deserialize, Debug)] - pub struct ConfigTomlFestivals { - pub name: String, - pub all_year: bool, - pub start: ConfigTomlFestivalsTime, - pub end: ConfigTomlFestivalsTime, - } - - #[derive(Deserialize, Debug)] - pub struct ConfigTomlFestivalsTime { - pub day: u32, - pub month: u32, - pub year: i32, - } - pub fn minimal() -> ConfigTomlLocal { - let toml_raw_min = include_str!("../../.server-icons.toml"); - toml::from_str::(toml_raw_min).unwrap_or_else(|e| { - dbg!(e); - ConfigTomlLocal { - source: ConfigTomlSource { - repo: "".to_string(), - directory: "".to_string(), - file: "".to_string(), - }, - } - }) - } - - // since a copy of the festival file is in the repo we just need to get to it - pub fn full(config: &Config) -> ConfigToml { - let config_source = minimal(); - - let file_path = format!("{}/open-governance/{}/{}", &config.home, &config_source.source.directory, &config_source.source.file); - let contents = fs::read_to_string(file_path).unwrap_or_else(|e| { - dbg!(e); - "".to_string() - }); - let festivals = match toml::from_str::(&contents) { - Ok(config_festivals) => config_festivals.festivals, - Err(e) => { - dbg!(e); - vec![] - } - }; - - ConfigToml { - source: config_source.source, - festivals, - } - } -} - -#[derive(Debug)] -pub struct LogoData { - pub name: OsString, - pub path: PathBuf, -} - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct ServerIcons { - pub id: i64, - pub name: String, - pub path: String, - pub date: String, -} - -pub mod update_icon { - use super::*; - use crate::{ - common::{ - renderer::{Args, Renderer}, - server_icon::get_config_icons::{self, ConfigToml, ConfigTomlLocal}, - }, - get_now_iso, Config, - }; - use chrono::{Datelike, Utc}; - use rand::{rngs::SmallRng, seq::IndexedRandom, SeedableRng}; - use serenity::{ - all::GuildId, - builder::{CreateAttachment, EditGuild}, - client::Context, - }; - use sqlx::{Pool, Sqlite}; - use std::process::Command; - - /// Update the server icon, pulling from open governance. - pub async fn update_icon_main(ctx: &Context, db: &Pool, config_global: &Config, config_toml_local: &ConfigTomlLocal) { - let server = config_global.compsoc_server; - - // clone repo into local folder - clone_repo(config_global, config_toml_local); - - // now the repo has been downloaded/updated we can now access the festivals - let config_toml = get_config_icons::full(config_global); - - // see if there is a current festival - let festival_data = get_festival(&config_toml); - - // get a list of all the graphics files - let logos = get_logos(config_global, &config_toml); - - // filter them so only the current season (if any) are active - let logos_filtered = logos_filter(&festival_data, logos); - - let mut rng = SmallRng::from_os_rng(); - let logo_selected = logos_filtered.choose(&mut rng).unwrap(); - - logo_set(ctx, db, &server, logo_selected).await; - } - - #[derive(Debug)] - pub struct FestivalData { - pub current: Vec, - exclusions: Vec, - } - - pub fn get_festival(config_toml: &ConfigToml) -> FestivalData { - let today = Utc::now(); - let day = today.day(); - let month = today.month(); - let year = today.year(); - - let mut result = FestivalData { - current: vec![], - exclusions: vec![], - }; - - for festival in &config_toml.festivals { - if (day >= festival.start.day && day <= festival.end.day) && (month >= festival.start.month && month <= festival.end.month) { - if festival.start.year == 0 || festival.end.year == 0 || (year >= festival.start.year && year <= festival.end.year) { - result.current.push(festival.name.to_owned()); - } - } else if !festival.all_year { - result.exclusions.push(festival.name.to_owned()); - } - } - - result - } - - fn clone_repo(config: &Config, config_toml: &ConfigTomlLocal) { - let url = &config_toml.source.repo; - let folder = format!("{}/open-governance", &config.home); - - if let Err(e) = Command::new("git") - // clone the repo, gracefully "fails" - .arg("clone") - .arg(url) - .arg(&folder) - .output() - { - dbg!(e); - } - - if let Err(e) = Command::new("git") - // Update the repo - .arg("pull") - .arg("origin") - .arg("main") - .current_dir(&folder) - .output() - { - dbg!(e); - } - - if let Err(e) = Command::new("git") - // Install LFS for the repo - .arg("lfs") - .arg("install") - .current_dir(&folder) - .output() - { - dbg!(e); - } - - if let Err(e) = Command::new("git") - // clone the repo, gracefully "fails" - .arg("lfs") - .arg("pull") - .arg("origin") - .arg("main") - .current_dir(&folder) - .output() - { - dbg!(e); - } - } - - fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { - let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); - let folder_path = PathBuf::from(&folder); - let mut folder_output = folder_path.clone(); - folder_output.push("converted"); - let paths = match fs::read_dir(folder) { - Ok(x) => x, - Err(e) => { - dbg!(e); - return vec![]; - } - }; - - let args = Args { - input: folder_path.clone(), - output: folder_output, - colors: String::from(""), - width: 1024, - height: 1024, - }; - let mut r = match Renderer::new(&args) { - Ok(x) => x, - Err(e) => { - let _ = dbg!(e); - return vec![]; - } - }; - - let mut logos = vec![]; - - for tmp in paths.flatten() { - let path_local = tmp.path().to_owned(); - let path_local2 = tmp.path().to_owned(); - let name = match path_local2.file_name() { - None => { - dbg!(path_local2); - continue; - } - Some(x) => x.to_owned(), - }; - let mut path = tmp.path(); - - if path.is_dir() { - continue; - } - - match tmp.path().extension() { - None => {} - Some(ext) => { - if ext == "svg" { - let mut path_new = path_local.clone(); - path_new.set_extension("png"); - let filename_tmp = path_new.clone(); - let filename = match filename_tmp.file_name() { - None => { - dbg!(filename_tmp); - continue; - } - Some(x) => x, - }; - path_new.pop(); - path_new.push("converted"); - path_new.push(filename); - - // check if exists - if !path_new.exists() { - // convert if it hasn't been converted already - match r.render(&path_local, &args) { - Ok(_) => {} - Err(_e) => { - dbg!("Failed to render {path_local:?}: {}"); - } - } - } - path = path_new; - } - } - }; - - logos.push(LogoData { - name, - path, - }); - - // println!("Name: {}", &tmp.path().display()); - } - - logos - } - - fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec { - let mut filtered: Vec = vec![]; - - let allowed_files = vec![".png", ".jpeg", ".gif", ".svg"]; - 'outer: for logo in existing { - let name_lowercase0 = logo.name.to_ascii_lowercase(); - let name_lowercase = name_lowercase0.to_str().unwrap_or_default(); - let mut allowed = false; - for allowed_type in &allowed_files { - if name_lowercase.ends_with(allowed_type) { - allowed = true; - } - } - if !allowed { - continue; - } - - if !festival_data.current.is_empty() { - // if its a current festival filter based on it - for festival in &festival_data.current { - if name_lowercase.contains(festival) { - filtered.push(logo); - continue 'outer; - } - } - } else { - // else filter using the excluded ones - let mut excluded = false; - for festival in &festival_data.exclusions { - if name_lowercase.contains(festival) { - excluded = true; - } - } - - if !excluded { - filtered.push(logo); - } - } - } - - filtered - } - - async fn logo_set(ctx: &Context, db: &Pool, server: &GuildId, logo_selected: &LogoData) { - // add to teh database - if !logo_set_db(db, logo_selected).await { - // something went wrong - return; - } - - if let Some(logo_path) = logo_selected.path.to_str() { - match CreateAttachment::path(logo_path).await { - Ok(icon) => { - // assuming a `guild` has already been bound - let builder = EditGuild::new().icon(Some(&icon)); - if let Err(e) = server.edit(ctx, builder).await { - dbg!(e); - } - } - Err(e) => { - dbg!(e); - } - } - } - } - - async fn logo_set_db(db: &Pool, logo_selected: &LogoData) -> bool { - let name = match logo_selected.name.to_str() { - None => return false, - Some(x) => x, - }; - let path = match logo_selected.path.to_str() { - None => return false, - Some(x) => x, - }; - - match sqlx::query_as::<_, ServerIcons>( - " - INSERT OR REPLACE INTO server_icons (name, date, path) - VALUES (?1, ?2, ?3) - ", - ) - .bind(name) - .bind(get_now_iso(false)) - .bind(path) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - dbg!(e); - return false; - } - } - true - } -} diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index e11c637..e47b95c 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -1,27 +1,18 @@ pub mod normal { - use crate::{ - common::database::{DataBase, ServerMembersWolves, Servers, Wolves}, - get_now_iso, - }; - use serenity::{ - client::Context, - model::id::{GuildId, RoleId, UserId}, - }; + use crate::common::database::{DataBase, ServerMembersWolves, Servers, Wolves}; + use crate::get_now_iso; + use serenity::client::Context; + use serenity::model::id::{GuildId, RoleId, UserId}; use sqlx::{Pool, Sqlite}; - struct RolesChange { - total: i32, - new: i32, - current_add: i32, - current_rem: i32, - } - pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() }; + let db = db_lock.read().await; + let Servers { server, role_past, @@ -29,12 +20,7 @@ pub mod normal { .. } = server; - let mut roles_set = RolesChange { - total: 0, - new: 0, - current_add: 0, - current_rem: 0, - }; + let mut roles_set = [0, 0, 0]; let mut members = vec![]; for member in get_server_member_bulk(&db, server).await { @@ -46,30 +32,28 @@ pub mod normal { if let Ok(x) = server.members(ctx, None, None).await { for member in x { - // members_changed acts as an override to only deal with the users in it + // members_changed acts as an override to only deal with teh users in it if !members_changed.is_empty() && !members_changed.contains(&member.user.id) { continue; } if members.contains(&member.user.id) { - roles_set.total += 1; - let mut roles = vec![]; if let Some(role) = &role_past { if !member.roles.contains(role) { - roles_set.new += 1; + roles_set[0] += 1; roles.push(role.to_owned()); } } if !member.roles.contains(role_current) { - roles_set.current_add += 1; + roles_set[1] += 1; roles.push(role_current.to_owned()); } if let Err(e) = member.add_roles(ctx, &roles).await { - println!("{e:?}"); + println!("{:?}", e); } } else { // old and never @@ -81,16 +65,16 @@ pub mod normal { } if member.roles.contains(role_current) { - roles_set.current_rem += 1; - // if they're not a current member and have the role then remove it + roles_set[2] += 1; + // if theya re not a current member and have the role then remove it if let Err(e) = member.remove_role(ctx, role_current).await { - println!("{e:?}"); + println!("{:?}", e); } } } for role in remove_roles.iter().flatten() { if let Err(e) = member.remove_role(ctx, role).await { - println!("{e:?}"); + println!("{:?}", e); } } } @@ -99,14 +83,7 @@ pub mod normal { set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; // small bit of logging to note changes over time - println!( - "{:?} Total: {} Changes: New: +{}, Current: +{}/-{}", - server.get(), - roles_set.total, - roles_set.new, - roles_set.current_add, - roles_set.current_rem - ); + println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.get(), roles_set[0], roles_set[1], roles_set[2]); } pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { @@ -146,7 +123,7 @@ pub mod normal { Ok(_) => {} Err(e) => { println!("Failure to insert into {}", server.get()); - println!("{e:?}"); + println!("{:?}", e); } } } @@ -154,29 +131,28 @@ pub mod normal { // for updating committee members pub mod committee { - use crate::{ - common::{ - database::{get_channel_from_row, get_role_from_row, DataBase, Wolves}, - wolves::committees::Committees, - }, - Config, - }; - use serde::{Deserialize, Serialize}; - use serenity::{ - all::EditRole, - builder::CreateChannel, - client::Context, - model::{channel::ChannelType, guild::Member, id::ChannelId, prelude::RoleId}, - }; - use sqlx::{sqlite::SqliteRow, Error, FromRow, Pool, Row, Sqlite}; + use crate::common::database::{DataBase, Wolves}; + use crate::common::wolves::committees::Committees; + use crate::Config; + use serenity::all::EditRole; + use serenity::builder::CreateChannel; + use serenity::client::Context; + use serenity::model::channel::ChannelType; + use serenity::model::guild::Member; + use serenity::model::id::ChannelId; + use serenity::model::prelude::RoleId; + use sqlx::{Pool, Sqlite}; use std::collections::HashMap; + use std::sync::Arc; - pub async fn check_committee(ctx: &Context) { - let db = { + pub async fn check_committee(ctx: Arc) { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; + let db = db_lock.read().await; + let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() @@ -188,48 +164,55 @@ pub mod committee { // because to use it to update a single user we need to pre-get the members of teh server let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); - update_committees(&db, ctx, &config_global, &mut members).await; + update_committees(&db, &ctx, &config_global, &mut members).await; } /** - This function can take a Vec of members (or just one) and gives them the appropriate roles on teh committee server + This function can take a vec of members (or just one) and gives tehm the appropiate roles on teh committee server */ pub async fn update_committees(db: &Pool, ctx: &Context, config: &Config, members: &mut Vec) { let server = config.committee_server; - let committee_member = config.committee_role; - let committees = match get_committees(db).await { - None => { - return; - } - Some(x) => x, - }; - let categories = config.committee_category.clone(); + let committee_member = RoleId::new(1226602779968274573); + let committees = get_committees(db).await; + let categories = vec![ + // C&S Chats 1 + ChannelId::new(1226606560973815839), + // C&S Chats 2 + ChannelId::new(1341457244973305927), + // C&S Chats 3 + ChannelId::new(1341457509717639279), + ]; // information about the server - let mut roles_db = HashMap::new(); - for role in db_roles_get(db).await { - roles_db.insert( - role.id_wolves, - CommitteeRoles { - id_wolves: role.id_wolves, - id_role: role.id_role, - id_channel: role.id_channel, - name_role: role.name_role, - name_channel: role.name_channel, - // always start at 0 - count: 0, - }, - ); + let roles = server.roles(&ctx).await.unwrap_or_default(); + let channels = server.channels(&ctx).await.unwrap_or_default(); + + // make a hashmap of the nameof roles to quickly get them out again + let mut roles_name = HashMap::new(); + for role in roles.values() { + roles_name.insert(role.name.to_owned(), role.to_owned()); } - let mut channels = server.channels(&ctx).await.unwrap_or_default(); + let mut channels_name = HashMap::new(); + for channel in channels.values() { + // we only care about teh channels in teh category + if let Some(x) = channel.parent_id { + for category in &categories { + if x.eq(category) { + channels_name.insert(channel.name.to_owned(), channel.to_owned()); + } + } + } + } - // a map of users and the roles they are going to be getting + // a map of users and the roles they are goign to be getting let mut users_roles = HashMap::new(); - let mut re_order = false; - // we need to create roles and channels if they don't already exist + // a list of all the roles that can be removed from folks who should have them + let mut committee_roles = vec![committee_member]; + let mut category_index = 0; + let mut i = 0; loop { if i >= committees.len() { @@ -237,17 +220,24 @@ pub mod committee { } let committee = &committees[i]; - // if a club/soc ever changes their name - if let Some(x) = roles_db.get_mut(&committee.id) { - committee.name_full.clone_into(&mut x.name_role); - committee.name_profile.clone_into(&mut x.name_channel); - } + // get the role for this committee/club/soc + let role = match roles_name.get(&committee.name_full) { + Some(x) => Some(x.to_owned()), + None => { + // create teh role if it does not exist + match server + .create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true)) + .await + { + Ok(x) => Some(x), + Err(_) => None, + } + } + }; - // handle new clubs/socs - if let std::collections::hash_map::Entry::Vacant(e) = roles_db.entry(committee.id) { - // create channel - // channel is first as the categories can only contain 50 channels - let channel = match server + // create teh channel if it does nto exist + if !channels_name.contains_key(&committee.name_profile) { + match server .create_channel( &ctx, CreateChannel::new(&committee.name_profile) @@ -257,93 +247,59 @@ pub mod committee { .await { Ok(x) => { - println!("Created channel: {}", &committee.name_profile); + // update teh channels name list + channels_name.insert(x.name.to_owned(), x.to_owned()); - x.id + println!("Created channel: {}", &committee.name_profile); } Err(x) => { let tmp = x.to_string(); - dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)")); + if x.to_string().contains("Maximum number of channels in category reached (50)") { category_index += 1; continue; } - ChannelId::new(1) + dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)")); } - }; + } + }; - // create role - let role = match server - .create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true)) - .await - { - Ok(x) => x.id, - Err(_) => RoleId::new(1), - }; + // so if the role exists + if let Some(r) = role { + committee_roles.push(r.id); - let tmp = CommitteeRoles { - id_wolves: committee.id, - id_role: role, - id_channel: channel, - name_role: committee.name_full.to_owned(), - name_channel: committee.name_profile.to_owned(), - count: 0, - }; - - // save it to the db in case of crash or error - db_role_set(db, &tmp).await; - - // insert it into teh local cache - e.insert(tmp); - - re_order = true; + for id_wolves in &committee.committee { + // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) + if let Some(x) = get_server_member_discord(db, id_wolves).await { + if let Some(member_tmp) = x.discord { + let values = users_roles.entry(member_tmp).or_insert(vec![committee_member]); + values.push(r.id); + } + } + } } i += 1; } - for committee in &committees { - let r = if let Some(x) = roles_db.get(&committee.id) { - x.id_role - } else { - continue; - }; - - for id_wolves in &committee.committee { - // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) - if let Some(x) = get_server_member_discord(db, id_wolves).await { - if let Some(member_tmp) = x.discord { - if server.member(ctx, &member_tmp).await.is_ok() { - let values = users_roles.entry(member_tmp).or_insert(vec![]); - values.push(r); - - if let Some(x) = roles_db.get_mut(&committee.id) { - x.count += 1; - } - } - } - } - } - } - // now we have a map of all users that should get roles time to go through all the folks on teh server for member in members { - // if member.user.id != 136522490632601600 { - // continue; - // } - // let roles_current = member.roles(ctx).unwrap_or_default(); let roles_required = match users_roles.get(&member.user.id) { None => { vec![] } - Some(x) => x.to_owned(), + Some(x) => { + let mut tmp = x.to_owned(); + if !tmp.is_empty() { + tmp.push(committee_member); + } + tmp + } }; - let on_committee = !roles_required.is_empty(); - let mut roles_rem = vec![]; let mut roles_add = vec![]; // get a list of all the roles to remove from someone @@ -352,28 +308,13 @@ pub mod committee { for role in &roles_current { roles_current_id.push(role.id.to_owned()); if !roles_required.contains(&role.id) { - if role.id == committee_member && on_committee { - continue; - } - roles_rem.push(role.id.to_owned()); } } - let has_committee_role = roles_current_id.contains(&committee_member); - - if on_committee && !has_committee_role { - // if there are committee roles then give the general purpose role - roles_add.push(committee_member); - } - if !on_committee && has_committee_role { - roles_rem.push(committee_member); - } - if !roles_required.is_empty() { - if let Some(x) = roles_db.get_mut(&0) { - x.count += 1; - } + // if there are committee roles then give the general purporse role + roles_add.push(committee_member); } for role in &roles_required { @@ -389,118 +330,40 @@ pub mod committee { if !roles_add.is_empty() { // these roles are flavor roles, only there to make folks mentionable member.add_roles(&ctx, &roles_add).await.unwrap_or_default(); + } else { + member.remove_roles(&ctx, &[committee_member]).await.unwrap_or_default(); } } - let mut channel_names = vec![]; - let mut positions = vec![]; - for role in roles_db.values() { - // save these to db - db_role_set(db, role).await; + // finally re-order teh channels to make them visually apealing + let mut channel_names = channels_name.clone().into_keys().collect::>(); + channel_names.sort(); - if re_order { - let channel_id = role.id_channel.to_owned(); - if let Some(channel) = channels.get_mut(&channel_id) { - // record the position of each of teh C&S channels - positions.push(channel.position); - - // pull out teh channel names - channel_names.push((role.name_channel.to_owned(), channel_id)); + // get a list of all teh new positions + let mut new_positions = vec![]; + for (i, name) in channel_names.iter().enumerate() { + if let Some(channel) = channels_name.get_mut(name) { + let position_new = i as u64; + if position_new != channel.position as u64 { + new_positions.push((channel.id.to_owned(), position_new)); } } } - if re_order { - // sort by the position and name - positions.sort(); - channel_names.sort_by_key(|(name, _)| name.to_owned()); - - let mut new_positions = vec![]; - for (i, (_, id)) in channel_names.iter().enumerate() { - new_positions.push((id.to_owned(), positions[i] as u64)); - } - - if !new_positions.is_empty() { - match server.reorder_channels(&ctx, new_positions).await { - Ok(_) => { - println!("Successfully re-orderd the committee category"); - } - Err(e) => { - dbg!("Failed to re-order ", e); - } + if !new_positions.is_empty() { + match server.reorder_channels(&ctx, new_positions).await { + Ok(_) => { + println!("Successfully re-orderd the committee category"); + } + Err(e) => { + dbg!("Failed to re-order ", e); } } } } - #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct CommitteeRoles { - id_wolves: i64, - pub id_role: RoleId, - pub id_channel: ChannelId, - pub name_role: String, - pub name_channel: String, - pub count: i64, - } - - impl<'r> FromRow<'r, SqliteRow> for CommitteeRoles { - fn from_row(row: &'r SqliteRow) -> Result { - Ok(Self { - id_wolves: row.try_get("id_wolves")?, - id_role: get_role_from_row(row, "id_role"), - id_channel: get_channel_from_row(row, "id_channel"), - name_role: row.try_get("name_role")?, - name_channel: row.try_get("name_channel")?, - count: row.try_get("count")?, - }) - } - } - - async fn db_role_set(db: &Pool, role: &CommitteeRoles) { - // expiry - match sqlx::query_as::<_, CommitteeRoles>( - " - INSERT INTO committee_roles (id_wolves, id_role, id_channel, name_role, name_channel, count) - VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT(id_wolves) DO UPDATE SET name_role = $4, name_channel = $5, count = $6 - ", - ) - .bind(role.id_wolves) - .bind(role.id_role.get() as i64) - .bind(role.id_channel.get() as i64) - .bind(&role.name_role) - .bind(&role.name_channel) - .bind(role.count) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into Wolves {role:?}"); - println!("{e:?}"); - } - } - } - - pub async fn db_roles_get(db: &Pool) -> Vec { - // expiry - sqlx::query_as::<_, CommitteeRoles>( - " - SELECT * - FROM committee_roles - ", - ) - .fetch_all(db) - .await - .unwrap_or_else(|e| { - println!("Failure to get Roles from committee_roles"); - println!("{e:?}"); - vec![] - }) - } - - pub async fn get_committees(db: &Pool) -> Option> { - match sqlx::query_as::<_, Committees>( + async fn get_committees(db: &Pool) -> Vec { + sqlx::query_as::<_, Committees>( r#" SELECT * FROM committees @@ -508,13 +371,10 @@ pub mod committee { ) .fetch_all(db) .await - { - Ok(x) => Some(x), - Err(e) => { - dbg!(e); - None - } - } + .unwrap_or_else(|e| { + dbg!(e); + vec![] + }) } async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 3744558..ea64699 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -38,8 +38,8 @@ async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { { Ok(_) => {} Err(e) => { - println!("Failure to insert into Wolves {user:?}"); - println!("{e:?}"); + println!("Failure to insert into Wolves {:?}", user); + println!("{:?}", e); } } } @@ -48,14 +48,12 @@ async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { This is getting data for Clubs and Socs */ pub mod cns { - use crate::{ - common::{ - database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers}, - wolves::{add_users_wolves, WolvesResultUserMin}, - }, - Config, - }; - use serenity::{client::Context, model::id::GuildId}; + use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers}; + use crate::common::set_roles::normal::update_server; + use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; + use crate::Config; + use serenity::client::Context; + use serenity::model::id::GuildId; use sqlx::{Pool, Sqlite}; use std::collections::BTreeMap; @@ -69,10 +67,11 @@ pub mod cns { } pub async fn get_wolves(ctx: &Context) { - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() }; + let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; @@ -88,7 +87,7 @@ pub mod cns { server, // this is the unique api key for each club/soc wolves_api, - wolves_id, + server_name, .. } = &server_config; // dbg!(&server_config); @@ -97,11 +96,12 @@ pub mod cns { let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::>(); // list of users that need to be updated for this server + let mut user_to_update = vec![]; let mut server_name_tmp = None; for user in wolves.get_members(wolves_api).await { // dbg!(&user.committee); if server_name_tmp.is_none() { - server_name_tmp = Some(user.committee_id); + server_name_tmp = Some(user.committee.to_owned()); } let id = user.member_id.parse::().unwrap_or_default(); match existing.get(&(id as i64)) { @@ -115,28 +115,35 @@ pub mod cns { add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; if old.expiry != user.expiry { add_users_server_members(&db, server, &user).await; + + if let Some(discord_id) = old.discord { + user_to_update.push(discord_id); + } } } } } - if let Some(cs_id) = server_name_tmp { - if &cs_id != wolves_id { - set_server_member(&db, server, cs_id).await; + if let Some(name) = server_name_tmp { + if &name != server_name { + set_server_member(&db, server, &name).await; } } + if !user_to_update.is_empty() { + update_server(ctx, &server_config, &[], &user_to_update).await; + } } } - async fn set_server_member(db: &Pool, server: &GuildId, wolves_id: i64) { + async fn set_server_member(db: &Pool, server: &GuildId, name: &str) { match sqlx::query_as::<_, Servers>( " UPDATE servers - SET wolves_id = ? + SET server_name = ? WHERE server = ? ", ) - .bind(wolves_id) + .bind(name) .bind(server.get() as i64) .fetch_optional(db) .await @@ -144,7 +151,7 @@ pub mod cns { Ok(_) => {} Err(e) => { println!("Failure to set server name {}", server.get()); - println!("{e:?}"); + println!("{:?}", e); } } } @@ -183,7 +190,7 @@ pub mod cns { Ok(_) => {} Err(e) => { println!("Failure to insert into ServerMembers {} {:?}", server.get(), user); - println!("{e:?}"); + println!("{:?}", e); } } } @@ -193,7 +200,8 @@ pub mod cns { Get and store the data on C&S committees */ pub mod committees { - use crate::{common::database::DataBase, Config}; + use crate::common::database::DataBase; + use crate::Config; use serenity::client::Context; use sqlx::{Pool, Sqlite}; @@ -223,10 +231,11 @@ pub mod committees { } pub async fn get_cns(ctx: &Context) { - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() }; + let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; @@ -261,8 +270,8 @@ pub mod committees { { Ok(_) => {} Err(e) => { - println!("Failure to insert into Committees {committee:?}"); - println!("{e:?}"); + println!("Failure to insert into Committees {:?}", committee); + println!("{:?}", e); } } } diff --git a/src/lib.rs b/src/lib.rs index 1a6afb1..0152205 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,10 @@ pub mod common; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distr::Alphanumeric, rng, Rng}; -use serenity::{ - model::id::{ChannelId, GuildId, RoleId}, - prelude::TypeMapKey, -}; +use serenity::all::CommandInteraction; +use serenity::client::Context; +use serenity::model::id::{ChannelId, GuildId, RoleId}; +use serenity::prelude::TypeMapKey; use std::{env, sync::Arc}; use tokio::sync::RwLock; @@ -34,10 +34,7 @@ pub struct Config { // discord server for committee pub committee_server: GuildId, pub committee_role: RoleId, - pub committee_category: Vec, - - // items pertaining to CompSoc only - pub compsoc_server: GuildId, + pub committee_category: ChannelId, } impl TypeMapKey for Config { type Value = Arc>; @@ -62,8 +59,7 @@ pub fn get_config() -> Config { wolves_api: "".to_string(), committee_server: GuildId::new(1), committee_role: RoleId::new(1), - committee_category: vec![], - compsoc_server: GuildId::new(1), + committee_category: ChannelId::new(1), }; if let Ok(x) = env::var("DATABASE_HOME") { @@ -105,22 +101,14 @@ pub fn get_config() -> Config { config.committee_server = GuildId::new(x); } } - if let Ok(x) = env::var("COMMITTEE_ROLE") { + if let Ok(x) = env::var("COMMITTEE_DISCORD") { if let Ok(x) = x.trim().parse::() { config.committee_role = RoleId::new(x); } } if let Ok(x) = env::var("COMMITTEE_CATEGORY") { - for part in x.split(',') { - if let Ok(x) = part.trim().parse::() { - config.committee_category.push(ChannelId::new(x)); - } - } - } - - if let Ok(x) = env::var("COMPSOC_DISCORD") { if let Ok(x) = x.trim().parse::() { - config.compsoc_server = GuildId::new(x); + config.committee_category = ChannelId::new(x); } } @@ -139,3 +127,41 @@ pub fn get_now_iso(short: bool) -> String { pub fn random_string(len: usize) -> String { rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } + +/** + For any time ye need to check if a user who calls a command has admin privlages +*/ +pub async fn is_admin(command: &CommandInteraction, ctx: &Context) -> Option { + let mut admin = false; + + let g_id = match command.guild_id { + None => return Some("Not in a server".to_string()), + Some(x) => x, + }; + + let roles_server = g_id.roles(&ctx.http).await.unwrap_or_default(); + + if let Ok(member) = g_id.member(&ctx.http, command.user.id).await { + if let Some(permissions) = member.permissions { + if permissions.administrator() { + admin = true; + } + } + + for role_id in member.roles { + if admin { + break; + } + if let Some(role) = roles_server.get(&role_id) { + if role.permissions.administrator() { + admin = true; + } + } + } + } + if !admin { + Some("Administrator permission required".to_string()) + } else { + None + } +} diff --git a/src/main.rs b/src/main.rs index 8dff675..36316d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,21 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; +use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildMemberUpdateEvent, Interaction}; +use serenity::model::guild::Member; use serenity::{ - all::{Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, Interaction}, async_trait, client::{Context, EventHandler}, - gateway::{ActivityData, ChunkGuildFilter}, model::{ - event::GuildMemberUpdateEvent, gateway::{GatewayIntents, Ready}, - guild::Member, - id::GuildId, user::OnlineStatus, }, Client, }; -use skynet_discord_bot::{ - common::{ - database::{db_init, get_server_config, get_server_member, DataBase}, - set_roles::committee::update_committees, - wolves::committees::Committees, - }, - get_config, Config, -}; +use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase}; +use skynet_discord_bot::common::set_roles::committee::update_committees; +use skynet_discord_bot::common::wolves::committees::Committees; +use skynet_discord_bot::{get_config, Config}; use sqlx::{Pool, Sqlite}; use std::sync::Arc; use tokio::sync::RwLock; @@ -31,21 +24,15 @@ struct Handler; #[async_trait] impl EventHandler for Handler { - // this caches members of all servers teh bot is in - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); - } - // handles previously linked accounts joining the server async fn guild_member_addition(&self, ctx: Context, new_member: Member) { - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; + let db = db_lock.read().await; + let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() @@ -53,8 +40,7 @@ impl EventHandler for Handler { let config_global = config_lock.read().await; // committee server takes priority - let committee_server = config_global.committee_server; - if new_member.guild_id.get() == committee_server.get() { + if new_member.guild_id.eq(&config_global.committee_server) { let mut member = vec![new_member.clone()]; update_committees(&db, &ctx, &config_global, &mut member).await; return; @@ -79,16 +65,16 @@ impl EventHandler for Handler { } if let Err(e) = new_member.add_roles(&ctx, &roles).await { - println!("{e:?}"); + println!("{:?}", e); } } else { - let tmp = get_committee(&db, config_server.wolves_id).await; + let tmp = get_committee(&db, &config_server.server_name).await; if !tmp.is_empty() { let committee = &tmp[0]; let msg = format!( r#" Welcome {} to the {} server! -Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/wolves link email_here`` with the email associated with your wolves account, to get full access. +Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/link_wolves`` to get full access. "#, new_member.display_name(), committee.name_full, @@ -107,12 +93,14 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use // handles role updates async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Option, _: GuildMemberUpdateEvent) { // get config/db - let db = { + let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; - // check if the role changed is part of the ones for this server + let db = db_lock.read().await; + + // check if the role changed is part of the oens for this server if let Some(x) = new_data { on_role_change(&db, &ctx, x).await; } @@ -122,54 +110,24 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use println!("[Main] {} is connected!", ready.user.name); ctx.set_presence(Some(ActivityData::playing("with humanity's fate")), OnlineStatus::Online); - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - match Command::set_global_commands( &ctx.http, vec![ - commands::wolves::register(), - commands::committee::register(), + commands::add_server::register(), + commands::role_adder::edit::register(), + commands::link_email::link::register(), + commands::link_email::verify::register(), commands::minecraft::server::add::register(), commands::minecraft::server::list::register(), commands::minecraft::server::delete::register(), + commands::minecraft::user::add::register(), ], ) .await { Ok(_) => {} Err(e) => { - println!("{e:?}") - } - } - - // Inter-Committee server - match config.committee_server.set_commands(&ctx.http, vec![commands::count::committee::register()]).await { - Ok(_) => {} - Err(e) => { - println!("{e:?}") - } - } - - // CompSoc Server - match config - .compsoc_server - .set_commands( - &ctx.http, - vec![ - // commands just for the CompSoc server - commands::count::servers::register(), - commands::server_icon::user::register(), - ], - ) - .await - { - Ok(_) => {} - Err(e) => { - println!("{e:?}") + println!("{:?}", e) } } } @@ -177,100 +135,38 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use async fn interaction_create(&self, ctx: Context, interaction: Interaction) { if let Interaction::Command(command) = interaction { let _ = command.defer_ephemeral(&ctx.http).await; - // println!("Received command interaction: {:#?}", command); + //println!("Received command interaction: {:#?}", command); let content = match command.data.name.as_str() { // user commands - "wolves" => match command.data.options.first() { - None => "Invalid Command".to_string(), - Some(x) => match x.name.as_str() { - "link" => commands::wolves::link::run(&command, &ctx).await, - "verify" => commands::wolves::verify::run(&command, &ctx).await, - "unlink" => commands::wolves::unlink::run(&command, &ctx).await, - "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, - "docs" => commands::wolves::link_docs::users::run(&command, &ctx).await, - // "link" => commands::count::servers::run(&command, &ctx).await, - &_ => format!("not implemented :( wolves {}", x.name.as_str()), - }, - }, - + "link_wolves" => commands::link_email::link::run(&command, &ctx).await, + "verify" => commands::link_email::verify::run(&command, &ctx).await, + "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, // admin commands - "committee" => match command.data.options.first() { - None => "Invalid Command".to_string(), - Some(x) => match x.name.as_str() { - "add" => commands::add_server::run(&command, &ctx).await, - "roles_adder" => commands::role_adder::edit::run(&command, &ctx).await, - "icon" => match &x.value { - CommandDataOptionValue::SubCommandGroup(y) => match y.first() { - None => "error".to_string(), - Some(z) => match z.name.as_str() { - "change" => commands::server_icon::admin::change::run(&command, &ctx).await, - &_ => format!("not implemented :( count {}", x.name.as_str()), - }, - }, - _ => { - format!("not implemented :( committee {}", x.name.as_str()) - } - }, - // TODO: move teh minecraft commands in here as a subgroup - // "link" => commands::count::servers::run(&command, &ctx).await, - &_ => format!("not implemented :( committee {}", x.name.as_str()), - }, - }, + "add" => commands::add_server::run(&command, &ctx).await, + "roles_adder" => commands::role_adder::edit::run(&command, &ctx).await, "minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await, "minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await, "minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await, - // sub command - "count" => match command.data.options.first() { - None => "Invalid Command".to_string(), - Some(x) => match x.name.as_str() { - "committee" => commands::count::committee::run(&command, &ctx).await, - "servers" => commands::count::servers::run(&command, &ctx).await, - &_ => format!("not implemented :( count {}", x.name.as_str()), - }, - }, - - "icon" => match command.data.options.first() { - None => "Invalid Command".to_string(), - Some(x) => match x.name.as_str() { - "current" => { - let result = match &x.value { - CommandDataOptionValue::SubCommandGroup(y) => match y.first() { - None => "error".to_string(), - Some(z) => match z.name.as_str() { - "icon" => commands::server_icon::user::current::icon::run(&command, &ctx).await, - "festival" => commands::server_icon::user::current::festival::run(&command, &ctx).await, - &_ => format!("not implemented :( count {}", x.name.as_str()), - }, - }, - &_ => format!("not implemented :( {}", command.data.name.as_str()), - }; - - result - } - "stats" => commands::server_icon::user::stats::run(&command, &ctx).await, - &_ => format!("not implemented :( count {}", x.name.as_str()), - }, - }, - _ => format!("not implemented :( {}", command.data.name.as_str()), + _ => "not implemented :(".to_string(), }; if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await { - println!("Cannot respond to slash command: {why}"); + println!("Cannot respond to slash command: {}", why); } } } } -async fn get_committee(db: &Pool, wolves_id: i64) -> Vec { +async fn get_committee(db: &Pool, committee: &str) -> Vec { sqlx::query_as::<_, Committees>( r#" SELECT * FROM committees - WHERE id = ? + WHERE name_plain = ? "#, ) - .bind(wolves_id) + .bind(committee) .fetch_all(db) .await .unwrap_or_default() @@ -291,8 +187,7 @@ async fn main() { let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; // Build our client. let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler) - .cache_settings(serenity::cache::Settings::default()) + .event_handler(Handler {}) .await .expect("Error creating client"); @@ -300,7 +195,7 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(db)); + data.insert::(Arc::new(RwLock::new(db))); } // Finally, start a single shard, and start listening to events. @@ -308,6 +203,6 @@ async fn main() { // Shards will automatically attempt to reconnect, and will perform // exponential backoff until it reconnects. if let Err(why) = client.start().await { - println!("Client error: {why:?}"); + println!("Client error: {:?}", why); } }