diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index 28f49f6..984c5d6 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -19,6 +19,10 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3 + with: + repository: ${{ gitea.repository }} + ref_name: ${{ gitea.ref_name }} - run: nix build .#fmt --verbose # clippy is incredibly useful for making yer code better @@ -30,6 +34,10 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3 + with: + repository: ${{ gitea.repository }} + ref_name: ${{ gitea.ref_name }} - run: nix build .#clippy --verbose build: @@ -39,6 +47,10 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3 + with: + repository: ${{ gitea.repository }} + ref_name: ${{ gitea.ref_name }} - name: "Build it locally" run: nix build --verbose @@ -49,7 +61,7 @@ jobs: needs: [ build ] steps: - name: "Deploy to Skynet" - uses: https://forgejo.skynet.ie/Skynet/actions-deploy-to-skynet@v2 + uses: https://forgejo.skynet.ie/Skynet/actions/deploy@v3 with: input: 'skynet_discord_bot' token: ${{ secrets.API_TOKEN_FORGEJO }} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6c7adef --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Git config here +* text eol=lf + +############################################# +# Git lfs stuff +# Documents +*.pdf filter=lfs diff=lfs merge=lfs -text +*.doc filter=lfs diff=lfs merge=lfs -text +*.docx filter=lfs diff=lfs merge=lfs -text +# Excel +*.xls filter=lfs diff=lfs merge=lfs -text +*.xlsx filter=lfs diff=lfs merge=lfs -text +*.xlsm filter=lfs diff=lfs merge=lfs -text +# Powerpoints +*.ppt filter=lfs diff=lfs merge=lfs -text +*.pptx filter=lfs diff=lfs merge=lfs -text +*.ppsx filter=lfs diff=lfs merge=lfs -text +# Images +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +# Video +*.mkv filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.wmv filter=lfs diff=lfs merge=lfs -text +# Misc +*.zip filter=lfs diff=lfs merge=lfs -text +# ET4011 +*.cbe filter=lfs diff=lfs merge=lfs -text +*.pbs filter=lfs diff=lfs merge=lfs -text +# Open/Libre office +# from https://www.libreoffice.org/discover/what-is-opendocument/ +*.odt filter=lfs diff=lfs merge=lfs -text +*.ods filter=lfs diff=lfs merge=lfs -text +*.odp filter=lfs diff=lfs merge=lfs -text +*.odg filter=lfs diff=lfs merge=lfs -text +# QT +*.ui filter=lfs diff=lfs merge=lfs -text diff --git a/Cargo.lock b/Cargo.lock index af719e5..23bef46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "async-channel" @@ -143,7 +143,7 @@ dependencies = [ "async-task", "concurrent-queue", "fastrand 2.1.1", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "slab", ] @@ -158,21 +158,21 @@ dependencies = [ "async-io", "async-lock", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "once_cell", ] [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "parking", "polling", "rustix", @@ -206,7 +206,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "gloo-timers", "kv-log-macro", "log", @@ -381,7 +381,7 @@ dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "piper", ] @@ -586,9 +586,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.46" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" dependencies = [ "curl-sys", "libc", @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.75+curl-8.10.0" +version = "0.4.78+curl-8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4fd752d337342e4314717c0d9b6586b059a120c80029ebe4d49b11fec7875e" +checksum = "8eec768341c5c7789611ae51cf6c459099f22e64a5d5d0ce4892434e33821eaf" dependencies = [ "cc", "libc", @@ -923,9 +923,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ "fastrand 2.1.1", "futures-core", @@ -1056,9 +1056,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes 1.7.1", @@ -1307,14 +1307,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes 1.7.1", "futures-channel", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -1347,9 +1347,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.16", + "rustls 0.23.18", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -1364,7 +1364,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes 1.7.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -1383,7 +1383,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.5.1", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -1907,18 +1907,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", @@ -1977,9 +1977,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", @@ -2205,11 +2205,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", @@ -2225,7 +2225,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "system-configuration 0.6.1", "tokio", "tokio-native-tls", @@ -2341,9 +2341,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" dependencies = [ "once_cell", "rustls-pki-types", @@ -2628,15 +2628,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - [[package]] name = "signature" version = "2.2.0" @@ -2657,7 +2648,6 @@ dependencies = [ "maud", "rand 0.8.5", "serde", - "serde_json", "serenity", "sqlx", "surf", @@ -3076,9 +3066,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -3252,9 +3242,7 @@ dependencies = [ "bytes 1.7.1", "libc", "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2 0.5.7", "tokio-macros", "windows-sys 0.52.0", @@ -3308,7 +3296,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.16", + "rustls 0.23.18", "rustls-pki-types", "tokio", ] @@ -3533,9 +3521,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "value-bag" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" [[package]] name = "vcpkg" @@ -3929,7 +3917,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#eee6a76c695a36f1fe220fdeafd5a43757e50527" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#867778128c1ef580ebfded7808bbd4e86f22903b" dependencies = [ "reqwest 0.12.9", "serde", diff --git a/Cargo.toml b/Cargo.toml index 92fa6c5..19cb9ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "full"] } # TODO: move off of unstable wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git", features = ["unstable"]} +# wolves api +wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git" } + # to make the http requests surf = "2.3.2" @@ -42,4 +45,4 @@ chrono = "0.4.26" lettre = "0.10.4" maud = "0.25.0" -serde = "1.0.188" \ No newline at end of file +serde = "1.0" \ No newline at end of file diff --git a/README.md b/README.md index 068979a..01543e6 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,10 @@ # Skynet Discord Bot -This bots core purpose is to give members roles based on their status on <ulwolves.ie>. -It uses an api key provided by wolves to get member lists. +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 is hosted by the Computer Society on Skynet (computer cluster). -Users are able to link their wolves account to the bot and that works across discord servers. -For example is a user links on the CompSoc Discord then they will also get their roles (automagically) on Games Dev if they are a member there. +## Documentation +We have split up the documentation into different segments depending on who the user is. -## Commands - Admin - -You need admin access to run any of the commands in this section. -Either the server owner or a suer with the ``Administrator`` permission - -### Getting the Skynet Discord bot -1. Email ``keith@assurememberships.com`` from committee email and say ye want an api key for ``193.1.99.74`` -2. Create a role for current members (maybe call it ``current-member`` ?) -3. (Optional) create a role for all past and current members (ye can use the existing ``member`` role for this, ) -4. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot -5. Make sure the bot role ``@skynet`` is above these two roles (so it can manage them) -6. Make sure that you have a role that gives ye administrator powers -7. Use the command ``/add`` and insert the api key, role current and role all (desktop recommended) - -The reason for both roles is ye have one for active members while the second is for all current and past members. - -### Minecraft -The bot is able to manage the whitelist of a Minecraft server managed by the Computer Society. -Talk to us to get a server. - -#### Add -This links a minecraft server with your club/society. - -``/minecraft_add SERVER_ID`` - - -#### List -List the servers linked to your club/society. -It is possible to have more than one minecraft server - -``/minecraft_list`` - -#### Delete -This unlinks a minecraft server from your club/society. - -``/minecraft_delete SERVER_ID`` - -## Commands - User - -### Setup -* Start the process using ``/link_wolves WOLVES_EMAIL`` - * The email that is in the Contact Email here: <https://ulwolves.ie/memberships/profile> -* An email will be sent to them that they need to verify using ``/verify CODE`` -* This will only have to be done once. - -* If the user is an active member on wolves - * If they are in any servers with teh Skynet Bot - * They will get relevant roles. - * If they Join a server with teh bot enabled. - * They will be granted the roles automatically -* If the user is **not** an active member on wolves. - * If they have no Roles - * No change - * If they have Past Member Role - * No change - * If they have both Roles - * The current-member role will be removed from them - * Past Member role will remain unchanged - -### Minecraft -Users can link their Minecraft username to grant them access to any servers where teh whitelist is managed by teh bot. - -``/link_minecraft MINECRAFT_USERNAME`` +* [Committees](./doc/Committee.md) +* [Member](./doc/User.md) \ No newline at end of file diff --git a/db/migrations/7_minecraft-bedrock.sql b/db/migrations/7_minecraft-bedrock.sql new file mode 100644 index 0000000..71d8976 --- /dev/null +++ b/db/migrations/7_minecraft-bedrock.sql @@ -0,0 +1,3 @@ + +-- Using this col we will be able to see if someone has a bedrock account (as well as a java one) +ALTER TABLE wolves ADD COLUMN minecraft_uid TEXT; \ No newline at end of file diff --git a/doc/Committee.md b/doc/Committee.md new file mode 100644 index 0000000..d53be13 --- /dev/null +++ b/doc/Committee.md @@ -0,0 +1,75 @@ +# Skynet Discord Bot +This bots core purpose is to give members roles based on their status on <https://ulwolves.ie>. +It uses an api key provided by wolves to get member lists. + +Users are able to link their wolves account to the bot and that works across discord servers. +For example is a user links on the CompSoc Discord then they will also get their roles (automagically) on Games Dev if they are a member there. + +## Setup - Committee +You need admin access to run any of the commands in this section. +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. + +1. Email ``keith@assurememberships.com`` from committee email and say you want an ``api_key`` for ``193.1.99.74`` + * The committee email is the one here: <https://cp.ulwolves.ie/mailbox/> + * This may take up to a week to get the key. + +### Setup Server +The Bot reason for existing is being able to give members Roles. +So we have to create those. + +1. Create a role for Current Members. + * You can call it whatever you want. + * ``member-current`` is a good choice. + * This should be a new role +2. **Optional**: you can create a role that is given to folks who were but no longer a member. + * ``member`` would be a good choice for this + * If you have an existing member role this is also a good fit. + +The reason for both roles is ye have one for active members while the second is for all current and past members. + +### Invite Bot +1. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot +2. Make sure the bot role ``@skynet`` is above these two roles created in the previous step + * This is so it can manage the roles (give and remove them from users) + +### 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 ``Administrator`` permission to be able to do this. + +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. +5. ``bot_channel`` is a channel that folks are recommended to use the bot. + * You can have it so folks cannot see message history +6. ``server_name`` For example ``UL Computer Society`` + * Will be removed in the future +7. ``wolves_link`` for example <https://ulwolves.ie/society/computer> + * Will be removed in the future + +At this point the bot is set up and no further action is required. + +### Minecraft +The bot is able to manage the whitelist of a Minecraft server managed by the Computer Society. +Talk to us to get a server. + +#### Add +This links a minecraft server with your club/society. + +``/minecraft_add SERVER_ID`` + + +#### List +List the servers linked to your club/society. +It is possible to have more than one minecraft server + +``/minecraft_list`` + +#### Delete +This unlinks a minecraft server from your club/society. + +``/minecraft_delete SERVER_ID`` diff --git a/doc/User.md b/doc/User.md new file mode 100644 index 0000000..fec6abe --- /dev/null +++ b/doc/User.md @@ -0,0 +1,26 @@ +# Skynet Discord Bot +The Skynet bot is designed to make it easy to verify that you are a member of a Club/Society. +The bot will be able to give you member roles for any partnered servers. +It also provides secondary manifests such as granting access to minecraft servers managed by teh Computer Society. + +## Setup +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 ``/link_wolves YOUR_WOLVES_CONTACT_EMAIL`` + <img src="../media/setup_user_01.png" alt="link process start" width="50%" height="50%"> + * Your ``YOUR_WOLVES_CONTACT_EMAIL`` is the email in the Contact Email here: <https://ulwolves.ie/memberships/profile> + * This is most likely your student mail +2. An email will be sent to you with a verification code. + <img src="../media/setup_user_02.png" alt="signup email" width="50%" height="50%"> +3. Verify the code using ``/verify CODE_FROM_EMAIL`` in Discord. + <img src="../media/setup_user_03.png" alt="verify in discord" width="50%" height="50%"> +4. Once complete your Wolves and Discord accounts will be linked. + +You will get member roles on any Discord that is using the bot that you are a member of. + +### Minecraft +You can link your Minecraft username to grant you access to any Minecraft server run by UL Computer Society. + +``/link_minecraft MINECRAFT_USERNAME`` diff --git a/flake.nix b/flake.nix index b48d90b..12ae36e 100644 --- a/flake.nix +++ b/flake.nix @@ -119,7 +119,7 @@ # modify these scripts = { # every 20 min - "update_data" = "*:0,20,40"; + "update_data" = "*:0,10,20,30,40,50"; # groups are updated every hour, offset from teh ldap "update_users" = "*:05:00"; # minecraft stuff is updated at 5am diff --git a/media/setup_user_01.png b/media/setup_user_01.png new file mode 100644 index 0000000..b5e207b --- /dev/null +++ b/media/setup_user_01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6440b2d302c5a7e46493687bb0bbf941c29c5552c71869303397da3c4f0a52b +size 72163 diff --git a/media/setup_user_02.png b/media/setup_user_02.png new file mode 100644 index 0000000..34898b9 --- /dev/null +++ b/media/setup_user_02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e64d8e2322990dcc6446a34e988c6754edc536c3ee1aa8c330e7dadb899152bc +size 72884 diff --git a/media/setup_user_03.png b/media/setup_user_03.png new file mode 100644 index 0000000..fda3bda --- /dev/null +++ b/media/setup_user_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f19d24686bb9da8857263d1f146e29413fd6bd316981281c18ef2b4ec3621596 +size 51740 diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 9d6328f..fdf82d4 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -163,7 +163,7 @@ pub mod link { "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" } } - div style="display: flex; flex-direction: column; align-items: center;" { + div { h2 { "Hello from Skynet!" } // Substitute in the name of our recipient. p { "Hi " (user) "," } @@ -263,6 +263,7 @@ pub mod link { .await } + #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum WolvesResultUserResult { diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 61b304d..45c4643 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -15,6 +15,7 @@ pub(crate) mod user { pub(crate) mod add { use super::*; use crate::commands::link_email::link::get_server_member_discord; + use serde::{Deserialize, Serialize}; use serenity::model::id::UserId; use skynet_discord_bot::common::database::Wolves; use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; @@ -22,13 +23,23 @@ pub(crate) mod user { use sqlx::Error; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("link_minecraft").description("Link your minecraft account").create_option(|option| { - option - .name("minecraft-username") - .description("Your Minecraft username") - .kind(CommandOptionType::String) - .required(true) - }) + command + .name("link_minecraft") + .description("Link your minecraft account") + .create_option(|option| { + option + .name("minecraft_username") + .description("Your Minecraft username") + .kind(CommandOptionType::String) + .required(true) + }) + .create_option(|option| { + option + .name("bedrock_account") + .description("Is this a Bedrock account?") + .kind(CommandOptionType::Boolean) + .required(false) + }) } pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { @@ -63,19 +74,48 @@ pub(crate) mod user { return "Please provide a valid username".to_string(); }; - // insert the username into the database - match add_minecraft(&db, &command.user.id, username).await { - Ok(_) => {} - Err(e) => { - dbg!("{:?}", e); - return format!("Failure to minecraft username {:?}", username); + // this is always true unless they state its not + let mut java = true; + if let Some(x) = command.data.options.get(1) { + if let Some(CommandDataOptionValue::Boolean(z)) = x.to_owned().resolved { + java = !z; + } + } + + let username_mc; + if java { + // insert the username into the database + match add_minecraft(&db, &command.user.id, username).await { + Ok(_) => {} + Err(e) => { + dbg!("{:?}", e); + 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); + } + Some(x) => { + match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await { + Ok(_) => {} + Err(e) => { + dbg!("{:?}", e); + return format!("Failure to minecraft UID {:?}", &x.floodgateuid); + } + } + + username_mc = x.floodgateuid; + } } } // get a list of servers that the user is a member of if let Ok(servers) = get_servers(&db, &command.user.id).await { for server in servers { - whitelist_update(&vec![username.to_string()], &server.minecraft, &config.discord_token_minecraft).await; + whitelist_update(&vec![(username_mc.to_owned(), java)], &server.minecraft, &config.discord_token_minecraft).await; } } @@ -96,6 +136,51 @@ pub(crate) mod user { .await } + #[derive(Serialize, Deserialize, Debug)] + struct BedrockDetails { + pub gamertag: String, + pub xuid: String, + pub floodgateuid: String, + pub icon: String, + pub gamescore: String, + pub accounttier: String, + pub textureid: String, + pub skin: String, + pub linked: bool, + pub java_uuid: String, + pub java_name: String, + } + + async fn get_minecraft_bedrock(username: &str, api_key: &str) -> Option<BedrockDetails> { + let url = format!("https://mcprofile.io/api/v1/bedrock/gamertag/{username}/"); + match surf::get(url) + .header("x-api-key", api_key) + .header("User-Agent", "UL Computer Society") + .recv_json() + .await + { + Ok(res) => Some(res), + Err(e) => { + dbg!(e); + None + } + } + } + + async fn add_minecraft_bedrock(db: &Pool<Sqlite>, user: &UserId, minecraft: &str) -> Result<Option<Wolves>, Error> { + sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET minecraft_uid = ?2 + WHERE discord = ?1; + ", + ) + .bind(*user.as_u64() as i64) + .bind(minecraft) + .fetch_optional(db) + .await + } + async fn get_servers(db: &Pool<Sqlite>, discord: &UserId) -> Result<Vec<Minecraft>, Error> { sqlx::query_as::<_, Minecraft>( " @@ -249,7 +334,7 @@ pub(crate) mod server { ID: {id} Online: {online} Info: {description} - Link: <http://panel.games.skynet.ie/server/{id}> + Link: <https://panel.games.skynet.ie/server/{id}> "#, name = &x.attributes.name, online = !x.attributes.is_suspended, diff --git a/src/lib.rs b/src/lib.rs index 09f60bf..1b5000d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ use serenity::model::prelude::application_command::ApplicationCommandInteraction use serenity::prelude::TypeMapKey; use std::{env, sync::Arc}; use tokio::sync::RwLock; + +#[derive(Debug)] pub struct Config { // manages where teh database is stored pub home: String, @@ -17,6 +19,7 @@ pub struct Config { // tokens for discord and other API's pub discord_token: String, pub discord_token_minecraft: String, + pub minecraft_mcprofile: String, // email settings pub mail_smtp: String, @@ -44,6 +47,7 @@ pub fn get_config() -> Config { let mut config = Config { discord_token: "".to_string(), discord_token_minecraft: "".to_string(), + minecraft_mcprofile: "".to_string(), home: ".".to_string(), database: "database.db".to_string(), @@ -52,7 +56,6 @@ pub fn get_config() -> Config { mail_user: "".to_string(), mail_pass: "".to_string(), wolves_url: "".to_string(), - wolves_api: "".to_string(), committee_server: GuildId(0), committee_role: RoleId(0), committee_category: ChannelId(0), @@ -71,6 +74,9 @@ pub fn get_config() -> Config { if let Ok(x) = env::var("DISCORD_TOKEN_MINECRAFT") { config.discord_token_minecraft = x.trim().to_string(); } + if let Ok(x) = env::var("MINECRAFT_MCPROFILE_KEY") { + config.minecraft_mcprofile = x.trim().to_string(); + } if let Ok(x) = env::var("EMAIL_SMTP") { config.mail_smtp = x.trim().to_string(); @@ -85,7 +91,6 @@ pub fn get_config() -> Config { if let Ok(x) = env::var("WOLVES_URL_BASE") { config.wolves_url = x.trim().to_string(); } - if let Ok(x) = env::var("WOLVES_API") { config.wolves_api = x.trim().to_string(); } @@ -109,6 +114,295 @@ pub fn get_config() -> Config { config } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ServerMembers { + pub server: GuildId, + pub id_wolves: i64, + pub expiry: String, +} +impl<'r> FromRow<'r, SqliteRow> for ServerMembers { + fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { + let server_tmp: i64 = row.try_get("server")?; + let server = GuildId::from(server_tmp as u64); + + Ok(Self { + server, + id_wolves: row.try_get("id_wolves")?, + expiry: row.try_get("expiry")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ServerMembersWolves { + pub server: GuildId, + pub id_wolves: i64, + pub expiry: String, + pub email: String, + pub discord: Option<UserId>, + pub minecraft: Option<String>, + pub minecraft_uid: Option<String>, +} +impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { + fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { + let server_tmp: i64 = row.try_get("server")?; + let server = GuildId::from(server_tmp as u64); + let discord = match row.try_get("discord") { + Ok(x) => { + let tmp: i64 = x; + if tmp == 0 { + None + } else { + Some(UserId::from(tmp as u64)) + } + } + _ => None, + }; + + Ok(Self { + server, + id_wolves: row.try_get("id_wolves")?, + expiry: row.try_get("expiry")?, + email: row.try_get("email")?, + discord, + minecraft: row.try_get("minecraft")?, + minecraft_uid: row.try_get("minecraft_uid")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Wolves { + pub id_wolves: i64, + pub email: String, + pub discord: Option<UserId>, + pub minecraft: Option<String>, +} +impl<'r> FromRow<'r, SqliteRow> for Wolves { + fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { + let discord = match row.try_get("discord") { + Ok(x) => { + let tmp: i64 = x; + if tmp == 0 { + None + } else { + Some(UserId::from(tmp as u64)) + } + } + _ => None, + }; + + Ok(Self { + id_wolves: row.try_get("id_wolves")?, + email: row.try_get("email")?, + discord, + minecraft: row.try_get("minecraft")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct WolvesVerify { + pub email: String, + pub discord: UserId, + pub auth_code: String, + pub date_expiry: String, +} +impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { + fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { + let user_tmp: i64 = row.try_get("discord")?; + let discord = UserId::from(user_tmp as u64); + + Ok(Self { + email: row.try_get("email")?, + discord, + auth_code: row.try_get("auth_code")?, + date_expiry: row.try_get("date_expiry")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Committee { + pub email: String, + pub discord: UserId, + pub auth_code: String, + pub committee: i64, +} +impl<'r> FromRow<'r, SqliteRow> for Committee { + fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { + let user_tmp: i64 = row.try_get("discord")?; + let discord = UserId::from(user_tmp as u64); + + Ok(Self { + email: row.try_get("email")?, + discord, + auth_code: row.try_get("auth_code")?, + committee: row.try_get("committee")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Servers { + pub server: GuildId, + pub wolves_api: String, + pub role_past: Option<RoleId>, + pub role_current: RoleId, + pub member_past: i64, + pub member_current: i64, + pub bot_channel_id: ChannelId, + // these can be removed in teh future with an API update + pub server_name: String, + pub wolves_link: String, +} +impl<'r> FromRow<'r, SqliteRow> for Servers { + fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { + let server_tmp: i64 = row.try_get("server")?; + let server = GuildId::from(server_tmp as u64); + let role_past = match row.try_get("role_past") { + Ok(x) => { + let tmp: i64 = x; + if tmp == 0 { + None + } else { + Some(RoleId::from(tmp as u64)) + } + } + _ => None, + }; + let role_current = match row.try_get("role_current") { + Ok(x) => { + let tmp: i64 = x; + RoleId::from(tmp as u64) + } + _ => RoleId::from(0u64), + }; + + let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?; + let bot_channel_id = ChannelId::from(bot_channel_tmp as u64); + + Ok(Self { + server, + wolves_api: row.try_get("wolves_api")?, + 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")?, + wolves_link: row.try_get("wolves_link")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Minecraft { + pub discord: GuildId, + pub minecraft: String, +} +impl<'r> FromRow<'r, SqliteRow> for Minecraft { + fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { + let server_tmp: i64 = row.try_get("server_discord")?; + let discord = GuildId::from(server_tmp as u64); + + Ok(Self { + discord, + minecraft: row.try_get("server_minecraft")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct RoleAdder { + pub server: GuildId, + pub role_a: RoleId, + pub role_b: RoleId, + pub role_c: RoleId, +} +impl<'r> FromRow<'r, SqliteRow> for RoleAdder { + fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { + let server_tmp: i64 = row.try_get("server")?; + let server = GuildId::from(server_tmp as u64); + + Ok(Self { + server, + role_a: get_role_from_row(row, "role_a"), + role_b: get_role_from_row(row, "role_b"), + role_c: get_role_from_row(row, "role_c"), + }) + } +} + +fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { + match row.try_get(col) { + Ok(x) => { + let tmp: i64 = x; + RoleId(tmp as u64) + } + _ => RoleId::from(0u64), + } +} + +pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> { + let database = format!("{}/{}", &config.home, &config.database); + + let pool = SqlitePoolOptions::new() + .max_connections(5) + .connect_with( + SqliteConnectOptions::from_str(&format!("sqlite://{}", database))? + .foreign_keys(true) + .create_if_missing(true), + ) + .await?; + + // migrations are amazing! + sqlx::migrate!("./db/migrations").run(&pool).await?; + + Ok(pool) +} + +pub async fn get_server_config(db: &Pool<Sqlite>, server: &GuildId) -> Option<Servers> { + sqlx::query_as::<_, Servers>( + r#" + SELECT * + FROM servers + WHERE server = ? + "#, + ) + .bind(*server.as_u64() as i64) + .fetch_one(db) + .await + .ok() +} + +pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId, member: &guild::Member) -> Result<ServerMembersWolves, Error> { + sqlx::query_as::<_, ServerMembersWolves>( + r#" + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + WHERE server = ? AND discord = ? + "#, + ) + .bind(*server.as_u64() as i64) + .bind(*member.user.id.as_u64() as i64) + .fetch_one(db) + .await +} + +pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> { + sqlx::query_as::<_, Servers>( + r#" + SELECT * + FROM servers + "#, + ) + .fetch_all(db) + .await + .unwrap_or_default() +} + pub fn get_now_iso(short: bool) -> String { let now = Utc::now(); if short { @@ -122,6 +416,255 @@ pub fn random_string(len: usize) -> String { thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } +pub mod set_roles { + use super::*; + pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &[UserId]) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + let Servers { + server, + role_past, + role_current, + .. + } = server; + + let mut roles_set = [0, 0, 0]; + let mut members = vec![]; + + for member in get_server_member_bulk(&db, server).await { + if let Some(x) = member.discord { + members.push(x); + } + } + let mut members_all = members.len(); + + if let Ok(x) = server.members(ctx, None, None).await { + for mut member in x { + // 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) { + let mut roles = vec![]; + + if let Some(role) = &role_past { + if !member.roles.contains(role) { + roles_set[0] += 1; + roles.push(role.to_owned()); + } + } + + if !member.roles.contains(role_current) { + roles_set[1] += 1; + roles.push(role_current.to_owned()); + } + + if let Err(e) = member.add_roles(ctx, &roles).await { + println!("{:?}", e); + } + } else { + // old and never + + if let Some(role) = &role_past { + if member.roles.contains(role) { + members_all += 1; + } + } + + if member.roles.contains(role_current) { + 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); + } + } + } + for role in remove_roles.iter().flatten() { + if let Err(e) = member.remove_role(ctx, role).await { + println!("{:?}", e); + } + } + } + } + + set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; + + // small bit of logging to note changes over time + println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]); + } + + pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> { + sqlx::query_as::<_, ServerMembersWolves>( + r#" + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + WHERE ( + server = ? + AND discord IS NOT NULL + AND expiry > ? + ) + "#, + ) + .bind(*server.as_u64() as i64) + .bind(get_now_iso(true)) + .fetch_all(db) + .await + .unwrap_or_default() + } + + async fn set_server_numbers(db: &Pool<Sqlite>, server: &GuildId, past: i64, current: i64) { + match sqlx::query_as::<_, Wolves>( + " + UPDATE servers + SET member_past = ?, member_current = ? + WHERE server = ? + ", + ) + .bind(past) + .bind(current) + .bind(*server.as_u64() as i64) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into {}", server.as_u64()); + println!("{:?}", e); + } + } + } +} + +pub mod get_data { + use super::*; + use crate::set_roles::update_server; + use std::collections::BTreeMap; + use wolves_oxidised::WolvesUser; + + pub async fn get_wolves(ctx: &Context) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::<Config>().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + // set up teh client + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); + + for server_config in get_server_config_bulk(&db).await { + let Servers { + server, + wolves_api, + .. + } = &server_config; + + let existing_tmp = get_server_member(&db, server).await; + let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>(); + + // list of users that need to be updated for this server + let mut user_to_update = vec![]; + for user in wolves.get_members(wolves_api).await { + let id = user.member_id.parse::<u64>().unwrap_or_default(); + match existing.get(&(id as i64)) { + None => { + // user does not exist already, add everything + add_users_wolves(&db, &user).await; + add_users_server_members(&db, server, &user).await; + } + Some(old) => { + // always update wolves table, in case data has changed + add_users_wolves(&db, &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 !user_to_update.is_empty() { + update_server(ctx, &server_config, &[], &user_to_update).await; + } + } + } + + pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> { + sqlx::query_as::<_, ServerMembersWolves>( + r#" + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + WHERE ( + server = ? + AND discord IS NOT NULL + ) + "#, + ) + .bind(*server.as_u64() as i64) + .fetch_all(db) + .await + .unwrap_or_default() + } + + async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesUser) { + // expiry + match sqlx::query_as::<_, Wolves>( + " + INSERT INTO wolves (id_wolves, email) + VALUES ($1, $2) + ON CONFLICT(id_wolves) DO UPDATE SET email = $2 + ", + ) + .bind(&user.member_id) + .bind(&user.contact_email) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into Wolves {:?}", user); + println!("{:?}", e); + } + } + } + async fn add_users_server_members(db: &Pool<Sqlite>, server: &GuildId, user: &WolvesUser) { + match sqlx::query_as::<_, ServerMembers>( + " + INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) + VALUES (?1, ?2, ?3) + ", + ) + .bind(*server.as_u64() as i64) + .bind(&user.member_id) + .bind(&user.expiry) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user); + println!("{:?}", e); + } + } + } +} + /** For any time ye need to check if a user who calls a command has admin privlages */ @@ -159,3 +702,153 @@ pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> None } } + +/** +loop through all members of server +get a list of folks with mc accounts that are members +and a list that arent members + */ +pub async fn update_server(server_id: &str, db: &Pool<Sqlite>, g_id: &GuildId, config: &Config) { + let mut usernames = vec![]; + for member in get_server_member_bulk(db, g_id).await { + if let Some(x) = member.minecraft { + usernames.push((x, true)); + } + if let Some(x) = member.minecraft_uid { + usernames.push((x, false)); + } + } + if !usernames.is_empty() { + whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await; + } +} + +async fn post<T: Serialize>(url: &str, bearer: &str, data: &T) { + match surf::post(url) + .header("Authorization", bearer) + .header("Content-Type", "application/json") + .header("Accept", "Application/vnd.pterodactyl.v1+json") + .body_json(&data) + { + Ok(req) => { + req.await.ok(); + } + Err(e) => { + dbg!(e); + } + } +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct ServerDetailsResSub { + pub identifier: String, + pub name: String, + pub description: String, + pub is_suspended: bool, +} +#[derive(Deserialize, Serialize, Debug)] +pub struct ServerDetailsRes { + pub attributes: ServerDetailsResSub, +} + +async fn get<T: Serialize + DeserializeOwned>(url: &str, bearer: &str) -> Option<T> { + match surf::get(url) + .header("Authorization", bearer) + .header("Content-Type", "application/json") + .header("Accept", "Application/vnd.pterodactyl.v1+json") + .recv_json() + .await + { + Ok(res) => Some(res), + Err(e) => { + dbg!(e); + + None + } + } +} + +#[derive(Deserialize, Serialize, Debug)] +struct BodyCommand { + command: String, +} + +#[derive(Deserialize, Serialize, Debug)] +struct BodyDelete { + root: String, + files: Vec<String>, +} + +pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { + println!("Update whitelist for {}", server); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + + for (name, java) in add { + let data = if *java { + BodyCommand { + command: format!("whitelist add {name}"), + } + } else { + BodyCommand { + command: format!("fwhitelist add {name}"), + } + }; + post(&format!("{url_base}/command"), &bearer, &data).await; + } +} + +pub async fn whitelist_wipe(server: &str, token: &str) { + println!("Wiping whitelist for {}", server); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + + // delete whitelist + let deletion = BodyDelete { + root: "/".to_string(), + files: vec!["whitelist.json".to_string()], + }; + post(&format!("{url_base}/files/delete"), &bearer, &deletion).await; + + // recreate teh file, passing in the type here so the compiler knows what type of vec it is + post::<Vec<&str>>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await; + + // reload the whitelist + let data = BodyCommand { + command: "whitelist reload".to_string(), + }; + post(&format!("{url_base}/command"), &bearer, &data).await; +} + +pub async fn server_information(server: &str, token: &str) -> Option<ServerDetailsRes> { + println!("Get server information for {}", server); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + get::<ServerDetailsRes>(&format!("{url_base}/"), &bearer).await +} + +pub async fn get_minecraft_config(db: &Pool<Sqlite>) -> Vec<Minecraft> { + sqlx::query_as::<_, Minecraft>( + r#" + SELECT * + FROM minecraft + "#, + ) + .fetch_all(db) + .await + .unwrap_or_default() +} + +pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Vec<Minecraft> { + sqlx::query_as::<_, Minecraft>( + r#" + SELECT * + FROM minecraft + WHERE server_discord = ?1 + "#, + ) + .bind(*g_id.as_u64() as i64) + .fetch_all(db) + .await + .unwrap_or_default() +}