Compare commits

..

74 commits

Author SHA1 Message Date
6353d77360 Merge pull request 'Remove RwLock for database' (#45) from cordlesscoder/discord-bot:#45_Remove_RwLock into main
All checks were successful
/ check_lfs (push) Successful in 3s
On_Push / lint_fmt (push) Successful in 7s
On_Push / lint_clippy (push) Successful in 7s
On_Push / build (push) Successful in 7s
On_Push / deploy (push) Successful in 11s
Reviewed-on: #45
2025-09-11 12:02:31 +00:00
Roman Moisieiev
062f826d28 Remove RwLock for database
All checks were successful
/ check_lfs (pull_request) Successful in 9s
/ lint_fmt (pull_request) Successful in 10s
/ lint_clippy (pull_request) Successful in 30s
/ build (pull_request) Successful in 1m19s
2025-09-11 12:54:54 +01:00
d8f785b0db Merge pull request 'Fix typos' (#44) from cordlesscoder/discord-bot:#44_Typos into main
All checks were successful
/ check_lfs (push) Successful in 2s
On_Push / lint_clippy (push) Successful in 7s
On_Push / lint_fmt (push) Successful in 13s
On_Push / build (push) Successful in 1m34s
On_Push / deploy (push) Successful in 10s
Reviewed-on: #44
2025-09-11 11:49:03 +00:00
Roman Moisieiev
d70a037057 Blame: ignore Fix Typos commit
All checks were successful
/ check_lfs (pull_request) Successful in 3s
/ lint_fmt (pull_request) Successful in 37s
/ lint_clippy (pull_request) Successful in 1m15s
/ build (pull_request) Successful in 3m1s
2025-09-11 12:36:48 +01:00
Roman Moisieiev
7e90f45196 Fix typos 2025-09-11 12:36:03 +01:00
7526a82bb7
feat: kill the service if it persists longer than 9 min
All checks were successful
/ check_lfs (push) Successful in 4s
On_Push / lint_fmt (push) Successful in 10s
On_Push / lint_clippy (push) Successful in 1m13s
On_Push / build (push) Successful in 1m36s
On_Push / deploy (push) Successful in 11s
2025-09-04 23:11:24 +01:00
3149a5f99f Bump 2
All checks were successful
/ check_lfs (push) Successful in 9s
On_Push / lint_fmt (push) Successful in 17s
On_Push / lint_clippy (push) Successful in 27s
On_Push / build (push) Successful in 1m38s
On_Push / deploy (push) Successful in 15s
2025-08-31 11:55:35 +00:00
061b73378a Bump the bot
Some checks failed
/ check_lfs (push) Successful in 14s
On_Push / lint_fmt (push) Failing after 1m13s
On_Push / lint_clippy (push) Successful in 2m53s
On_Push / build (push) Has been skipped
On_Push / deploy (push) Has been skipped
2025-08-31 11:51:57 +00:00
a225c14b4f
fix: was ddossing the poor database
All checks were successful
/ check_lfs (push) Successful in 24s
On_Push / lint_clippy (push) Successful in 15s
On_Push / lint_fmt (push) Successful in 21s
On_Push / build (push) Successful in 1m51s
On_Push / deploy (push) Successful in 23s
guild_members_chunk is triggered for each chunk for each server it is on, and the bot is currently in 10 servers so it was runnign teh same thigns 10 times, clogging up conenctions
2025-07-21 04:29:03 +01:00
095ff6f2ce
fix: better handling of returning teh committees
Some checks failed
On_Push / lint_fmt (push) Successful in 21s
On_Push / lint_clippy (push) Successful in 52s
On_Push / build (push) Successful in 1m57s
On_Push / deploy (push) Successful in 15s
/ check_lfs (push) Failing after 10m2s
2025-07-21 02:52:59 +01:00
18fd45d39b Merge pull request '#40_Improve_Preformance' (#41) from #40_Improve_Preformance into main
All checks were successful
On_Push / lint_fmt (push) Successful in 11s
/ check_lfs (push) Successful in 8s
On_Push / lint_clippy (push) Successful in 13s
On_Push / build (push) Successful in 8s
On_Push / deploy (push) Successful in 13s
Reviewed-on: #41

Closes #40
2025-07-21 01:06:27 +00:00
854e946a8f
ci: improve the pipeline to test for the full suite
All checks were successful
/ check_lfs (push) Successful in 10s
/ check_lfs (pull_request) Successful in 8s
/ lint_fmt (pull_request) Successful in 14s
/ lint_clippy (pull_request) Successful in 16s
/ build (pull_request) Successful in 3m39s
ci: dont pull in the lfs for teh PR pipeline build
ci: checkout without LFS
ci: see if using the ref directly will help
ci: test a slight modification
ci: see if new get ldfs script works
ci: test using teh new v8
2025-07-21 01:59:36 +01:00
d0726169ee
clippy: changes from nightly clippy
All checks were successful
/ check_lfs (push) Successful in 16s
/ check_lfs (pull_request) Successful in 12s
all of this is embeding teh var into teh format macro
2025-07-21 00:50:20 +01:00
9d409e3692
fmt: clippy and nightly fmt 2025-07-21 00:38:59 +01:00
57d4947edf
fix: no longer needint to wait until the cache in teh main program is filled 2025-07-20 23:48:05 +01:00
6d08312f48
fix: these were not using teh cache to access teh member/role data
*was executing before teh cache was built (``cache_ready`` is only when teh cache has been init, not when it is populated)
2025-07-20 23:46:30 +01:00
bd9d0cd43f
fix: these do not need to use teh cache 2025-07-20 23:44:13 +01:00
1af7f28a45
feat: restore teh original services, giving up the logging and control was too much 2025-07-20 23:40:34 +01:00
feff293043
feat: moved the update server icon to the main thread 2025-07-20 23:12:02 +01:00
227db8a741
feat: moved the update data to the main thread 2025-07-20 22:54:34 +01:00
13eb230754
fix: updating the wolves (user data) should not trigger a server update (directly).
That should always be triggered separately.
This was a holdover from a  time when updating teh users was expensive (timewise)
2025-07-20 22:48:04 +01:00
eb88216740
feat: removed the cronjob for updating the committee server 2025-07-20 22:33:56 +01:00
96eb81293b
feat: got the committee update running smoothly (using cache and far far faster due to fixing some logic issues) 2025-07-20 22:32:55 +01:00
5815cde38b
fmt: better wording for logs 2025-07-20 22:28:59 +01:00
1729ec0a54
feat: drastically speed up the committee server script, it no longer tries to remove the committee role from those who dont have it. 2025-07-20 22:26:51 +01:00
43ef787d59
fix: no need to pass in teh full object, a reference will do 2025-07-20 22:25:31 +01:00
a8bed0bacc
fix: was calling the wrong ctx 2025-07-20 20:43:46 +01:00
3dd81a5c54
feat: remove the update_users service 2025-07-20 20:40:15 +01:00
04aa0e63d4
feat: setup update users to be every 5 min while using the cache 2025-07-20 20:37:28 +01:00
2b2dfc2531
feat: cleaned up array that was used to count members/changes to a struct 2025-07-20 20:32:43 +01:00
e901f3ed74
feat: add caching to everything, should make all member interacts faster
All checks were successful
/ check_lfs (push) Successful in 13s
On_Push / lint_fmt (push) Successful in 24s
On_Push / lint_clippy (push) Successful in 25s
On_Push / build (push) Successful in 1m59s
On_Push / deploy (push) Successful in 17s
2025-07-07 22:22:25 +01:00
3abbb8d485
feat: added script to clean up the committee server if it got flooded with extra channels 2025-07-07 22:18:04 +01:00
b8ffd42184
feat: the bot wasnt using any caching, this should make many operations far faster now
All checks were successful
On_Push / lint_fmt (push) Successful in 23s
/ check_lfs (push) Successful in 22s
On_Push / lint_clippy (push) Successful in 1m45s
On_Push / build (push) Successful in 1m46s
On_Push / deploy (push) Successful in 14s
2025-07-07 21:29:37 +01:00
764e8cd620 Fix LFS on discord bot (#39)
All checks were successful
/ check_lfs (push) Successful in 10s
On_Push / lint_fmt (push) Successful in 29s
On_Push / lint_clippy (push) Successful in 1m11s
On_Push / build (push) Successful in 2m37s
On_Push / deploy (push) Successful in 13s
Reviewed-on: #39

Thanks @esy
Co-authored-by: Daragh <esy@skynet.ie>
Co-committed-by: Daragh <esy@skynet.ie>
2025-07-06 23:28:51 +00:00
esy
76f8aa2712 Update README.md
All checks were successful
/ check_lfs (push) Successful in 6s
2025-07-06 18:12:04 +00:00
c4da3e9109
fix: only allow image files to be chosen
All checks were successful
/ check_lfs (push) Successful in 20s
On_Push / lint_fmt (push) Successful in 1m5s
On_Push / lint_clippy (push) Successful in 2m18s
On_Push / build (push) Successful in 2m1s
On_Push / deploy (push) Successful in 21s
2025-07-05 15:31:53 +01:00
d27befdac6 Merge pull request 'Add command for link to documentation' (#38) from #37_add-documentation-command into main
All checks were successful
/ check_lfs (push) Successful in 17s
On_Push / lint_fmt (push) Successful in 1m5s
On_Push / lint_clippy (push) Successful in 2m10s
On_Push / build (push) Successful in 1m30s
On_Push / deploy (push) Successful in 24s
Reviewed-on: #38
2025-06-23 23:16:41 +00:00
7403f531eb
feat: the backend is pretty simple, just pull the rep link from teh config_toml and add on the path to the docs.
All checks were successful
/ check_lfs (push) Successful in 11s
/ check_lfs (pull_request) Successful in 12s
Then its just linking it all up.

Closes #37
2025-06-24 00:13:29 +01:00
1dc5c105df
fix: needed to add git and git lfs to teh path of the service
All checks were successful
/ check_lfs (push) Successful in 20s
On_Push / lint_fmt (push) Successful in 59s
On_Push / lint_clippy (push) Successful in 4m33s
On_Push / build (push) Successful in 2m2s
On_Push / deploy (push) Successful in 33s
2025-06-18 03:57:04 +01:00
3a56d7bba5
feat: lock the `nix build` to using the repo rust version
All checks were successful
/ check_lfs (push) Successful in 43s
On_Push / lint_fmt (push) Successful in 5m2s
On_Push / lint_clippy (push) Successful in 8m26s
On_Push / build (push) Successful in 7m58s
On_Push / deploy (push) Successful in 36s
2025-06-17 16:21:57 +01:00
327ff99b69
fix: the pipeline got caught on a lint
This isnt in the 1.87 rustfmt but its stilla  good catch
2025-06-17 16:21:15 +01:00
dedf8c3644 Merge pull request '#35_remove-hardcoded-servers' (#36) from #35_remove-hardcided-servers into main
Some checks failed
On_Push / lint_fmt (push) Successful in 24s
On_Push / lint_clippy (push) Failing after 4m4s
On_Push / build (push) Has been skipped
On_Push / deploy (push) Has been skipped
/ check_lfs (push) Failing after 12m3s
Reviewed-on: #36

Closes #35
2025-06-17 14:56:57 +00:00
a6eff75e39
feat: use values from teh env file to dictate the servers
All checks were successful
/ check_lfs (pull_request) Successful in 8s
/ check_lfs (push) Successful in 8s
2025-06-17 15:38:15 +01:00
72226cc59b
feat: add support for passing teh compsoc server id via env 2025-06-17 15:38:15 +01:00
f841039c53
fix: was pulling in the wrong env var 2025-06-17 15:38:15 +01:00
87dd04e12f Merge pull request 'Automatically change server icon daily' (#33) from #32_rotating-server-icon into main
Some checks failed
/ check_lfs (push) Successful in 27s
On_Push / lint_fmt (push) Successful in 3m13s
On_Push / build (push) Has been cancelled
On_Push / deploy (push) Has been cancelled
On_Push / lint_clippy (push) Has been cancelled
Reviewed-on: #33

Closes #32
2025-06-17 14:35:43 +00:00
9134feee4e
feat: cleaned up remaining unwraps, and then clippy+fmt
All checks were successful
/ check_lfs (pull_request) Successful in 9s
/ check_lfs (push) Successful in 4s
2025-06-16 21:56:06 +01:00
652dd6ff1c
ci: add workflow to check for lfs status 2025-06-16 21:56:06 +01:00
cae383a186
feat: set up the systemd timer for teh binary 2025-06-16 21:56:06 +01:00
b4cadffdb5
fmt: for whenever it gets stabised (or we use nightly) this would be really good for cleaning up imports 2025-06-16 21:56:06 +01:00
721c8246ac
todo: add a todo where teh mc commands get moved in under committee 2025-06-16 21:56:06 +01:00
0f4524ea63
feat: tidied up the command outouts 2025-06-16 21:56:06 +01:00
86f54aec6d
feat: got the commands mostly working, will need some further fine tuning 2025-06-16 21:56:05 +01:00
86a3af2a65
feat: pull the config for the festivals locally, using teh imported repo 2025-06-16 21:56:05 +01:00
6d5ad8e418
feat: split out the functions so they can be shared with commands 2025-06-16 21:56:05 +01:00
9d50efb757
fmt: cargo+clippy 2025-06-16 21:56:05 +01:00
51bc2f177f
feat: save the selected image to teh library 2025-06-16 21:56:05 +01:00
3523dac46e
fix: properly filter icon based on the festival 2025-06-16 21:56:05 +01:00
51d5904ffd
feat: allow for overlapping festivals 2025-06-16 21:56:05 +01:00
1555a94656
fix: give a reference where it needed to be 2025-06-16 21:56:05 +01:00
7bcf30fb3a
feat: can now set the server icon programmatically 2025-06-16 21:56:05 +01:00
4f96c9087f
feat: get a random image 2025-06-16 21:56:05 +01:00
1ff993d236
feat: only need to keep whatever ones are in teh current season (if at all) 2025-06-16 21:56:05 +01:00
acdfe4b423
fix: only convert if it hasnt already been converted 2025-06-16 21:56:05 +01:00
537fdfd40c
feat: put the converted files into a subfolder 2025-06-16 21:56:05 +01:00
ffd6e40d0b
fix: was being too strict in matching the year 2025-06-16 21:56:05 +01:00
a7423959dc
fix: use a struct for clarity 2025-06-16 21:56:05 +01:00
b4f6835704
feat: got the logos, and converted them if needs be 2025-06-16 21:56:05 +01:00
0034bd34d6
feat: code borrowed from https://github.com/MCorange99/svg2colored-png/tree/main in order to convert from svg to png 2025-06-16 21:56:05 +01:00
725bfa41cc
feat: initial tests of new function to handle changing the logo in discord server 2025-06-16 21:56:05 +01:00
fcfcfb8409
feat: added libraries needed to run the new feature 2025-06-16 21:56:05 +01:00
e449204863
feat: need some new inputs to get this to build 2025-06-16 21:56:05 +01:00
f1dbbec32d
feat: update teh base rust version 2025-06-16 21:56:05 +01:00
esy
8560ed6de5 Merge pull request 'correct command for linking wolves acc' (#31) from update-dm into main
All checks were successful
On_Push / lint_fmt (push) Successful in 1m26s
On_Push / lint_clippy (push) Successful in 6m31s
On_Push / build (push) Successful in 4m19s
On_Push / deploy (push) Successful in 23s
Reviewed-on: #31
Reviewed-by: silver <silver@skynet.ie>
2025-06-16 13:07:53 +00:00
36 changed files with 2233 additions and 335 deletions

View file

@ -0,0 +1,10 @@
on:
- push
- workflow_dispatch
jobs:
check_lfs:
# nix/docker
runs-on: nix
steps:
- uses: https://github.com/MPLew-is/lfs-check-action@1

View file

@ -0,0 +1,51 @@
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

2
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,2 @@
# Fix typos
7e90f451965b0edbd331765ad295a02f31d2bf24

View file

@ -6,4 +6,5 @@ fn_params_layout = "Compressed"
#brace_style = "PreferSameLine"
struct_lit_width = 0
tab_spaces = 2
use_small_heuristics = "Max"
use_small_heuristics = "Max"
imports_granularity = "Crate"

6
.server-icons.toml Normal file
View file

@ -0,0 +1,6 @@
# this file controls the
[source]
repo = "https://forgejo.skynet.ie/Computer_Society/open-goverance"
directory = "Resources/Logo_Variants"
file = "_festivals.toml"

2
.taplo.toml Normal file
View file

@ -0,0 +1,2 @@
[formatting]
column_width = 120

555
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@ -110,6 +110,12 @@ 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"
@ -383,6 +389,12 @@ 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"
@ -449,6 +461,39 @@ 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"
@ -637,6 +682,12 @@ 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"
@ -790,6 +841,16 @@ 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"
@ -805,6 +866,15 @@ 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"
@ -815,6 +885,12 @@ 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"
@ -849,6 +925,27 @@ 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"
@ -1063,6 +1160,16 @@ 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"
@ -1586,16 +1693,6 @@ 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"
@ -1617,6 +1714,18 @@ 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"
@ -1677,6 +1786,12 @@ 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"
@ -1686,6 +1801,24 @@ 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"
@ -1718,7 +1851,7 @@ dependencies = [
"futures-util",
"hostname",
"httpdate",
"idna 1.0.3",
"idna",
"mime",
"native-tls",
"nom",
@ -1843,6 +1976,15 @@ 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"
@ -1872,6 +2014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
@ -2031,6 +2174,12 @@ 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"
@ -2075,6 +2224,12 @@ 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"
@ -2145,6 +2300,19 @@ 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"
@ -2339,6 +2507,12 @@ 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"
@ -2435,6 +2609,34 @@ 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"
@ -2450,6 +2652,34 @@ 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"
@ -2582,6 +2812,22 @@ 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"
@ -2713,6 +2959,15 @@ 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"
@ -2806,6 +3061,15 @@ 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"
@ -2831,21 +3095,49 @@ 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",
]
@ -3180,6 +3472,15 @@ 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"
@ -3220,6 +3521,36 @@ 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"
@ -3363,6 +3694,16 @@ 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"
@ -3432,6 +3773,31 @@ 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"
@ -3581,6 +3947,47 @@ 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"
@ -3617,6 +4024,17 @@ 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]]
@ -3629,12 +4047,29 @@ 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"
@ -3683,6 +4118,24 @@ 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"
@ -3704,6 +4157,18 @@ 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"
@ -3722,16 +4187,49 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.2"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna 0.5.0",
"idna",
"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"
@ -3750,6 +4248,12 @@ 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"
@ -3915,6 +4419,12 @@ 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"
@ -4144,6 +4654,15 @@ 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"
@ -4186,6 +4705,12 @@ 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"

View file

@ -4,19 +4,21 @@ 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_users"
[[bin]]
name = "update_committee"
[[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"] }
@ -32,7 +34,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
@ -45,4 +47,13 @@ chrono = "0.4"
lettre = "0.11"
maud = "0.27"
serde = "1.0"
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"

View file

@ -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 is hosted by the Computer Society on Skynet (computer cluster).
Skynet (bot) 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.

View file

@ -0,0 +1,9 @@
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);

17
flake.lock generated
View file

@ -32,6 +32,22 @@
"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,
@ -51,6 +67,7 @@
"inputs": {
"naersk": "naersk",
"nixpkgs": "nixpkgs_2",
"nixpkgs-mozilla": "nixpkgs-mozilla",
"utils": "utils"
}
},

View file

@ -4,6 +4,10 @@
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";
};
@ -17,16 +21,33 @@
nixpkgs,
utils,
naersk,
nixpkgs-mozilla,
}:
utils.lib.eachDefaultSystem (
system: let
overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
pkgs = (import nixpkgs) {inherit system;};
naersk' = pkgs.callPackage naersk {};
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;
};
package_name = "skynet_discord_bot";
desc = "Skynet Discord Bot";
buildInputs = with pkgs; [
openssl
glib
gdk-pixbuf
pkg-config
rustfmt
];
@ -37,6 +58,10 @@
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 {
@ -63,9 +88,9 @@
# `nix develop`
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook pkg-config openssl];
nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook];
# libraries here
buildInputs = [ ];
buildInputs = buildInputs;
RUSTC_VERSION = overrides.toolchain.channel;
# https://github.com/rust-lang/rust-bindgen#environment-variables
shellHook = ''
@ -97,12 +122,14 @@
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}"
@ -127,14 +154,17 @@
# modify these
scripts = {
# every 20 min
# every 10 min
"update_data" = "*:0,10,20,30,40,50";
# groups are updated every hour, offset from teh ldap
"update_users" = "*:05:00";
"update_users" = "*:5,15,25,35,45,55";
# Committee server has its own timer
"update_committee" = "*:15:00";
"update_committee" = "*:5,15,25,35,45,55";
# 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}" = {
@ -194,6 +224,7 @@
after = ["network-online.target"];
wants = [];
environment = environment_config;
path = with pkgs; [ git git-lfs ];
serviceConfig = {
User = "${cfg.user}";

View file

@ -1,2 +1,2 @@
[toolchain]
channel = "1.80"
channel = "1.87"

View file

@ -0,0 +1,137 @@
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::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(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<GuildId>) {
let config_lock = {
let data_read = ctx.data.read().await;
data_read.get::<Config>().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::<DataBase>().expect("Expected Config in TypeMap.").clone()
};
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;
cleanup(&db, &ctx, &config).await;
// finish up
process::exit(0);
}
}
}
async fn cleanup(db: &Pool<Sqlite>, 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::<Vec<_>>();
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::<Vec<_>>();
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);
}
}
}
}
}

View file

@ -1,12 +1,17 @@
use serenity::{
all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent},
async_trait,
client::{Context, EventHandler},
model::gateway::{GatewayIntents, Ready},
model::gateway::GatewayIntents,
Client,
};
use skynet_discord_bot::common::database::{db_init, DataBase};
use skynet_discord_bot::common::set_roles::committee;
use skynet_discord_bot::{get_config, Config};
use skynet_discord_bot::{
common::{
database::{db_init, DataBase},
set_roles::committee,
},
get_config, Config,
};
use std::{process, sync::Arc};
use tokio::sync::RwLock;
@ -23,6 +28,7 @@ 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");
@ -30,25 +36,39 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
data.insert::<DataBase>(Arc::new(db));
}
if let Err(why) = client.start().await {
println!("Client error: {:?}", why);
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);
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
let config_lock = {
let data_read = ctx.data.read().await;
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
};
let config_global = config_lock.read().await;
// u[date committee server
committee::check_committee(Arc::clone(&ctx)).await;
let server = config_global.committee_server;
// finish up
process::exit(0);
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);
}
}
}

View file

@ -4,10 +4,13 @@ use serenity::{
model::gateway::{GatewayIntents, Ready},
Client,
};
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 skynet_discord_bot::{
common::{
database::{db_init, DataBase},
wolves::{cns::get_wolves, committees::get_cns},
},
get_config, Config,
};
use std::{process, sync::Arc};
use tokio::sync::RwLock;
@ -27,6 +30,7 @@ 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");
@ -34,11 +38,11 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
data.insert::<DataBase>(Arc::new(db));
}
if let Err(why) = client.start().await {
println!("Client error: {:?}", why);
println!("Client error: {why:?}");
}
}

View file

@ -1,6 +1,10 @@
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 skynet_discord_bot::{
common::{
database::db_init,
minecraft::{get_minecraft_config, update_server, whitelist_wipe},
},
get_config,
};
use std::collections::HashSet;
#[tokio::main]

View file

@ -0,0 +1,71 @@
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::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(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::<DataBase>().expect("Expected Config in TypeMap.").clone()
};
let config_lock = {
let data_read = ctx.data.read().await;
data_read.get::<Config>().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);
}
}

View file

@ -1,13 +1,24 @@
use serenity::{
all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent},
async_trait,
client::{Context, EventHandler},
model::gateway::{GatewayIntents, Ready},
model::gateway::GatewayIntents,
Client,
};
use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase};
use skynet_discord_bot::common::set_roles::normal;
use skynet_discord_bot::{get_config, Config};
use std::{process, sync::Arc};
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 tokio::sync::RwLock;
#[tokio::main]
@ -22,7 +33,11 @@ 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 {})
.event_handler(Handler {
server_count: Default::default(),
server_cached: Default::default(),
})
.cache_settings(serenity::cache::Settings::default())
.await
.expect("Error creating client");
@ -30,38 +45,51 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
data.insert::<DataBase>(Arc::new(db));
}
if let Err(why) = client.start().await {
println!("Client error: {:?}", why);
println!("Client error: {why:?}");
}
}
struct Handler;
struct Handler {
server_count: AtomicUsize,
server_cached: AtomicUsize,
}
#[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);
async fn cache_ready(&self, ctx: Context, guilds: Vec<GuildId>) {
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));
}
// this goes into each server and sets roles for each wolves member
check_bulk(Arc::clone(&ctx)).await;
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!");
// finish up
process::exit(0);
// this goes into each server and sets roles for each wolves member
check_bulk(&ctx).await;
// finish up
process::exit(0);
}
}
}
}
async fn check_bulk(ctx: Arc<Context>) {
let db_lock = {
async fn check_bulk(ctx: &Context) {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().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;
}
}

View file

@ -1,8 +1,12 @@
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
use serenity::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 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 sqlx::{Error, Pool, Sqlite};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
@ -52,11 +56,10 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
return "Please provide a valid channel for ``Bot Channel``".to_string();
};
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
let server_data = Servers {
server: command.guild_id.unwrap_or_default(),
@ -72,8 +75,8 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> 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:?}");
}
}
@ -98,7 +101,7 @@ async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Resul
.fetch_optional(db)
.await;
// if the entry does not exist already tehn do a user update
// if the entry does not exist already then do a user update
let (update, current_remove, current_role, past_remove, past_role) = match &existing {
None => (true, false, None, false, None),
Some(x) => {

View file

@ -20,4 +20,8 @@ pub fn register() -> CreateCommand {
.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.")),
)
}

View file

@ -5,8 +5,7 @@ pub mod committee {
use serenity::all::{
CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption,
};
use skynet_discord_bot::common::database::DataBase;
use skynet_discord_bot::common::set_roles::committee::db_roles_get;
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 {
@ -28,11 +27,10 @@ pub mod committee {
false
};
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
let mut cs = vec![];
// pull it from a DB
@ -53,7 +51,7 @@ pub mod committee {
for (count, name) in cs {
let leading = if count < 10 { " " } else { "" };
let line = format!("{}{} {}", leading, count, name);
let line = format!("{leading}{count} {name}");
let length = line.len() + 1;
@ -85,22 +83,27 @@ 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};
use skynet_discord_bot::common::set_roles::committee::get_committees;
use skynet_discord_bot::get_now_iso;
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_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
let mut committees = HashMap::new();
for committee in get_committees(&db).await {
committees.insert(committee.id, committee.to_owned());
if let Some(x) = get_committees(&db).await {
for committee in x {
committees.insert(committee.id, committee.to_owned());
}
}
let mut cs = vec![];
@ -143,11 +146,11 @@ pub mod servers {
""
};
let line = format!("{}{} {}{} {}", current_leading, current, past_leading, past, name);
let line = format!("{current_leading}{current} {past_leading}{past} {name}");
let length = line.len() + 1;
// +3 is to account for the closing fense
// +3 is to account for the closing fence
if length < (limit + 3) {
response.push(line);
limit -= length;

View file

@ -9,19 +9,24 @@ pub(crate) mod user {
use super::*;
use crate::commands::wolves::link::get_server_member_discord;
use serde::{Deserialize, Serialize};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
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 serenity::{
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction},
model::id::UserId,
};
use skynet_discord_bot::{
common::{
database::Wolves,
minecraft::{whitelist_update, Minecraft},
},
Config,
};
use sqlx::Error;
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
let config_lock = {
let data_read = ctx.data.read().await;
@ -69,14 +74,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 {
@ -185,14 +190,17 @@ pub(crate) mod server {
use super::*;
pub(crate) mod add {
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption};
use serenity::model::id::GuildId;
use serenity::{
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption},
model::id::GuildId,
};
use sqlx::Error;
// this is to managfe the server side of commands related to minecraft
// this is to manage the server side of commands related to minecraft
use super::*;
use skynet_discord_bot::common::minecraft::update_server;
use skynet_discord_bot::common::minecraft::Minecraft;
use skynet_discord_bot::Config;
use skynet_discord_bot::{
common::minecraft::{update_server, Minecraft},
Config,
};
pub fn register() -> CreateCommand {
CreateCommand::new("minecraft_add")
@ -220,16 +228,15 @@ pub(crate) mod server {
return String::from("Expected Server ID");
};
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().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);
}
}
@ -260,12 +267,14 @@ pub(crate) mod server {
}
pub(crate) mod list {
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::Config;
use serenity::{all::CommandInteraction, builder::CreateCommand, client::Context};
use skynet_discord_bot::{
common::{
database::DataBase,
minecraft::{get_minecraft_config_server, server_information},
},
Config,
};
pub fn register() -> CreateCommand {
CreateCommand::new("minecraft_list")
@ -279,11 +288,10 @@ pub(crate) mod server {
Some(x) => x,
};
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
let servers = get_minecraft_config_server(&db, g_id).await;
@ -320,12 +328,13 @@ pub(crate) mod server {
}
pub(crate) mod delete {
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 serenity::{
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption},
builder::CreateCommand,
client::Context,
model::id::GuildId,
};
use skynet_discord_bot::common::{database::DataBase, minecraft::Minecraft};
use sqlx::{Error, Pool, Sqlite};
pub fn register() -> CreateCommand {
@ -354,16 +363,15 @@ pub(crate) mod server {
return String::from("Expected Server ID");
};
let db_lock = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
let db = {
let data = ctx.data.read().await;
data.get::<DataBase>().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);
}
}

View file

@ -3,4 +3,5 @@ pub mod committee;
pub mod count;
pub mod minecraft;
pub mod role_adder;
pub mod server_icon;
pub mod wolves;

View file

@ -62,11 +62,10 @@ pub mod edit {
false
};
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
let server = command.guild_id.unwrap_or_default();
let server_data = RoleAdder {
@ -79,8 +78,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:?}");
}
}
@ -101,9 +100,9 @@ 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}")
}
}
@ -142,13 +141,12 @@ pub mod edit {
pub mod list {}
pub mod tools {
use serenity::client::Context;
use serenity::model::guild::Member;
use serenity::{client::Context, model::guild::Member};
use skynet_discord_bot::common::database::RoleAdder;
use sqlx::{Pool, Sqlite};
pub async fn on_role_change(db: &Pool<Sqlite>, ctx: &Context, new_data: Member) {
// check if the role changed is part of the oens for this server
// check if the role changed is part of the ones for this server
if let Ok(role_adders) = sqlx::query_as::<_, RoleAdder>(
r#"
SELECT *
@ -164,7 +162,7 @@ pub mod tools {
let mut roles_remove = vec![];
for role_adder in role_adders {
// if the user has both A dnd B give them C
// if the user has both A and 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);
@ -180,13 +178,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:?}");
}
}
}

223
src/commands/server_icon.rs Normal file
View file

@ -0,0 +1,223 @@
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::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let config_lock = {
let data_read = ctx.data.read().await;
data_read.get::<Config>().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::<DataBase>().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<Sqlite>) -> Option<ServerIcons> {
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::<Config>().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::<DataBase>().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<Sqlite>) -> Vec<CountResult> {
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")
}
}
}

View file

@ -4,11 +4,16 @@ use lettre::{
Message, SmtpTransport, Transport,
};
use maud::html;
use serenity::all::CommandOptionType;
use serenity::builder::CreateCommandOption;
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 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 sqlx::{Pool, Sqlite};
pub mod link {
@ -16,11 +21,10 @@ pub mod link {
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
let config_lock = {
let data_read = ctx.data.read().await;
@ -96,7 +100,7 @@ pub mod link {
return "Email already verified".to_string();
}
// generate a auth key
// generate an auth key
let auth = random_string(20);
match send_mail(&config, &details.email, &auth, &command.user.name) {
Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await {
@ -110,7 +114,7 @@ pub mod link {
}
}
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)
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.")
}
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Wolves> {
@ -205,7 +209,7 @@ pub mod link {
.subject("Skynet: Link Discord to Wolves.")
.multipart(
// This is composed of two parts.
// also helps not trip spam settings (uneven number of url's
// also helps not trip spam settings (uneven number of urls)
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())),
@ -279,22 +283,40 @@ 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, GuildId, RoleId};
use serenity::model::user::User;
use skynet_discord_bot::common::database::get_server_config;
use skynet_discord_bot::common::database::{ServerMembersWolves, Servers};
use skynet_discord_bot::common::wolves::committees::Committees;
use serenity::{
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction},
model::user::User,
};
use skynet_discord_bot::common::{
database::{get_server_config, ServerMembersWolves, Servers},
wolves::committees::Committees,
};
use sqlx::Error;
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Database 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 {
@ -341,12 +363,12 @@ pub mod verify {
"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()
@ -403,7 +425,7 @@ pub mod verify {
}
if let Err(e) = member.add_roles(&ctx, &roles).await {
println!("{:?}", e);
println!("{e:?}");
}
}
}
@ -419,7 +441,7 @@ pub mod verify {
WHERE committee LIKE ?1
"#,
)
.bind(format!("%{}%", wolves_id))
.bind(format!("%{wolves_id}%"))
.fetch_all(db)
.await
.unwrap_or_else(|e| {
@ -429,12 +451,18 @@ pub mod verify {
}
async fn set_server_roles_committee(db: &Pool<Sqlite>, discord: &User, ctx: &Context) {
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;
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 the teh general committee role
// 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 = GuildId::new(1220150752656363520);
let committee_member = RoleId::new(1226602779968274573);
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();
@ -464,13 +492,12 @@ pub mod unlink {
use sqlx::{Pool, Sqlite};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
// dosent matter if there is one or not, it will be removed regardless
// 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()
@ -516,4 +543,5 @@ pub fn register() -> CreateCommand {
.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."))
}

View file

@ -1,17 +1,21 @@
use crate::Config;
use serde::{Deserialize, Serialize};
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;
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};
pub struct DataBase;
impl TypeMapKey for DataBase {
type Value = Arc<RwLock<Pool<Sqlite>>>;
type Value = Arc<Pool<Sqlite>>;
}
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -220,7 +224,7 @@ pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, 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),
)

View file

@ -1,10 +1,7 @@
use crate::common::set_roles::normal::get_server_member_bulk;
use crate::Config;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use crate::{common::set_roles::normal::get_server_member_bulk, Config};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serenity::model::id::GuildId;
use sqlx::sqlite::SqliteRow;
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
use sqlx::{sqlite::SqliteRow, Error, FromRow, Pool, Row, Sqlite};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Minecraft {
@ -27,7 +24,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 arent members
and a list that aren't members
*/
pub async fn update_server(server_id: &str, db: &Pool<Sqlite>, g_id: &GuildId, config: &Config) {
let mut usernames = vec![];
@ -112,7 +109,7 @@ pub async fn whitelist_wipe(server: &str, token: &str) {
};
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
// recreate the 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
@ -155,7 +152,7 @@ pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, 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}");

View file

@ -2,3 +2,6 @@ pub mod database;
pub mod minecraft;
pub mod set_roles;
pub mod wolves;
pub mod renderer;
pub mod server_icon;

193
src/common/renderer.rs Normal file
View file

@ -0,0 +1,193 @@
// 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<String>),
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<Self> {
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::<Vec<&str>>();
if s.len() < 2 {
dbg!("Invalid color object, try checking help");
return None;
}
Some((s[0].to_string(), s[1].to_string()))
})
.collect::<Vec<Option<(String, String)>>>();
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::<Vec<String>>();
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<String> {
match std::fs::read_to_string(fi) {
Ok(d) => Ok(d),
Err(_) => {
dbg!("File {fi:?} does not exist");
bail!("File {fi:?} does not exist");
}
}
}
}

404
src/common/server_icon.rs Normal file
View file

@ -0,0 +1,404 @@
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<ConfigTomlFestivals>,
}
#[derive(Deserialize)]
pub struct ConfigTomlLocal {
pub source: ConfigTomlSource,
}
#[derive(Deserialize)]
pub struct ConfigTomlRemote {
pub festivals: Vec<ConfigTomlFestivals>,
}
#[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::<ConfigTomlLocal>(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::<ConfigTomlRemote>(&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<Sqlite>, 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<String>,
exclusions: Vec<String>,
}
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<LogoData> {
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<LogoData>) -> Vec<LogoData> {
let mut filtered: Vec<LogoData> = 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<Sqlite>, 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<Sqlite>, 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
}
}

View file

@ -1,18 +1,27 @@
pub mod normal {
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 crate::{
common::database::{DataBase, ServerMembersWolves, Servers, Wolves},
get_now_iso,
};
use serenity::{
client::Context,
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<RoleId>], members_changed: &[UserId]) {
let db_lock = {
let db = {
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,
@ -20,7 +29,12 @@ pub mod normal {
..
} = server;
let mut roles_set = [0, 0, 0];
let mut roles_set = RolesChange {
total: 0,
new: 0,
current_add: 0,
current_rem: 0,
};
let mut members = vec![];
for member in get_server_member_bulk(&db, server).await {
@ -32,28 +46,30 @@ 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 teh users in it
// members_changed acts as an override to only deal with the 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[0] += 1;
roles_set.new += 1;
roles.push(role.to_owned());
}
}
if !member.roles.contains(role_current) {
roles_set[1] += 1;
roles_set.current_add += 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
@ -65,16 +81,16 @@ pub mod normal {
}
if member.roles.contains(role_current) {
roles_set[2] += 1;
// if theya re not a current member and have the role then remove it
roles_set.current_rem += 1;
// if they'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:?}");
}
}
}
@ -83,7 +99,14 @@ 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!("{:?} Changes: New: +{}, Current: +{}/-{}", server.get(), roles_set[0], roles_set[1], roles_set[2]);
println!(
"{:?} Total: {} Changes: New: +{}, Current: +{}/-{}",
server.get(),
roles_set.total,
roles_set.new,
roles_set.current_add,
roles_set.current_rem
);
}
pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
@ -123,7 +146,7 @@ pub mod normal {
Ok(_) => {}
Err(e) => {
println!("Failure to insert into {}", server.get());
println!("{:?}", e);
println!("{e:?}");
}
}
}
@ -131,58 +154,56 @@ pub mod normal {
// for updating committee members
pub mod committee {
use crate::common::database::{get_channel_from_row, get_role_from_row, DataBase, Wolves};
use crate::common::wolves::committees::Committees;
use crate::Config;
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, GuildId};
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::sqlite::SqliteRow;
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
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 std::collections::HashMap;
use std::sync::Arc;
pub async fn check_committee(ctx: Arc<Context>) {
let db_lock = {
pub async fn check_committee(ctx: &Context) {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().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::<Config>().expect("Expected Config in TypeMap.").clone()
};
let config_global = config_lock.read().await;
let server = GuildId::new(1220150752656363520);
let server = config_global.committee_server;
// 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 tehm the appropiate roles on teh committee server
This function can take a Vec of members (or just one) and gives them the appropriate roles on teh committee server
*/
pub async fn update_committees(db: &Pool<Sqlite>, ctx: &Context, _config: &Config, members: &mut Vec<Member>) {
let server = GuildId::new(1220150752656363520);
let committee_member = RoleId::new(1226602779968274573);
let committees = get_committees(db).await;
let categories = [
ChannelId::new(1226606560973815839),
// C&S Chats 2
ChannelId::new(1341457244973305927),
// C&S Chats 3
ChannelId::new(1341457509717639279),
];
pub async fn update_committees(db: &Pool<Sqlite>, ctx: &Context, config: &Config, members: &mut Vec<Member>) {
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();
// information about the server
let mut roles_db = HashMap::new();
@ -203,11 +224,11 @@ pub mod committee {
let mut channels = server.channels(&ctx).await.unwrap_or_default();
// a map of users and the roles they are goign to be getting
// a map of users and the roles they are going to be getting
let mut users_roles = HashMap::new();
let mut re_order = false;
// we need to create roles and channels if tehy dont already exist
// we need to create roles and channels if they don't already exist
let mut category_index = 0;
let mut i = 0;
loop {
@ -308,21 +329,21 @@ pub mod committee {
// 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) => {
let mut tmp = x.to_owned();
if !tmp.is_empty() {
tmp.push(committee_member);
}
tmp
}
Some(x) => x.to_owned(),
};
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
@ -331,14 +352,25 @@ 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());
}
}
if !roles_required.is_empty() {
// if there are committee roles then give the general purporse role
roles_add.push(committee_member);
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;
}
@ -357,8 +389,6 @@ 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();
}
}
@ -406,10 +436,10 @@ pub mod committee {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CommitteeRoles {
id_wolves: i64,
id_role: RoleId,
id_channel: ChannelId,
pub id_role: RoleId,
pub id_channel: ChannelId,
pub name_role: String,
name_channel: String,
pub name_channel: String,
pub count: i64,
}
@ -446,8 +476,8 @@ pub mod committee {
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Wolves {:?}", role);
println!("{:?}", e);
println!("Failure to insert into Wolves {role:?}");
println!("{e:?}");
}
}
}
@ -464,13 +494,13 @@ pub mod committee {
.await
.unwrap_or_else(|e| {
println!("Failure to get Roles from committee_roles");
println!("{:?}", e);
println!("{e:?}");
vec![]
})
}
pub async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
sqlx::query_as::<_, Committees>(
pub async fn get_committees(db: &Pool<Sqlite>) -> Option<Vec<Committees>> {
match sqlx::query_as::<_, Committees>(
r#"
SELECT *
FROM committees
@ -478,10 +508,13 @@ pub mod committee {
)
.fetch_all(db)
.await
.unwrap_or_else(|e| {
dbg!(e);
vec![]
})
{
Ok(x) => Some(x),
Err(e) => {
dbg!(e);
None
}
}
}
async fn get_server_member_discord(db: &Pool<Sqlite>, user: &i64) -> Option<Wolves> {

View file

@ -38,8 +38,8 @@ async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUserMin) {
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Wolves {:?}", user);
println!("{:?}", e);
println!("Failure to insert into Wolves {user:?}");
println!("{e:?}");
}
}
}
@ -48,12 +48,14 @@ async fn add_users_wolves(db: &Pool<Sqlite>, 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};
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 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 sqlx::{Pool, Sqlite};
use std::collections::BTreeMap;
@ -67,11 +69,10 @@ pub mod cns {
}
pub async fn get_wolves(ctx: &Context) {
let db_lock = {
let db = {
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;
@ -96,7 +97,6 @@ pub mod cns {
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![];
let mut server_name_tmp = None;
for user in wolves.get_members(wolves_api).await {
// dbg!(&user.committee);
@ -115,10 +115,6 @@ 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);
}
}
}
}
@ -129,9 +125,6 @@ pub mod cns {
set_server_member(&db, server, cs_id).await;
}
}
if !user_to_update.is_empty() {
update_server(ctx, &server_config, &[], &user_to_update).await;
}
}
}
@ -151,7 +144,7 @@ pub mod cns {
Ok(_) => {}
Err(e) => {
println!("Failure to set server name {}", server.get());
println!("{:?}", e);
println!("{e:?}");
}
}
}
@ -190,7 +183,7 @@ pub mod cns {
Ok(_) => {}
Err(e) => {
println!("Failure to insert into ServerMembers {} {:?}", server.get(), user);
println!("{:?}", e);
println!("{e:?}");
}
}
}
@ -200,8 +193,7 @@ pub mod cns {
Get and store the data on C&S committees
*/
pub mod committees {
use crate::common::database::DataBase;
use crate::Config;
use crate::{common::database::DataBase, Config};
use serenity::client::Context;
use sqlx::{Pool, Sqlite};
@ -231,11 +223,10 @@ pub mod committees {
}
pub async fn get_cns(ctx: &Context) {
let db_lock = {
let db = {
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;
@ -270,8 +261,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:?}");
}
}
}

View file

@ -3,8 +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};
use serenity::prelude::TypeMapKey;
use serenity::{
model::id::{ChannelId, GuildId, RoleId},
prelude::TypeMapKey,
};
use std::{env, sync::Arc};
use tokio::sync::RwLock;
@ -32,7 +34,10 @@ pub struct Config {
// discord server for committee
pub committee_server: GuildId,
pub committee_role: RoleId,
pub committee_category: ChannelId,
pub committee_category: Vec<ChannelId>,
// items pertaining to CompSoc only
pub compsoc_server: GuildId,
}
impl TypeMapKey for Config {
type Value = Arc<RwLock<Config>>;
@ -57,7 +62,8 @@ pub fn get_config() -> Config {
wolves_api: "".to_string(),
committee_server: GuildId::new(1),
committee_role: RoleId::new(1),
committee_category: ChannelId::new(1),
committee_category: vec![],
compsoc_server: GuildId::new(1),
};
if let Ok(x) = env::var("DATABASE_HOME") {
@ -99,14 +105,22 @@ pub fn get_config() -> Config {
config.committee_server = GuildId::new(x);
}
}
if let Ok(x) = env::var("COMMITTEE_DISCORD") {
if let Ok(x) = env::var("COMMITTEE_ROLE") {
if let Ok(x) = x.trim().parse::<u64>() {
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::<u64>() {
config.committee_category.push(ChannelId::new(x));
}
}
}
if let Ok(x) = env::var("COMPSOC_DISCORD") {
if let Ok(x) = x.trim().parse::<u64>() {
config.committee_category = ChannelId::new(x);
config.compsoc_server = GuildId::new(x);
}
}

View file

@ -1,21 +1,28 @@
pub mod commands;
use crate::commands::role_adder::tools::on_role_change;
use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildId, 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};
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 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 sqlx::{Pool, Sqlite};
use std::sync::Arc;
use tokio::sync::RwLock;
@ -24,15 +31,21 @@ 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<GuildId>) {
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_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().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::<Config>().expect("Expected Config in TypeMap.").clone()
@ -40,7 +53,7 @@ impl EventHandler for Handler {
let config_global = config_lock.read().await;
// committee server takes priority
let committee_server = GuildId::new(1220150752656363520);
let committee_server = config_global.committee_server;
if new_member.guild_id.get() == committee_server.get() {
let mut member = vec![new_member.clone()];
update_committees(&db, &ctx, &config_global, &mut member).await;
@ -66,7 +79,7 @@ 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;
@ -94,14 +107,12 @@ 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<Member>, new_data: Option<Member>, _: GuildMemberUpdateEvent) {
// get config/db
let db_lock = {
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
};
let db = db_lock.read().await;
// check if the role changed is part of the oens for this server
// check if the role changed is part of the ones for this server
if let Some(x) = new_data {
on_role_change(&db, &ctx, x).await;
}
@ -111,6 +122,12 @@ 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::<Config>().expect("Expected Config in TypeMap.").clone()
};
let config = config_lock.read().await;
match Command::set_global_commands(
&ctx.http,
vec![
@ -125,27 +142,34 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
{
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
println!("{e:?}")
}
}
match GuildId::new(1220150752656363520)
.set_commands(&ctx.http, vec![commands::count::committee::register()])
// 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)
}
}
match GuildId::new(689189992417067052)
.set_commands(&ctx.http, vec![commands::count::servers::register()])
.await
{
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
println!("{e:?}")
}
}
}
@ -153,7 +177,7 @@ 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
@ -164,6 +188,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
"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()),
},
@ -175,6 +200,19 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
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()),
},
@ -191,11 +229,34 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
&_ => 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()),
};
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}");
}
}
}
@ -230,7 +291,8 @@ 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 {})
.event_handler(Handler)
.cache_settings(serenity::cache::Settings::default())
.await
.expect("Error creating client");
@ -238,7 +300,7 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
data.insert::<DataBase>(Arc::new(db));
}
// Finally, start a single shard, and start listening to events.
@ -246,6 +308,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:?}");
}
}