forked from Skynet/discord-bot
Compare commits
122 commits
new-member
...
main
Author | SHA1 | Date | |
---|---|---|---|
421d425f5d | |||
76cddde36d | |||
f307fcea43 | |||
df032f2d7b | |||
0e6a5d3455 | |||
052f6aecb2 | |||
aa58c97fcf | |||
3a39084f40 | |||
058f8a7a7d | |||
b43f760fb1 | |||
a11ba15f4a | |||
7d7afcd00c | |||
934842cbc9 | |||
143483d3b3 | |||
0bedf96da5 | |||
555e38ee26 | |||
6a5f651ba2 | |||
f046410cdc | |||
44bb40d96d | |||
7406f0e620 | |||
09ce45f70f | |||
b67894fc6e | |||
6481fcb89f | |||
9ce5b8136b | |||
348020ecfe | |||
8645a9b3ce | |||
0eba54b6f2 | |||
25fcc04287 | |||
c79944bd6e | |||
262eb0c991 | |||
30287466cb | |||
86f71f0fa4 | |||
6b84f33d2e | |||
a8c1cc9cf1 | |||
8c81fb864a | |||
b6cffd8a22 | |||
1aef86ad01 | |||
4eeb7f2135 | |||
cab04a068f | |||
c79113921d | |||
5fcc24a867 | |||
0a4f5281a5 | |||
a9d3af024e | |||
b7d36de976 | |||
68ffa55dc5 | |||
9b3c71e321 | |||
0478f634fa | |||
ee0c8f0987 | |||
b55650b221 | |||
4691869ae9 | |||
68d7b53905 | |||
bf55dfe31e | |||
ad94b197ae | |||
1f3c33458e | |||
bab6e4fdec | |||
f00db7ef5d | |||
37ea38f516 | |||
ca55a78447 | |||
93359698f0 | |||
dda05d7ca1 | |||
5dee9acbaa | |||
96a61e6fc8 | |||
94292fa388 | |||
77a7b7b81d | |||
2f75dc41c8 | |||
c98baa9d72 | |||
e4a8cce725 | |||
5b22f699d6 | |||
6739c7e068 | |||
d673dce6fa | |||
015f23b922 | |||
7a6421469c | |||
733827c3e6 | |||
2daa010d25 | |||
da4d006bc0 | |||
344d6d3585 | |||
b7161e2614 | |||
32249364ff | |||
f1138a3c81 | |||
61e76db8dd | |||
3e6dc9d560 | |||
aff6299ac7 | |||
bd80bda22f | |||
fe5aa5b252 | |||
273c58d035 | |||
3927734083 | |||
41407ecefb | |||
79f880daea | |||
ceade9b972 | |||
b2d8238c17 | |||
a7e8f5698e | |||
80c9191eee | |||
1c3ccbecf5 | |||
d1af8a7c1f | |||
0d9ce2de7f | |||
5e7964ae26 | |||
32292a3c0b | |||
ffce78a10d | |||
7980739627 | |||
2aad895bb3 | |||
ec74dc0aa7 | |||
42f301455a | |||
5b21bc47bd | |||
2db136a736 | |||
72c4b1e794 | |||
5e17a98bff | |||
0ab290a876 | |||
a62f893a98 | |||
43c5cd2eff | |||
d9211dca9a | |||
c71dbe7214 | |||
bf08aa650c | |||
f3ef03a418 | |||
11240914ac | |||
e9aed40f41 | |||
0df7c8a29f | |||
4998dba225 | |||
8d1c6b1bd1 | |||
d3a975a1d1 | |||
0e1a7d56b6 | |||
fd32adb138 | |||
04a487cd8f |
38 changed files with 3880 additions and 2112 deletions
|
@ -19,6 +19,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# get the repo first
|
# get the repo first
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
|
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3
|
||||||
|
with:
|
||||||
|
repository: ${{ gitea.repository }}
|
||||||
|
ref_name: ${{ gitea.ref_name }}
|
||||||
- run: nix build .#fmt --verbose
|
- run: nix build .#fmt --verbose
|
||||||
|
|
||||||
# clippy is incredibly useful for making yer code better
|
# clippy is incredibly useful for making yer code better
|
||||||
|
@ -30,6 +34,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# get the repo first
|
# get the repo first
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
|
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3
|
||||||
|
with:
|
||||||
|
repository: ${{ gitea.repository }}
|
||||||
|
ref_name: ${{ gitea.ref_name }}
|
||||||
- run: nix build .#clippy --verbose
|
- run: nix build .#clippy --verbose
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
@ -39,6 +47,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# get the repo first
|
# get the repo first
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
|
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3
|
||||||
|
with:
|
||||||
|
repository: ${{ gitea.repository }}
|
||||||
|
ref_name: ${{ gitea.ref_name }}
|
||||||
- name: "Build it locally"
|
- name: "Build it locally"
|
||||||
run: nix build --verbose
|
run: nix build --verbose
|
||||||
|
|
||||||
|
@ -49,7 +61,7 @@ jobs:
|
||||||
needs: [ build ]
|
needs: [ build ]
|
||||||
steps:
|
steps:
|
||||||
- name: "Deploy to Skynet"
|
- name: "Deploy to Skynet"
|
||||||
uses: https://forgejo.skynet.ie/Skynet/actions-deploy-to-skynet@v2
|
uses: https://forgejo.skynet.ie/Skynet/actions/deploy@v3
|
||||||
with:
|
with:
|
||||||
input: 'skynet_discord_bot'
|
input: 'skynet_discord_bot'
|
||||||
token: ${{ secrets.API_TOKEN_FORGEJO }}
|
token: ${{ secrets.API_TOKEN_FORGEJO }}
|
37
.gitattributes
vendored
Normal file
37
.gitattributes
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Git config here
|
||||||
|
* text eol=lf
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# Git lfs stuff
|
||||||
|
# Documents
|
||||||
|
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.doc filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.docx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
# Excel
|
||||||
|
*.xls filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.xlsx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.xlsm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
# Powerpoints
|
||||||
|
*.ppt filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pptx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ppsx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
# Images
|
||||||
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
# Video
|
||||||
|
*.mkv filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.wmv filter=lfs diff=lfs merge=lfs -text
|
||||||
|
# Misc
|
||||||
|
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
|
# ET4011
|
||||||
|
*.cbe filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pbs filter=lfs diff=lfs merge=lfs -text
|
||||||
|
# Open/Libre office
|
||||||
|
# from https://www.libreoffice.org/discover/what-is-opendocument/
|
||||||
|
*.odt filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ods filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.odp filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.odg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
# QT
|
||||||
|
*.ui filter=lfs diff=lfs merge=lfs -text
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,11 +2,13 @@
|
||||||
/.idea
|
/.idea
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
*.env
|
||||||
|
|
||||||
result
|
result
|
||||||
/result
|
/result
|
||||||
|
|
||||||
*.db
|
*.db
|
||||||
|
*.db.*
|
||||||
|
|
||||||
tmp/
|
tmp/
|
||||||
tmp.*
|
tmp.*
|
||||||
|
|
1373
Cargo.lock
generated
1373
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
28
Cargo.toml
28
Cargo.toml
|
@ -11,30 +11,38 @@ name = "update_data"
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "update_users"
|
name = "update_users"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "update_committee"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "update_minecraft"
|
name = "update_minecraft"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# discord library
|
# discord library
|
||||||
serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
||||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread", "full"] }
|
||||||
|
|
||||||
|
# wolves api
|
||||||
|
wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git", features = ["unstable"] }
|
||||||
|
# wolves_oxidised = { path = "../wolves-oxidised", features = ["unstable"] }
|
||||||
|
|
||||||
# to make the http requests
|
# to make the http requests
|
||||||
surf = "2.3.2"
|
surf = "2.3"
|
||||||
|
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15"
|
||||||
|
|
||||||
# For sqlite
|
# For sqlite
|
||||||
sqlx = { version = "0.7.1", 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
|
# create random strings
|
||||||
rand = "0.8.5"
|
rand = "0.9"
|
||||||
|
|
||||||
# fancy time stuff
|
# fancy time stuff
|
||||||
chrono = "0.4.26"
|
chrono = "0.4"
|
||||||
|
|
||||||
# for email
|
# for email
|
||||||
lettre = "0.10.4"
|
lettre = "0.11"
|
||||||
maud = "0.25.0"
|
maud = "0.27"
|
||||||
|
|
||||||
serde = "1.0.188"
|
serde = "1.0"
|
9
LICENSE
Normal file
9
LICENSE
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Skynet
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
74
README.md
74
README.md
|
@ -1,70 +1,10 @@
|
||||||
# Skynet Discord Bot
|
# Skynet Discord Bot
|
||||||
This bots core purpose is to give members roles based on their status on <ulwolves.ie>.
|
The Skynet bot is designed to manage users on Discord.
|
||||||
It uses an api key provided by wolves to get member lists.
|
It allows users to link their UL Wolves account with Wolves in a GDPR compliant manner.
|
||||||
|
Skynet (bot) is hosted is hosted by the Computer Society on Skynet (computer cluster).
|
||||||
|
|
||||||
Users are able to link their wolves account to the bot and that works across discord servers.
|
## Documentation
|
||||||
For example is a user links on the CompSoc Discord then they will also get their roles (automagically) on Games Dev if they are a member there.
|
We have split up the documentation into different segments depending on who the user is.
|
||||||
|
|
||||||
## Commands - Admin
|
* [Committees](./doc/Committee.md)
|
||||||
|
* [Member](./doc/User.md)
|
||||||
You need admin access to run any of the commands in this section.
|
|
||||||
Either the server owner or a suer with the ``Administrator`` permission
|
|
||||||
|
|
||||||
### Getting the Skynet Discord bot
|
|
||||||
1. Email ``keith@assurememberships.com`` from committee email and say ye want an api key for ``193.1.99.74``
|
|
||||||
2. Create a role for current members (maybe call it ``current-member`` ?)
|
|
||||||
3. (Optional) create a role for all past and current members (ye can use the existing ``member`` role for this, )
|
|
||||||
4. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot
|
|
||||||
5. Make sure the bot role ``@skynet`` is above these two roles (so it can manage them)
|
|
||||||
6. Make sure that you have a role that gives ye administrator powers
|
|
||||||
7. Use the command ``/add`` and insert the api key, role current and role all (desktop recommended)
|
|
||||||
|
|
||||||
The reason for both roles is ye have one for active members while the second is for all current and past members.
|
|
||||||
|
|
||||||
### Minecraft
|
|
||||||
The bot is able to manage the whitelist of a Minecraft server managed by the Computer Society.
|
|
||||||
Talk to us to get a server.
|
|
||||||
|
|
||||||
#### Add
|
|
||||||
This links a minecraft server with your club/society.
|
|
||||||
|
|
||||||
``/minecraft_add SERVER_ID``
|
|
||||||
|
|
||||||
|
|
||||||
#### List
|
|
||||||
List the servers linked to your club/society.
|
|
||||||
It is possible to have more than one minecraft server
|
|
||||||
|
|
||||||
``/minecraft_list``
|
|
||||||
|
|
||||||
#### Delete
|
|
||||||
This unlinks a minecraft server from your club/society.
|
|
||||||
|
|
||||||
``/minecraft_delete SERVER_ID``
|
|
||||||
|
|
||||||
## Commands - User
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
* Start the process using ``/link_wolves WOLVES_EMAIL``
|
|
||||||
* The email that is in the Contact Email here: <https://ulwolves.ie/memberships/profile>
|
|
||||||
* An email will be sent to them that they need to verify using ``/verify CODE``
|
|
||||||
* This will only have to be done once.
|
|
||||||
|
|
||||||
* If the user is an active member on wolves
|
|
||||||
* If they are in any servers with teh Skynet Bot
|
|
||||||
* They will get relevant roles.
|
|
||||||
* If they Join a server with teh bot enabled.
|
|
||||||
* They will be granted the roles automatically
|
|
||||||
* If the user is **not** an active member on wolves.
|
|
||||||
* If they have no Roles
|
|
||||||
* No change
|
|
||||||
* If they have Past Member Role
|
|
||||||
* No change
|
|
||||||
* If they have both Roles
|
|
||||||
* The current-member role will be removed from them
|
|
||||||
* Past Member role will remain unchanged
|
|
||||||
|
|
||||||
### Minecraft
|
|
||||||
Users can link their Minecraft username to grant them access to any servers where teh whitelist is managed by teh bot.
|
|
||||||
|
|
||||||
``/link_minecraft MINECRAFT_USERNAME``
|
|
10
db/migrations/10_member_committee-roles.sql
Normal file
10
db/migrations/10_member_committee-roles.sql
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS committee_roles (
|
||||||
|
id_wolves integer PRIMARY KEY,
|
||||||
|
id_role integer DEFAULT 1,
|
||||||
|
id_channel integer DEFAULT 1,
|
||||||
|
-- not strictly required but for readability and debugging
|
||||||
|
name_role text NOT NULL DEFAULT '',
|
||||||
|
name_channel text NOT NULL DEFAULT '',
|
||||||
|
count integer DEFAULT 0
|
||||||
|
);
|
5
db/migrations/5_welcome-message.sql
Normal file
5
db/migrations/5_welcome-message.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
-- temp table to allow folks to verify by committee email.
|
||||||
|
-- delete the col in teh server table
|
||||||
|
ALTER TABLE servers ADD COLUMN bot_channel_id integer DEFAULT 0;
|
||||||
|
ALTER TABLE servers ADD COLUMN server_name text NOT NULL DEFAULT "";
|
||||||
|
ALTER TABLE servers ADD COLUMN wolves_link text NOT NULL DEFAULT "";
|
11
db/migrations/6_role-adder.sql
Normal file
11
db/migrations/6_role-adder.sql
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS roles_adder (
|
||||||
|
server integer not null,
|
||||||
|
role_a integer not null,
|
||||||
|
role_b integer not null,
|
||||||
|
role_c integer not null,
|
||||||
|
PRIMARY KEY(server,role_a,role_b,role_c)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS index_roles_adder_server ON roles_adder (server);
|
||||||
|
CREATE INDEX IF NOT EXISTS index_roles_adder_from ON roles_adder (role_a,role_b);
|
||||||
|
CREATE INDEX IF NOT EXISTS index_roles_adder_to ON roles_adder (role_c);
|
||||||
|
CREATE INDEX IF NOT EXISTS index_roles_adder_search ON roles_adder (server,role_a,role_b);
|
3
db/migrations/7_minecraft-bedrock.sql
Normal file
3
db/migrations/7_minecraft-bedrock.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
-- Using this col we will be able to see if someone has a bedrock account (as well as a java one)
|
||||||
|
ALTER TABLE wolves ADD COLUMN minecraft_uid TEXT;
|
14
db/migrations/8_committee-mk-ii.sql
Normal file
14
db/migrations/8_committee-mk-ii.sql
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
-- No longer using the previous committee table
|
||||||
|
DROP TABLE committee;
|
||||||
|
|
||||||
|
-- new table pulling from teh api
|
||||||
|
CREATE TABLE IF NOT EXISTS committees (
|
||||||
|
id integer PRIMARY KEY,
|
||||||
|
name_profile text not null,
|
||||||
|
name_plain text not null,
|
||||||
|
name_full text not null,
|
||||||
|
link text not null,
|
||||||
|
committee text not null
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE servers DROP COLUMN wolves_link;
|
6
db/migrations/9_member_committee-id.sql
Normal file
6
db/migrations/9_member_committee-id.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-- No need for this col since it is goign to be in "committees" anyways
|
||||||
|
ALTER TABLE servers DROP COLUMN server_name;
|
||||||
|
|
||||||
|
-- we do care about teh ID of the club/soc though
|
||||||
|
ALTER TABLE servers ADD COLUMN wolves_id integer DEFAULT 0;
|
||||||
|
|
71
doc/Committee.md
Normal file
71
doc/Committee.md
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# Skynet Discord Bot
|
||||||
|
This bots core purpose is to give members roles based on their status on <https://ulwolves.ie>.
|
||||||
|
It uses an api key provided by wolves to get member lists.
|
||||||
|
|
||||||
|
Users are able to link their wolves account to the bot and that works across discord servers.
|
||||||
|
For example is a user links on the CompSoc Discord then they will also get their roles (automagically) on Games Dev if they are a member there.
|
||||||
|
|
||||||
|
## Setup - Committee
|
||||||
|
You need admin access to run any of the commands in this section.
|
||||||
|
Either the server owner or a user with the ``Manage Server`` permission.
|
||||||
|
|
||||||
|
### Get the API Key
|
||||||
|
The ``api_key`` is used by the Bot in order to request information, it will be used later in the process.
|
||||||
|
|
||||||
|
1. Email ``keith@assurememberships.com`` from committee email and say you want an ``api_key`` for ``193.1.99.74``
|
||||||
|
* The committee email is the one here: <https://cp.ulwolves.ie/mailbox/>
|
||||||
|
* This may take up to a week to get the key.
|
||||||
|
|
||||||
|
### Setup Server
|
||||||
|
The Bot reason for existing is being able to give members Roles.
|
||||||
|
So we have to create those.
|
||||||
|
|
||||||
|
1. Create a role for Current Members.
|
||||||
|
* You can call it whatever you want.
|
||||||
|
* ``member-current`` is a good choice.
|
||||||
|
* This should be a new role
|
||||||
|
2. **Optional**: you can create a role that is given to folks who were but no longer a member.
|
||||||
|
* ``member`` would be a good choice for this
|
||||||
|
* If you have an existing member role this is also a good fit.
|
||||||
|
|
||||||
|
The reason for both roles is ye have one for active members while the second is for all current and past members.
|
||||||
|
|
||||||
|
### Invite Bot
|
||||||
|
1. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot
|
||||||
|
2. Make sure the bot role ``@skynet`` is above these two roles created in the previous step
|
||||||
|
* This is so it can manage the roles (give and remove them from users)
|
||||||
|
|
||||||
|
### Setup Bot
|
||||||
|
This is where the bot is configured.
|
||||||
|
You will need the ``api_key`` from the start of the process.
|
||||||
|
You (personally) will need a role with ``Manage Server`` permission to be able to do this.
|
||||||
|
|
||||||
|
1. Use the command ``/committee add`` and a list of options will pop up.
|
||||||
|
2. ``api_key`` is the key you got from Keith earlier.
|
||||||
|
3. ``role_current`` is the ``member-current`` that you created earlier.
|
||||||
|
4. ``role_past`` (optional) is the role for all current and past members.
|
||||||
|
5. ``bot_channel`` is a channel that folks are recommended to use the bot.
|
||||||
|
* You can have it so folks cannot see message history
|
||||||
|
|
||||||
|
At this point the bot is set up and no further action is required.
|
||||||
|
|
||||||
|
### Minecraft
|
||||||
|
The bot is able to manage the whitelist of a Minecraft server managed by the Computer Society.
|
||||||
|
Talk to us to get a server.
|
||||||
|
|
||||||
|
#### Add
|
||||||
|
This links a minecraft server with your club/society.
|
||||||
|
|
||||||
|
``/minecraft_add SERVER_ID``
|
||||||
|
|
||||||
|
|
||||||
|
#### List
|
||||||
|
List the servers linked to your club/society.
|
||||||
|
It is possible to have more than one minecraft server
|
||||||
|
|
||||||
|
``/minecraft_list``
|
||||||
|
|
||||||
|
#### Delete
|
||||||
|
This unlinks a minecraft server from your club/society.
|
||||||
|
|
||||||
|
``/minecraft_delete SERVER_ID``
|
26
doc/User.md
Normal file
26
doc/User.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Skynet Discord Bot
|
||||||
|
The Skynet bot is designed to make it easy to verify that you are a member of a Club/Society.
|
||||||
|
The bot will be able to give you member roles for any partnered servers.
|
||||||
|
It also provides secondary manifests such as granting access to minecraft servers managed by teh Computer Society.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
This is to link your Discord account with your UL Wolves account.
|
||||||
|
**You will only need to do this once**.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
1. In a Discord server with the Skynet Bot enter ``/wolves link YOUR_WOLVES_CONTACT_EMAIL``
|
||||||
|
<img src="../media/setup_user_01.png" alt="link process start" width="50%" height="50%">
|
||||||
|
* Your ``YOUR_WOLVES_CONTACT_EMAIL`` is the email in the Contact Email here: <https://ulwolves.ie/memberships/profile>
|
||||||
|
* This is most likely your student mail
|
||||||
|
2. An email will be sent to you with a verification code.
|
||||||
|
<img src="../media/setup_user_02.png" alt="signup email" width="50%" height="50%">
|
||||||
|
3. Verify the code using ``/wolves verify CODE_FROM_EMAIL`` in Discord.
|
||||||
|
<img src="../media/setup_user_03.png" alt="verify in discord" width="50%" height="50%">
|
||||||
|
4. Once complete your Wolves and Discord accounts will be linked.
|
||||||
|
|
||||||
|
You will get member roles on any Discord that is using the bot that you are a member of.
|
||||||
|
|
||||||
|
### Minecraft
|
||||||
|
You can link your Minecraft username to grant you access to any Minecraft server run by UL Computer Society.
|
||||||
|
|
||||||
|
``/wolves link_minecraft MINECRAFT_USERNAME``
|
17
flake.nix
17
flake.nix
|
@ -20,6 +20,7 @@
|
||||||
}:
|
}:
|
||||||
utils.lib.eachDefaultSystem (
|
utils.lib.eachDefaultSystem (
|
||||||
system: let
|
system: let
|
||||||
|
overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
|
||||||
pkgs = (import nixpkgs) {inherit system;};
|
pkgs = (import nixpkgs) {inherit system;};
|
||||||
naersk' = pkgs.callPackage naersk {};
|
naersk' = pkgs.callPackage naersk {};
|
||||||
package_name = "skynet_discord_bot";
|
package_name = "skynet_discord_bot";
|
||||||
|
@ -62,7 +63,15 @@
|
||||||
|
|
||||||
# `nix develop`
|
# `nix develop`
|
||||||
devShell = pkgs.mkShell {
|
devShell = pkgs.mkShell {
|
||||||
nativeBuildInputs = with pkgs; [rustc cargo pkg-config openssl rustfmt];
|
nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook pkg-config openssl];
|
||||||
|
# libraries here
|
||||||
|
buildInputs = [ ];
|
||||||
|
RUSTC_VERSION = overrides.toolchain.channel;
|
||||||
|
# https://github.com/rust-lang/rust-bindgen#environment-variables
|
||||||
|
shellHook = ''
|
||||||
|
export PATH="''${CARGO_HOME:-~/.cargo}/bin":"$PATH"
|
||||||
|
export PATH="''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-${pkgs.stdenv.hostPlatform.rust.rustcTarget}/bin":"$PATH"
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
nixosModule = {
|
nixosModule = {
|
||||||
|
@ -75,7 +84,7 @@
|
||||||
cfg = config.services."${package_name}";
|
cfg = config.services."${package_name}";
|
||||||
# secret options are in the env file(s) loaded separately
|
# secret options are in the env file(s) loaded separately
|
||||||
environment_config = {
|
environment_config = {
|
||||||
HOME = cfg.home;
|
DATABASE_HOME = cfg.home;
|
||||||
DATABASE = "database.db";
|
DATABASE = "database.db";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,9 +128,11 @@
|
||||||
# modify these
|
# modify these
|
||||||
scripts = {
|
scripts = {
|
||||||
# every 20 min
|
# every 20 min
|
||||||
"update_data" = "*:0,20,40";
|
"update_data" = "*:0,10,20,30,40,50";
|
||||||
# groups are updated every hour, offset from teh ldap
|
# groups are updated every hour, offset from teh ldap
|
||||||
"update_users" = "*:05:00";
|
"update_users" = "*:05:00";
|
||||||
|
# Committee server has its own timer
|
||||||
|
"update_committee" = "*:15:00";
|
||||||
# minecraft stuff is updated at 5am
|
# minecraft stuff is updated at 5am
|
||||||
"update_minecraft" = "5:10:00";
|
"update_minecraft" = "5:10:00";
|
||||||
};
|
};
|
||||||
|
|
BIN
media/setup_user_01.png
(Stored with Git LFS)
Normal file
BIN
media/setup_user_01.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
media/setup_user_02.png
(Stored with Git LFS)
Normal file
BIN
media/setup_user_02.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
media/setup_user_03.png
(Stored with Git LFS)
Normal file
BIN
media/setup_user_03.png
(Stored with Git LFS)
Normal file
Binary file not shown.
54
src/bin/update_committee.rs
Normal file
54
src/bin/update_committee.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use serenity::{
|
||||||
|
async_trait,
|
||||||
|
client::{Context, EventHandler},
|
||||||
|
model::gateway::{GatewayIntents, Ready},
|
||||||
|
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 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 {})
|
||||||
|
.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(RwLock::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);
|
||||||
|
|
||||||
|
// u[date committee server
|
||||||
|
committee::check_committee(Arc::clone(&ctx)).await;
|
||||||
|
|
||||||
|
// finish up
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,10 @@ use serenity::{
|
||||||
model::gateway::{GatewayIntents, Ready},
|
model::gateway::{GatewayIntents, Ready},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{db_init, get_config, get_data::get_wolves, Config, DataBase};
|
use skynet_discord_bot::common::database::{db_init, DataBase};
|
||||||
|
use skynet_discord_bot::common::wolves::cns::get_wolves;
|
||||||
|
use skynet_discord_bot::common::wolves::committees::get_cns;
|
||||||
|
use skynet_discord_bot::{get_config, Config};
|
||||||
use std::{process, sync::Arc};
|
use std::{process, sync::Arc};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
@ -13,7 +16,10 @@ async fn main() {
|
||||||
let config = get_config();
|
let config = get_config();
|
||||||
let db = match db_init(&config).await {
|
let db = match db_init(&config).await {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(_) => return,
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use
|
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use
|
||||||
|
@ -43,8 +49,12 @@ impl EventHandler for Handler {
|
||||||
let ctx = Arc::new(ctx);
|
let ctx = Arc::new(ctx);
|
||||||
println!("{} is connected!", ready.user.name);
|
println!("{} is connected!", ready.user.name);
|
||||||
|
|
||||||
|
// get the data for each individual club/soc
|
||||||
get_wolves(&ctx).await;
|
get_wolves(&ctx).await;
|
||||||
|
|
||||||
|
// get teh data for the clubs/socs committees
|
||||||
|
get_cns(&ctx).await;
|
||||||
|
|
||||||
// finish up
|
// finish up
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use skynet_discord_bot::{db_init, get_config, get_minecraft_config, update_server, whitelist_wipe};
|
use skynet_discord_bot::common::database::db_init;
|
||||||
|
use skynet_discord_bot::common::minecraft::{get_minecraft_config, update_server, whitelist_wipe};
|
||||||
|
use skynet_discord_bot::get_config;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
|
@ -4,7 +4,9 @@ use serenity::{
|
||||||
model::gateway::{GatewayIntents, Ready},
|
model::gateway::{GatewayIntents, Ready},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles, Config, DataBase};
|
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 std::{process, sync::Arc};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
@ -43,14 +45,15 @@ impl EventHandler for Handler {
|
||||||
let ctx = Arc::new(ctx);
|
let ctx = Arc::new(ctx);
|
||||||
println!("{} is connected!", ready.user.name);
|
println!("{} is connected!", ready.user.name);
|
||||||
|
|
||||||
bulk_check(Arc::clone(&ctx)).await;
|
// this goes into each server and sets roles for each wolves member
|
||||||
|
check_bulk(Arc::clone(&ctx)).await;
|
||||||
|
|
||||||
// finish up
|
// finish up
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bulk_check(ctx: Arc<Context>) {
|
async fn check_bulk(ctx: Arc<Context>) {
|
||||||
let db_lock = {
|
let db_lock = {
|
||||||
let data_read = ctx.data.read().await;
|
let data_read = ctx.data.read().await;
|
||||||
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
@ -59,6 +62,6 @@ async fn bulk_check(ctx: Arc<Context>) {
|
||||||
let db = db_lock.read().await;
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
for server_config in get_server_config_bulk(&db).await {
|
for server_config in get_server_config_bulk(&db).await {
|
||||||
set_roles::update_server(&ctx, &server_config, &[], &[]).await;
|
normal::update_server(&ctx, &server_config, &[], &[]).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,55 @@
|
||||||
use serenity::{
|
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
|
||||||
builder::CreateApplicationCommand,
|
use serenity::client::Context;
|
||||||
client::Context,
|
use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers};
|
||||||
model::{
|
use skynet_discord_bot::common::set_roles::normal::update_server;
|
||||||
application::interaction::application_command::ApplicationCommandInteraction,
|
use skynet_discord_bot::common::wolves::cns::get_wolves;
|
||||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::get_data::get_wolves;
|
|
||||||
use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers};
|
|
||||||
use sqlx::{Error, Pool, Sqlite};
|
use sqlx::{Error, Pool, Sqlite};
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
// check if user has high enough permisssions
|
let sub_options = if let Some(CommandDataOption {
|
||||||
if let Some(msg) = is_admin(command, ctx).await {
|
value: CommandDataOptionValue::SubCommand(options),
|
||||||
return msg;
|
..
|
||||||
}
|
}) = command.data.options.first()
|
||||||
|
|
||||||
let api_key = if let CommandDataOptionValue::String(key) = command
|
|
||||||
.data
|
|
||||||
.options
|
|
||||||
.first()
|
|
||||||
.expect("Expected user option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected user object")
|
|
||||||
{
|
{
|
||||||
key
|
options
|
||||||
|
} else {
|
||||||
|
return "Please provide sub options".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let wolves_api = if let Some(x) = sub_options.first() {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::String(key) => key.to_string(),
|
||||||
|
_ => return "Please provide a wolves API key".to_string(),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return "Please provide a wolves API key".to_string();
|
return "Please provide a wolves API key".to_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
let role_current = if let CommandDataOptionValue::Role(role) = command
|
let role_current = if let Some(x) = sub_options.get(1) {
|
||||||
.data
|
match &x.value {
|
||||||
.options
|
CommandDataOptionValue::Role(role) => role.to_owned(),
|
||||||
.get(1)
|
_ => return "Please provide a valid role for ``Role Current``".to_string(),
|
||||||
.expect("Expected role option")
|
}
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected role object")
|
|
||||||
{
|
|
||||||
Some(role.id.to_owned())
|
|
||||||
} else {
|
} else {
|
||||||
return "Please provide a valid role for ``Role Current``".to_string();
|
return "Please provide a valid role for ``Role Current``".to_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut role_past = None;
|
let role_past = if let Some(x) = sub_options.get(5) {
|
||||||
if let Some(x) = command.data.options.get(2) {
|
match &x.value {
|
||||||
if let Some(CommandDataOptionValue::Role(role)) = &x.resolved {
|
CommandDataOptionValue::Role(role) => Some(role.to_owned()),
|
||||||
role_past = Some(role.id.to_owned());
|
_ => None,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let bot_channel_id = if let Some(x) = sub_options.get(2) {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::Channel(channel) => channel.to_owned(),
|
||||||
|
_ => return "Please provide a valid channel for ``Bot Channel``".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Please provide a valid channel for ``Bot Channel``".to_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
let db_lock = {
|
let db_lock = {
|
||||||
|
@ -59,11 +60,13 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri
|
||||||
|
|
||||||
let server_data = Servers {
|
let server_data = Servers {
|
||||||
server: command.guild_id.unwrap_or_default(),
|
server: command.guild_id.unwrap_or_default(),
|
||||||
wolves_api: api_key.to_owned(),
|
wolves_api,
|
||||||
|
wolves_id: 0,
|
||||||
role_past,
|
role_past,
|
||||||
role_current,
|
role_current,
|
||||||
member_past: 0,
|
member_past: 0,
|
||||||
member_current: 0,
|
member_current: 0,
|
||||||
|
bot_channel_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
match add_server(&db, ctx, &server_data).await {
|
match add_server(&db, ctx, &server_data).await {
|
||||||
|
@ -77,48 +80,21 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri
|
||||||
"Added/Updated server info".to_string()
|
"Added/Updated server info".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
|
||||||
command
|
|
||||||
.name("add")
|
|
||||||
.description("Enable the bot for this discord")
|
|
||||||
.create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("api_key")
|
|
||||||
.description("UL Wolves API Key")
|
|
||||||
.kind(CommandOptionType::String)
|
|
||||||
.required(true)
|
|
||||||
})
|
|
||||||
.create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("role_current")
|
|
||||||
.description("Role for Current members")
|
|
||||||
.kind(CommandOptionType::Role)
|
|
||||||
.required(true)
|
|
||||||
})
|
|
||||||
.create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("role_past")
|
|
||||||
.description("Role for Past members")
|
|
||||||
.kind(CommandOptionType::Role)
|
|
||||||
.required(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Result<Option<Servers>, Error> {
|
async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Result<Option<Servers>, Error> {
|
||||||
let existing = get_server_config(db, &server.server).await;
|
let existing = get_server_config(db, &server.server).await;
|
||||||
let role_past = server.role_past.map(|x| *x.as_u64() as i64);
|
let role_past = server.role_past.map(|x| x.get() as i64);
|
||||||
let role_current = server.role_current.map(|x| *x.as_u64() as i64);
|
|
||||||
|
|
||||||
let insert = sqlx::query_as::<_, Servers>(
|
let insert = sqlx::query_as::<_, Servers>(
|
||||||
"
|
"
|
||||||
INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current)
|
INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current, bot_channel_id)
|
||||||
VALUES (?1, ?2, ?3, ?4)
|
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(*server.server.as_u64() as i64)
|
.bind(server.server.get() as i64)
|
||||||
.bind(&server.wolves_api)
|
.bind(&server.wolves_api)
|
||||||
.bind(role_past)
|
.bind(role_past)
|
||||||
.bind(role_current)
|
.bind(server.role_current.get() as i64)
|
||||||
|
.bind(server.bot_channel_id.get() as i64)
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -133,7 +109,7 @@ async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Resul
|
||||||
if x.role_current != server.role_current {
|
if x.role_current != server.role_current {
|
||||||
result.0 = true;
|
result.0 = true;
|
||||||
result.1 = true;
|
result.1 = true;
|
||||||
result.2 = x.role_current;
|
result.2 = Some(x.role_current);
|
||||||
}
|
}
|
||||||
if x.role_past != server.role_past {
|
if x.role_past != server.role_past {
|
||||||
result.0 = true;
|
result.0 = true;
|
||||||
|
|
|
@ -1,311 +1,23 @@
|
||||||
use lettre::{
|
use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption};
|
||||||
message::{header, MultiPart, SinglePart},
|
|
||||||
transport::smtp::{self, authentication::Credentials},
|
|
||||||
Message, SmtpTransport, Transport,
|
|
||||||
};
|
|
||||||
use maud::html;
|
|
||||||
use serenity::{
|
|
||||||
builder::CreateApplicationCommand,
|
|
||||||
client::Context,
|
|
||||||
model::{
|
|
||||||
application::interaction::application_command::ApplicationCommandInteraction,
|
|
||||||
id::UserId,
|
|
||||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::{random_string, Config, DataBase};
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
|
|
||||||
pub mod link {
|
pub fn register() -> CreateCommand {
|
||||||
use super::*;
|
CreateCommand::new("committee")
|
||||||
use serenity::model::id::GuildId;
|
.description("Commands related to what committees can do")
|
||||||
use skynet_discord_bot::Committee;
|
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
||||||
|
.add_option(
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
CreateCommandOption::new(CommandOptionType::SubCommand, "add", "Enable the bot for this discord")
|
||||||
let committee_server = GuildId(1220150752656363520);
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true))
|
||||||
match command.guild_id {
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true))
|
||||||
None => {
|
.add_sub_option(
|
||||||
return "Not in correct discord server.".to_string();
|
CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true),
|
||||||
}
|
|
||||||
Some(x) => {
|
|
||||||
if x != committee_server {
|
|
||||||
return "Not in correct discord server.".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let option = command
|
|
||||||
.data
|
|
||||||
.options
|
|
||||||
.first()
|
|
||||||
.expect("Expected email option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected email object");
|
|
||||||
|
|
||||||
let email = if let CommandDataOptionValue::String(email) = option {
|
|
||||||
email.trim()
|
|
||||||
} else {
|
|
||||||
return "Please provide a valid committee email.".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
// fail early
|
|
||||||
if !email.ends_with("@ulwolves.ie") {
|
|
||||||
return "Please use a @ulwolves.ie address you have access to.".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let db_lock = {
|
|
||||||
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;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
if get_server_member_discord(&db, &command.user.id).await.is_some() {
|
|
||||||
return "Already linked".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if get_verify_from_db(&db, &command.user.id).await.is_some() {
|
|
||||||
return "Linking already in process, please check email.".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate a auth key
|
|
||||||
let auth = random_string(20);
|
|
||||||
match send_mail(&config, email, &auth, &command.user.name) {
|
|
||||||
Ok(_) => match save_to_db(&db, email, &auth, &command.user.id).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
return format!("Unable to save to db {} {e:?}", email);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
return format!("Unable to send mail to {} {e:?}", email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
|
||||||
command
|
|
||||||
.name("link_committee")
|
|
||||||
.description("Verify you are a committee member")
|
|
||||||
.create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("email")
|
|
||||||
.description("UL Wolves Committee Email")
|
|
||||||
.kind(CommandOptionType::String)
|
|
||||||
.required(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Committee> {
|
|
||||||
sqlx::query_as::<_, Committee>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM committee
|
|
||||||
WHERE discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
)
|
||||||
.bind(*user.as_u64() as i64)
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false)),
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result<smtp::response::Response, smtp::Error> {
|
|
||||||
let sender = format!("UL Computer Society <{}>", &config.mail_user);
|
|
||||||
|
|
||||||
// Create the html we want to send.
|
|
||||||
let html = html! {
|
|
||||||
head {
|
|
||||||
title { "Hello from Skynet!" }
|
|
||||||
style type="text/css" {
|
|
||||||
"h2, h4 { font-family: Arial, Helvetica, sans-serif; }"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div style="display: flex; flex-direction: column; align-items: center;" {
|
|
||||||
h2 { "Hello from Skynet!" }
|
|
||||||
// Substitute in the name of our recipient.
|
|
||||||
p { "Hi " (user) "," }
|
|
||||||
p {
|
|
||||||
"Please use " pre { "/verify_committee code: " (auth)} " to verify your discord account."
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
"Skynet Team"
|
|
||||||
br;
|
|
||||||
"UL Computer Society"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let body_text = format!(
|
|
||||||
r#"
|
|
||||||
Hi {user}
|
|
||||||
|
|
||||||
Please use "/verify_committee code: {auth}" to verify your discord account.
|
|
||||||
|
|
||||||
Skynet Team
|
|
||||||
UL Computer Society
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build the message.
|
|
||||||
let email = Message::builder()
|
|
||||||
.from(sender.parse().unwrap())
|
|
||||||
.to(mail.parse().unwrap())
|
|
||||||
.subject("Skynet-Discord: Link Committee.")
|
|
||||||
.multipart(
|
|
||||||
// This is composed of two parts.
|
|
||||||
// also helps not trip spam settings (uneven number of url's
|
|
||||||
MultiPart::alternative()
|
|
||||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text))
|
|
||||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())),
|
|
||||||
)
|
)
|
||||||
.expect("failed to build email");
|
.add_option(
|
||||||
|
CreateCommandOption::new(CommandOptionType::SubCommand, "roles_adder", "Combine roles together to an new one")
|
||||||
let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone());
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true))
|
||||||
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true))
|
||||||
// Open a remote connection to gmail using STARTTLS
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true))
|
||||||
let mailer = SmtpTransport::starttls_relay(&config.mail_smtp).unwrap().credentials(creds).build();
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)),
|
||||||
|
|
||||||
// Send the email
|
|
||||||
mailer.send(&email)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_verify_from_db(db: &Pool<Sqlite>, user: &UserId) -> Option<Committee> {
|
|
||||||
sqlx::query_as::<_, Committee>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM committee
|
|
||||||
WHERE discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
)
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn save_to_db(db: &Pool<Sqlite>, email: &str, auth: &str, user: &UserId) -> Result<Option<Committee>, sqlx::Error> {
|
|
||||||
sqlx::query_as::<_, Committee>(
|
|
||||||
"
|
|
||||||
INSERT INTO committee (email, discord, auth_code)
|
|
||||||
VALUES (?1, ?2, ?3)
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(email.to_owned())
|
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.bind(auth.to_owned())
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod verify {
|
|
||||||
use super::*;
|
|
||||||
use crate::commands::committee::link::get_verify_from_db;
|
|
||||||
use serenity::model::id::{GuildId, RoleId};
|
|
||||||
use serenity::model::user::User;
|
|
||||||
use skynet_discord_bot::Committee;
|
|
||||||
use sqlx::Error;
|
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
|
||||||
let committee_server = GuildId(1220150752656363520);
|
|
||||||
match command.guild_id {
|
|
||||||
None => {
|
|
||||||
return "Not in correct discord server.".to_string();
|
|
||||||
}
|
|
||||||
Some(x) => {
|
|
||||||
if x != committee_server {
|
|
||||||
return "Not in correct discord server.".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let db_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let db = db_lock.read().await;
|
|
||||||
|
|
||||||
// check if user has used /link_committee
|
|
||||||
let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await {
|
|
||||||
x
|
|
||||||
} else {
|
|
||||||
return "Please use /link_committee first".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
let option = command
|
|
||||||
.data
|
|
||||||
.options
|
|
||||||
.first()
|
|
||||||
.expect("Expected code option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected code object");
|
|
||||||
|
|
||||||
let code = if let CommandDataOptionValue::String(code) = option {
|
|
||||||
code
|
|
||||||
} else {
|
|
||||||
return "Please provide a verification code".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
if &details.auth_code != code {
|
|
||||||
return "Invalid verification code".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
match set_discord(&db, &command.user.id).await {
|
|
||||||
Ok(_) => {
|
|
||||||
// get teh right roles for the user
|
|
||||||
set_server_roles(&command.user, ctx).await;
|
|
||||||
"Discord username linked to Wolves for committee".to_string()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("{:?}", e);
|
|
||||||
"Failed to save, please try /link_committee again".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
|
||||||
command
|
|
||||||
.name("verify_committee")
|
|
||||||
.description("Verify Wolves Committee Email")
|
|
||||||
.create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("code")
|
|
||||||
.description("Code from verification email")
|
|
||||||
.kind(CommandOptionType::String)
|
|
||||||
.required(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_discord(db: &Pool<Sqlite>, discord: &UserId) -> Result<Option<Committee>, Error> {
|
|
||||||
sqlx::query_as::<_, Committee>(
|
|
||||||
"
|
|
||||||
UPDATE committee
|
|
||||||
SET committee = 1
|
|
||||||
WHERE discord = ?
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(*discord.as_u64() as i64)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_server_roles(discord: &User, ctx: &Context) {
|
|
||||||
let committee_server = GuildId(1220150752656363520);
|
|
||||||
if let Ok(mut member) = committee_server.member(&ctx.http, &discord.id).await {
|
|
||||||
let committee_member = RoleId(1226602779968274573);
|
|
||||||
if let Err(e) = member.add_role(&ctx, committee_member).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
157
src/commands/count.rs
Normal file
157
src/commands/count.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
pub mod committee {
|
||||||
|
|
||||||
|
// get the list of all the current clubs/socs members
|
||||||
|
|
||||||
|
use serenity::all::{
|
||||||
|
CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption,
|
||||||
|
};
|
||||||
|
use skynet_discord_bot::common::database::DataBase;
|
||||||
|
use skynet_discord_bot::common::set_roles::committee::db_roles_get;
|
||||||
|
|
||||||
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
|
let sub_options = if let Some(CommandDataOption {
|
||||||
|
value: CommandDataOptionValue::SubCommand(key),
|
||||||
|
..
|
||||||
|
}) = command.data.options.first()
|
||||||
|
{
|
||||||
|
key
|
||||||
|
} else {
|
||||||
|
return "Please provide a wolves API key".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let all = if let Some(x) = sub_options.first() {
|
||||||
|
match x.value {
|
||||||
|
CommandDataOptionValue::Boolean(y) => y,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let db_lock = {
|
||||||
|
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
|
||||||
|
for committee in db_roles_get(&db).await {
|
||||||
|
if !all && committee.count == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cs.push((committee.count, committee.name_role.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.sort_by_key(|(count, _)| *count);
|
||||||
|
cs.reverse();
|
||||||
|
|
||||||
|
// msg can be a max 2000 chars long
|
||||||
|
let mut limit = 2000 - 3;
|
||||||
|
|
||||||
|
let mut response = vec!["```".to_string()];
|
||||||
|
for (count, name) in cs {
|
||||||
|
let leading = if count < 10 { " " } else { "" };
|
||||||
|
|
||||||
|
let line = format!("{}{} {}", leading, count, name);
|
||||||
|
|
||||||
|
let length = line.len() + 1;
|
||||||
|
|
||||||
|
if length < (limit + 3) {
|
||||||
|
response.push(line);
|
||||||
|
limit -= length;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.push("```".to_string());
|
||||||
|
|
||||||
|
response.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register() -> CreateCommand {
|
||||||
|
CreateCommand::new("count")
|
||||||
|
.description("Count Committee Members")
|
||||||
|
// All Committee members are able to add reactions to posts
|
||||||
|
.default_member_permissions(serenity::model::Permissions::ADD_REACTIONS)
|
||||||
|
.add_option(
|
||||||
|
CreateCommandOption::new(CommandOptionType::SubCommand, "committee", "List out the Committee Roles Numbers")
|
||||||
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod servers {
|
||||||
|
// get the list of all the current clubs/socs
|
||||||
|
use 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 std::collections::HashMap;
|
||||||
|
|
||||||
|
pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
|
let db_lock = {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cs = vec![];
|
||||||
|
// pull it from a DB
|
||||||
|
for server_config in get_server_config_bulk(&db).await {
|
||||||
|
if let Some(x) = committees.get(&server_config.wolves_id) {
|
||||||
|
cs.push((server_config.member_current, server_config.member_past, x.name_full.to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.sort_by_key(|(count, _, _)| *count);
|
||||||
|
cs.reverse();
|
||||||
|
|
||||||
|
// msg can be a max 2000 chars long
|
||||||
|
let mut limit = 2000 - 3;
|
||||||
|
|
||||||
|
let mut response = vec!["```".to_string()];
|
||||||
|
for (current, past, name) in cs {
|
||||||
|
let current_leading = if current < 10 {
|
||||||
|
" "
|
||||||
|
} else if current < 100 {
|
||||||
|
" "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
let past_leading = if past < 10 {
|
||||||
|
" "
|
||||||
|
} else if past < 100 {
|
||||||
|
" "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
let line = format!("{}{} {}{} {}", current_leading, current, past_leading, past, name);
|
||||||
|
|
||||||
|
let length = line.len() + 1;
|
||||||
|
|
||||||
|
// +3 is to account for the closing fense
|
||||||
|
if length < (limit + 3) {
|
||||||
|
response.push(line);
|
||||||
|
limit -= length;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.push("```".to_string());
|
||||||
|
|
||||||
|
response.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register() -> CreateCommand {
|
||||||
|
CreateCommand::new("count")
|
||||||
|
.description("Count the servers")
|
||||||
|
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
||||||
|
.add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "servers", "List out all servers using the skynet bot"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,383 +0,0 @@
|
||||||
use lettre::{
|
|
||||||
message::{header, MultiPart, SinglePart},
|
|
||||||
transport::smtp::{self, authentication::Credentials},
|
|
||||||
Message, SmtpTransport, Transport,
|
|
||||||
};
|
|
||||||
use maud::html;
|
|
||||||
use serenity::{
|
|
||||||
builder::CreateApplicationCommand,
|
|
||||||
client::Context,
|
|
||||||
model::{
|
|
||||||
application::interaction::application_command::ApplicationCommandInteraction,
|
|
||||||
id::UserId,
|
|
||||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, WolvesVerify};
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
pub mod link {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
|
||||||
let db_lock = {
|
|
||||||
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;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
if get_server_member_discord(&db, &command.user.id).await.is_some() {
|
|
||||||
return "Already linked".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
db_pending_clear_expired(&db).await;
|
|
||||||
|
|
||||||
if get_verify_from_db(&db, &command.user.id).await.is_some() {
|
|
||||||
return "Linking already in process, please check email.".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let option = command
|
|
||||||
.data
|
|
||||||
.options
|
|
||||||
.first()
|
|
||||||
.expect("Expected email option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected email object");
|
|
||||||
|
|
||||||
let email = if let CommandDataOptionValue::String(email) = option {
|
|
||||||
email.trim()
|
|
||||||
} else {
|
|
||||||
return "Please provide a valid user".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
// check if email exists
|
|
||||||
let details = match get_server_member_email(&db, email).await {
|
|
||||||
None => {
|
|
||||||
return "Please check it is your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string()
|
|
||||||
}
|
|
||||||
Some(x) => x,
|
|
||||||
};
|
|
||||||
|
|
||||||
if details.discord.is_some() {
|
|
||||||
return "Email already verified".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate a auth key
|
|
||||||
let auth = random_string(20);
|
|
||||||
match send_mail(&config, &details, &auth, &command.user.name) {
|
|
||||||
Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
return format!("Unable to save to db {} {e:?}", &details.email);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
return format!("Unable to send mail to {} {e:?}", &details.email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
|
||||||
command
|
|
||||||
.name("link_wolves")
|
|
||||||
.description("Set Wolves Email")
|
|
||||||
.create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Wolves> {
|
|
||||||
sqlx::query_as::<_, Wolves>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM wolves
|
|
||||||
WHERE discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_server_member_email(db: &Pool<Sqlite>, email: &str) -> Option<Wolves> {
|
|
||||||
sqlx::query_as::<_, Wolves>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM wolves
|
|
||||||
WHERE email = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(email)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_mail(config: &Config, email: &Wolves, auth: &str, user: &str) -> Result<smtp::response::Response, smtp::Error> {
|
|
||||||
let mail = &email.email;
|
|
||||||
let discord = "https://discord.skynet.ie";
|
|
||||||
let sender = format!("UL Computer Society <{}>", &config.mail_user);
|
|
||||||
|
|
||||||
// Create the html we want to send.
|
|
||||||
let html = html! {
|
|
||||||
head {
|
|
||||||
title { "Hello from Skynet!" }
|
|
||||||
style type="text/css" {
|
|
||||||
"h2, h4 { font-family: Arial, Helvetica, sans-serif; }"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div style="display: flex; flex-direction: column; align-items: center;" {
|
|
||||||
h2 { "Hello from Skynet!" }
|
|
||||||
// Substitute in the name of our recipient.
|
|
||||||
p { "Hi " (user) "," }
|
|
||||||
p {
|
|
||||||
"Please use " pre { "/verify code: " (auth)} " to verify your discord account."
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
"If you have issues please refer to our Discord server:"
|
|
||||||
br;
|
|
||||||
a href=(discord) { (discord) }
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
"Skynet Team"
|
|
||||||
br;
|
|
||||||
"UL Computer Society"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let body_text = format!(
|
|
||||||
r#"
|
|
||||||
Hi {user}
|
|
||||||
|
|
||||||
Please use "/verify code: {auth}" to verify your discord account.
|
|
||||||
|
|
||||||
If you have issues please refer to our Discord server:
|
|
||||||
{discord}
|
|
||||||
|
|
||||||
Skynet Team
|
|
||||||
UL Computer Society
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build the message.
|
|
||||||
let email = Message::builder()
|
|
||||||
.from(sender.parse().unwrap())
|
|
||||||
.to(mail.parse().unwrap())
|
|
||||||
.subject("Skynet-Discord: Link Wolves.")
|
|
||||||
.multipart(
|
|
||||||
// This is composed of two parts.
|
|
||||||
// also helps not trip spam settings (uneven number of url's
|
|
||||||
MultiPart::alternative()
|
|
||||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text))
|
|
||||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())),
|
|
||||||
)
|
|
||||||
.expect("failed to build email");
|
|
||||||
|
|
||||||
let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone());
|
|
||||||
|
|
||||||
// Open a remote connection to gmail using STARTTLS
|
|
||||||
let mailer = SmtpTransport::starttls_relay(&config.mail_smtp).unwrap().credentials(creds).build();
|
|
||||||
|
|
||||||
// Send the email
|
|
||||||
mailer.send(&email)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn db_pending_clear_expired(pool: &Pool<Sqlite>) -> Option<WolvesVerify> {
|
|
||||||
sqlx::query_as::<_, WolvesVerify>(
|
|
||||||
r#"
|
|
||||||
DELETE
|
|
||||||
FROM wolves_verify
|
|
||||||
WHERE date_expiry < ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(get_now_iso(true))
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_verify_from_db(db: &Pool<Sqlite>, user: &UserId) -> Option<WolvesVerify> {
|
|
||||||
sqlx::query_as::<_, WolvesVerify>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM wolves_verify
|
|
||||||
WHERE discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn save_to_db(db: &Pool<Sqlite>, record: &Wolves, auth: &str, user: &UserId) -> Result<Option<WolvesVerify>, sqlx::Error> {
|
|
||||||
sqlx::query_as::<_, WolvesVerify>(
|
|
||||||
"
|
|
||||||
INSERT INTO wolves_verify (email, discord, auth_code, date_expiry)
|
|
||||||
VALUES (?1, ?2, ?3, ?4)
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(record.email.to_owned())
|
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.bind(auth.to_owned())
|
|
||||||
.bind(get_now_iso(false))
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod verify {
|
|
||||||
use super::*;
|
|
||||||
use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db};
|
|
||||||
use serenity::model::user::User;
|
|
||||||
use skynet_discord_bot::{get_server_config, ServerMembersWolves, Servers};
|
|
||||||
use sqlx::Error;
|
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
|
||||||
let db_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let db = db_lock.read().await;
|
|
||||||
|
|
||||||
// check if user has used /link_wolves
|
|
||||||
let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await {
|
|
||||||
x
|
|
||||||
} else {
|
|
||||||
return "Please use /link_wolves first".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
let option = command
|
|
||||||
.data
|
|
||||||
.options
|
|
||||||
.first()
|
|
||||||
.expect("Expected code option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected code object");
|
|
||||||
|
|
||||||
let code = if let CommandDataOptionValue::String(code) = option {
|
|
||||||
code
|
|
||||||
} else {
|
|
||||||
return "Please provide a verification code".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
db_pending_clear_expired(&db).await;
|
|
||||||
|
|
||||||
if &details.auth_code != code {
|
|
||||||
return "Invalid verification code".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
match db_pending_clear_successful(&db, &command.user.id).await {
|
|
||||||
Ok(_) => {
|
|
||||||
return match set_discord(&db, &command.user.id, &details.email).await {
|
|
||||||
Ok(_) => {
|
|
||||||
// get teh right roles for the user
|
|
||||||
set_server_roles(&db, &command.user, ctx).await;
|
|
||||||
"Discord username linked to Wolves".to_string()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("{:?}", e);
|
|
||||||
"Failed to save, please try /link_wolves again".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Err(e) => println!("{:?}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
"Failed to verify".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
|
||||||
command.name("verify").description("Verify Wolves Email").create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("code")
|
|
||||||
.description("Code from verification email")
|
|
||||||
.kind(CommandOptionType::String)
|
|
||||||
.required(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn db_pending_clear_successful(pool: &Pool<Sqlite>, user: &UserId) -> Result<Option<WolvesVerify>, Error> {
|
|
||||||
sqlx::query_as::<_, WolvesVerify>(
|
|
||||||
r#"
|
|
||||||
DELETE
|
|
||||||
FROM wolves_verify
|
|
||||||
WHERE discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.fetch_optional(pool)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_discord(db: &Pool<Sqlite>, discord: &UserId, email: &str) -> Result<Option<Wolves>, Error> {
|
|
||||||
sqlx::query_as::<_, Wolves>(
|
|
||||||
"
|
|
||||||
UPDATE wolves
|
|
||||||
SET discord = ?
|
|
||||||
WHERE email = ?
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(*discord.as_u64() as i64)
|
|
||||||
.bind(email)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_server_roles(db: &Pool<Sqlite>, discord: &User, ctx: &Context) {
|
|
||||||
if let Ok(servers) = get_servers(db, &discord.id).await {
|
|
||||||
for server in servers {
|
|
||||||
if let Ok(mut member) = server.server.member(&ctx.http, &discord.id).await {
|
|
||||||
if let Some(config) = get_server_config(db, &server.server).await {
|
|
||||||
let Servers {
|
|
||||||
role_past,
|
|
||||||
role_current,
|
|
||||||
..
|
|
||||||
} = config;
|
|
||||||
|
|
||||||
let mut roles = vec![];
|
|
||||||
|
|
||||||
if let Some(role) = &role_past {
|
|
||||||
if !member.roles.contains(role) {
|
|
||||||
roles.push(role.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(role) = &role_current {
|
|
||||||
if !member.roles.contains(role) {
|
|
||||||
roles.push(role.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = member.add_roles(&ctx, &roles).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_servers(db: &Pool<Sqlite>, discord: &UserId) -> Result<Vec<ServerMembersWolves>, Error> {
|
|
||||||
sqlx::query_as::<_, ServerMembersWolves>(
|
|
||||||
"
|
|
||||||
SELECT *
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE discord = ?
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(*discord.as_u64() as i64)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +1,22 @@
|
||||||
use serenity::{
|
use serenity::client::Context;
|
||||||
builder::CreateApplicationCommand,
|
|
||||||
client::Context,
|
|
||||||
model::{
|
|
||||||
application::interaction::application_command::ApplicationCommandInteraction,
|
|
||||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use skynet_discord_bot::DataBase;
|
use skynet_discord_bot::common::database::DataBase;
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
pub(crate) mod user {
|
pub(crate) mod user {
|
||||||
use super::*;
|
use super::*;
|
||||||
pub(crate) mod add {
|
pub(crate) mod add {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::commands::link_email::link::get_server_member_discord;
|
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 serenity::model::id::UserId;
|
||||||
use skynet_discord_bot::{whitelist_update, Config, Minecraft, Wolves};
|
use skynet_discord_bot::common::database::Wolves;
|
||||||
|
use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft};
|
||||||
|
use skynet_discord_bot::Config;
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
command.name("link_minecraft").description("Link your minecraft account").create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("minecraft-username")
|
|
||||||
.description("Your Minecraft username")
|
|
||||||
.kind(CommandOptionType::String)
|
|
||||||
.required(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
|
||||||
let db_lock = {
|
let db_lock = {
|
||||||
let data_read = ctx.data.read().await;
|
let data_read = ctx.data.read().await;
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||||
|
@ -47,20 +34,36 @@ pub(crate) mod user {
|
||||||
return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string();
|
return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let username = if let CommandDataOptionValue::String(username) = command
|
let sub_options = if let Some(CommandDataOption {
|
||||||
.data
|
value: CommandDataOptionValue::SubCommand(options),
|
||||||
.options
|
..
|
||||||
.first()
|
}) = command.data.options.first()
|
||||||
.expect("Expected username option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected username object")
|
|
||||||
{
|
{
|
||||||
username.trim()
|
options
|
||||||
|
} else {
|
||||||
|
return "Please provide sub options".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let username = if let Some(x) = sub_options.first() {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::String(username) => username.trim(),
|
||||||
|
_ => return "Please provide a valid username".to_string(),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return "Please provide a valid username".to_string();
|
return "Please provide a valid username".to_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let java = if let Some(x) = sub_options.get(1) {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::Boolean(z) => !z,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
let username_mc;
|
||||||
|
if java {
|
||||||
// insert the username into the database
|
// insert the username into the database
|
||||||
match add_minecraft(&db, &command.user.id, username).await {
|
match add_minecraft(&db, &command.user.id, username).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
|
@ -69,11 +72,30 @@ pub(crate) mod user {
|
||||||
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);
|
||||||
|
}
|
||||||
|
Some(x) => {
|
||||||
|
match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!("{:?}", e);
|
||||||
|
return format!("Failure to minecraft UID {:?}", &x.floodgateuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
username_mc = x.floodgateuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get a list of servers that the user is a member of
|
// get a list of servers that the user is a member of
|
||||||
if let Ok(servers) = get_servers(&db, &command.user.id).await {
|
if let Ok(servers) = get_servers(&db, &command.user.id).await {
|
||||||
for server in servers {
|
for server in servers {
|
||||||
whitelist_update(&vec![username.to_string()], &server.minecraft, &config.discord_token_minecraft).await;
|
whitelist_update(&vec![(username_mc.to_owned(), java)], &server.minecraft, &config.discord_token_minecraft).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +110,52 @@ pub(crate) mod user {
|
||||||
WHERE discord = ?1;
|
WHERE discord = ?1;
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(*user.as_u64() as i64)
|
.bind(user.get() as i64)
|
||||||
|
.bind(minecraft)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
struct BedrockDetails {
|
||||||
|
pub gamertag: String,
|
||||||
|
pub xuid: String,
|
||||||
|
pub floodgateuid: String,
|
||||||
|
pub icon: String,
|
||||||
|
pub gamescore: String,
|
||||||
|
pub accounttier: String,
|
||||||
|
pub textureid: String,
|
||||||
|
pub skin: String,
|
||||||
|
pub linked: bool,
|
||||||
|
pub java_uuid: String,
|
||||||
|
pub java_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_minecraft_bedrock(username: &str, api_key: &str) -> Option<BedrockDetails> {
|
||||||
|
let url = format!("https://mcprofile.io/api/v1/bedrock/gamertag/{username}/");
|
||||||
|
match surf::get(url)
|
||||||
|
.header("x-api-key", api_key)
|
||||||
|
.header("User-Agent", "UL Computer Society")
|
||||||
|
.recv_json()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => Some(res),
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_minecraft_bedrock(db: &Pool<Sqlite>, user: &UserId, minecraft: &str) -> Result<Option<Wolves>, Error> {
|
||||||
|
sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
UPDATE wolves
|
||||||
|
SET minecraft_uid = ?2
|
||||||
|
WHERE discord = ?1;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(user.get() as i64)
|
||||||
.bind(minecraft)
|
.bind(minecraft)
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await
|
.await
|
||||||
|
@ -107,7 +174,7 @@ pub(crate) mod user {
|
||||||
) sub on minecraft.server_discord = sub.server
|
) sub on minecraft.server_discord = sub.server
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(*discord.as_u64() as i64)
|
.bind(discord.get() as i64)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -118,42 +185,37 @@ pub(crate) mod server {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub(crate) mod add {
|
pub(crate) mod add {
|
||||||
|
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption};
|
||||||
use serenity::model::id::GuildId;
|
use serenity::model::id::GuildId;
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
// this is to managfe the server side of commands related to minecraft
|
// this is to managfe the server side of commands related to minecraft
|
||||||
use super::*;
|
use super::*;
|
||||||
use skynet_discord_bot::{is_admin, update_server, Config, Minecraft};
|
use skynet_discord_bot::common::minecraft::update_server;
|
||||||
|
use skynet_discord_bot::common::minecraft::Minecraft;
|
||||||
|
use skynet_discord_bot::Config;
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
pub fn register() -> CreateCommand {
|
||||||
command.name("minecraft_add").description("Add a minecraft server").create_option(|option| {
|
CreateCommand::new("minecraft_add")
|
||||||
option
|
.description("Add a minecraft server")
|
||||||
.name("server_id")
|
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
||||||
.description("ID of the Minecraft server hosted by the Computer Society")
|
.add_option(
|
||||||
.kind(CommandOptionType::String)
|
CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society")
|
||||||
.required(true)
|
.required(true),
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
// check if user has high enough permisssions
|
|
||||||
if let Some(msg) = is_admin(command, ctx).await {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
let g_id = match command.guild_id {
|
let g_id = match command.guild_id {
|
||||||
None => return "Not in a server".to_string(),
|
None => return "Not in a server".to_string(),
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
};
|
};
|
||||||
|
|
||||||
let server_minecraft = if let CommandDataOptionValue::String(id) = command
|
let server_minecraft = if let Some(CommandDataOption {
|
||||||
.data
|
value: CommandDataOptionValue::String(id),
|
||||||
.options
|
..
|
||||||
.first()
|
}) = command.data.options.first()
|
||||||
.expect("Expected server_id option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected server_id object")
|
|
||||||
{
|
{
|
||||||
id.to_owned()
|
id.to_string()
|
||||||
} else {
|
} else {
|
||||||
return String::from("Expected Server ID");
|
return String::from("Expected Server ID");
|
||||||
};
|
};
|
||||||
|
@ -190,7 +252,7 @@ pub(crate) mod server {
|
||||||
VALUES (?1, ?2)
|
VALUES (?1, ?2)
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(*discord.as_u64() as i64)
|
.bind(discord.get() as i64)
|
||||||
.bind(minecraft)
|
.bind(minecraft)
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await
|
.await
|
||||||
|
@ -198,19 +260,20 @@ pub(crate) mod server {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod list {
|
pub(crate) mod list {
|
||||||
use serenity::builder::CreateApplicationCommand;
|
use serenity::all::CommandInteraction;
|
||||||
|
use serenity::builder::CreateCommand;
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
use skynet_discord_bot::common::database::DataBase;
|
||||||
use skynet_discord_bot::{get_minecraft_config_server, is_admin, server_information, Config, DataBase};
|
use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information};
|
||||||
|
use skynet_discord_bot::Config;
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
pub fn register() -> CreateCommand {
|
||||||
command.name("minecraft_list").description("List your minecraft servers")
|
CreateCommand::new("minecraft_list")
|
||||||
|
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
||||||
|
.description("List your minecraft servers")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
if let Some(msg) = is_admin(command, ctx).await {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
let g_id = match command.guild_id {
|
let g_id = match command.guild_id {
|
||||||
None => return "Not in a server".to_string(),
|
None => return "Not in a server".to_string(),
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
|
@ -243,7 +306,7 @@ pub(crate) mod server {
|
||||||
ID: {id}
|
ID: {id}
|
||||||
Online: {online}
|
Online: {online}
|
||||||
Info: {description}
|
Info: {description}
|
||||||
Link: <http://panel.games.skynet.ie/server/{id}>
|
Link: <https://panel.games.skynet.ie/server/{id}>
|
||||||
"#,
|
"#,
|
||||||
name = &x.attributes.name,
|
name = &x.attributes.name,
|
||||||
online = !x.attributes.is_suspended,
|
online = !x.attributes.is_suspended,
|
||||||
|
@ -257,44 +320,36 @@ pub(crate) mod server {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod delete {
|
pub(crate) mod delete {
|
||||||
use serenity::builder::CreateApplicationCommand;
|
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption};
|
||||||
|
use serenity::builder::CreateCommand;
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
use serenity::model::application::command::CommandOptionType;
|
|
||||||
use serenity::model::id::GuildId;
|
use serenity::model::id::GuildId;
|
||||||
use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue};
|
use skynet_discord_bot::common::database::DataBase;
|
||||||
use skynet_discord_bot::{is_admin, DataBase, Minecraft};
|
use skynet_discord_bot::common::minecraft::Minecraft;
|
||||||
use sqlx::{Error, Pool, Sqlite};
|
use sqlx::{Error, Pool, Sqlite};
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
pub fn register() -> CreateCommand {
|
||||||
command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| {
|
CreateCommand::new("minecraft_delete")
|
||||||
option
|
.description("Delete a minecraft server")
|
||||||
.name("server_id")
|
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
||||||
.description("ID of the Minecraft server hosted by the Computer Society")
|
.add_option(
|
||||||
.kind(CommandOptionType::String)
|
CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society")
|
||||||
.required(true)
|
.required(true),
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
// check if user has high enough permisssions
|
|
||||||
if let Some(msg) = is_admin(command, ctx).await {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
let g_id = match command.guild_id {
|
let g_id = match command.guild_id {
|
||||||
None => return "Not in a server".to_string(),
|
None => return "Not in a server".to_string(),
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
};
|
};
|
||||||
|
|
||||||
let server_minecraft = if let CommandDataOptionValue::String(id) = command
|
let server_minecraft = if let Some(CommandDataOption {
|
||||||
.data
|
value: CommandDataOptionValue::String(id),
|
||||||
.options
|
..
|
||||||
.first()
|
}) = command.data.options.first()
|
||||||
.expect("Expected server_id option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected server_id object")
|
|
||||||
{
|
{
|
||||||
id.to_owned()
|
id.to_string()
|
||||||
} else {
|
} else {
|
||||||
return String::from("Expected Server ID");
|
return String::from("Expected Server ID");
|
||||||
};
|
};
|
||||||
|
@ -325,7 +380,7 @@ pub(crate) mod server {
|
||||||
WHERE server_discord = ?1 AND server_minecraft = ?2
|
WHERE server_discord = ?1 AND server_minecraft = ?2
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(*discord.as_u64() as i64)
|
.bind(discord.get() as i64)
|
||||||
.bind(minecraft)
|
.bind(minecraft)
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod add_server;
|
pub mod add_server;
|
||||||
pub mod committee;
|
pub mod committee;
|
||||||
pub mod link_email;
|
pub mod count;
|
||||||
pub mod minecraft;
|
pub mod minecraft;
|
||||||
|
pub mod role_adder;
|
||||||
|
pub mod wolves;
|
||||||
|
|
194
src/commands/role_adder.rs
Normal file
194
src/commands/role_adder.rs
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
use serenity::client::Context;
|
||||||
|
|
||||||
|
use skynet_discord_bot::common::database::{DataBase, RoleAdder};
|
||||||
|
use sqlx::{Error, Pool, Sqlite};
|
||||||
|
|
||||||
|
pub mod edit {
|
||||||
|
use super::*;
|
||||||
|
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
|
||||||
|
|
||||||
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
|
let sub_options = if let Some(CommandDataOption {
|
||||||
|
value: CommandDataOptionValue::SubCommand(options),
|
||||||
|
..
|
||||||
|
}) = command.data.options.first()
|
||||||
|
{
|
||||||
|
options
|
||||||
|
} else {
|
||||||
|
return "Please provide sub options".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let role_a = if let Some(x) = sub_options.first() {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::Role(role) => role.to_owned(),
|
||||||
|
_ => return "Please provide a valid role for ``Role A``".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Please provide a valid role for ``Role A``".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let role_b = if let Some(x) = sub_options.get(1) {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::Role(role) => role.to_owned(),
|
||||||
|
_ => return "Please provide a valid role for ``Role B``".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Please provide a valid role for ``Role B``".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let role_c = if let Some(x) = sub_options.get(2) {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::Role(role) => role.to_owned(),
|
||||||
|
_ => return "Please provide a valid role for ``Role C``".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Please provide a valid role for ``Role C``".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
if role_a == role_b {
|
||||||
|
return "Roles A and B must be different".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role_c == role_a) || (role_c == role_b) {
|
||||||
|
return "Role C cannot be same as A or B".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let delete = if let Some(x) = sub_options.get(3) {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::Boolean(z) => *z,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let db_lock = {
|
||||||
|
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 {
|
||||||
|
server,
|
||||||
|
role_a,
|
||||||
|
role_b,
|
||||||
|
role_c,
|
||||||
|
};
|
||||||
|
|
||||||
|
match add_server(&db, &server_data, delete).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{:?}", e);
|
||||||
|
return format!("Failure to insert into Servers {:?}", server_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut role_a_name = String::new();
|
||||||
|
let mut role_b_name = String::new();
|
||||||
|
let mut role_c_name = String::new();
|
||||||
|
|
||||||
|
if let Ok(x) = server.roles(&ctx).await {
|
||||||
|
if let Some(y) = x.get(&role_a) {
|
||||||
|
role_a_name = y.to_owned().name;
|
||||||
|
}
|
||||||
|
if let Some(y) = x.get(&role_b) {
|
||||||
|
role_b_name = y.to_owned().name;
|
||||||
|
}
|
||||||
|
if let Some(y) = x.get(&role_b) {
|
||||||
|
role_c_name = y.to_owned().name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if delete {
|
||||||
|
format!("Removed {} + {} = {}", role_a_name, role_b_name, role_c_name)
|
||||||
|
} else {
|
||||||
|
format!("Added {} + {} = {}", role_a_name, role_b_name, role_c_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_server(db: &Pool<Sqlite>, server: &RoleAdder, delete: bool) -> Result<Option<RoleAdder>, Error> {
|
||||||
|
if delete {
|
||||||
|
sqlx::query_as::<_, RoleAdder>(
|
||||||
|
"
|
||||||
|
DELETE FROM roles_adder
|
||||||
|
WHERE server = ?1 AND role_a = ?2 AND role_b = ?3 AND role_c = ?4
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(server.server.get() as i64)
|
||||||
|
.bind(server.role_a.get() as i64)
|
||||||
|
.bind(server.role_b.get() as i64)
|
||||||
|
.bind(server.role_c.get() as i64)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
sqlx::query_as::<_, RoleAdder>(
|
||||||
|
"
|
||||||
|
INSERT OR REPLACE INTO roles_adder (server, role_a, role_b, role_c)
|
||||||
|
VALUES (?1, ?2, ?3, ?4)
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(server.server.get() as i64)
|
||||||
|
.bind(server.role_a.get() as i64)
|
||||||
|
.bind(server.role_b.get() as i64)
|
||||||
|
.bind(server.role_c.get() as i64)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub mod list {}
|
||||||
|
|
||||||
|
pub mod tools {
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::model::guild::Member;
|
||||||
|
use skynet_discord_bot::common::database::RoleAdder;
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
pub async fn on_role_change(db: &Pool<Sqlite>, ctx: &Context, new_data: Member) {
|
||||||
|
// check if the role changed is part of the oens for this server
|
||||||
|
if let Ok(role_adders) = sqlx::query_as::<_, RoleAdder>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM roles_adder
|
||||||
|
WHERE server = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(new_data.guild_id.get() as i64)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let mut roles_add = vec![];
|
||||||
|
let mut roles_remove = vec![];
|
||||||
|
|
||||||
|
for role_adder in role_adders {
|
||||||
|
// if the user has both A dnd B give them C
|
||||||
|
if new_data.roles.contains(&role_adder.role_a) && new_data.roles.contains(&role_adder.role_b) && !new_data.roles.contains(&role_adder.role_c)
|
||||||
|
{
|
||||||
|
roles_add.push(role_adder.role_c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the suer has C but not A or B remove C
|
||||||
|
if new_data.roles.contains(&role_adder.role_c)
|
||||||
|
&& (!new_data.roles.contains(&role_adder.role_a) || !new_data.roles.contains(&role_adder.role_b))
|
||||||
|
{
|
||||||
|
roles_remove.push(role_adder.role_c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !roles_add.is_empty() {
|
||||||
|
if let Err(e) = new_data.add_roles(&ctx, &roles_add).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !roles_remove.is_empty() {
|
||||||
|
if let Err(e) = new_data.remove_roles(&ctx, &roles_remove).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
519
src/commands/wolves.rs
Normal file
519
src/commands/wolves.rs
Normal file
|
@ -0,0 +1,519 @@
|
||||||
|
use lettre::{
|
||||||
|
message::{header, MultiPart, SinglePart},
|
||||||
|
transport::smtp::{self, authentication::Credentials},
|
||||||
|
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 sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
pub mod link {
|
||||||
|
use super::*;
|
||||||
|
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
|
||||||
|
|
||||||
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
|
let db_lock = {
|
||||||
|
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;
|
||||||
|
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let config = config_lock.read().await;
|
||||||
|
|
||||||
|
if get_server_member_discord(&db, &command.user.id).await.is_some() {
|
||||||
|
return "Already linked".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
db_pending_clear_expired(&db).await;
|
||||||
|
|
||||||
|
if get_verify_from_db(&db, &command.user.id).await.is_some() {
|
||||||
|
return "Linking already in process, please check email.".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let sub_options = if let Some(CommandDataOption {
|
||||||
|
value: CommandDataOptionValue::SubCommand(options),
|
||||||
|
..
|
||||||
|
}) = command.data.options.first()
|
||||||
|
{
|
||||||
|
options
|
||||||
|
} else {
|
||||||
|
return "Please provide sub options".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let email = if let Some(x) = sub_options.first() {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::String(email) => email.trim(),
|
||||||
|
_ => return "Please provide a valid email".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Please provide a valid email".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if email exists
|
||||||
|
let details = match get_server_member_email(&db, email).await {
|
||||||
|
None => {
|
||||||
|
let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string();
|
||||||
|
|
||||||
|
let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api));
|
||||||
|
|
||||||
|
// see if the user actually exists
|
||||||
|
let id = match wolves.get_member(email).await {
|
||||||
|
None => {
|
||||||
|
return invalid_user;
|
||||||
|
}
|
||||||
|
Some(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
// save teh user id and email to teh db
|
||||||
|
match save_to_db_user(&db, id, email).await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(x) => {
|
||||||
|
dbg!(x);
|
||||||
|
return "Error: unable to save user to teh database, contact Computer Society".to_string();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// pull it back out (technically could do it in previous step but more explicit)
|
||||||
|
match get_server_member_email(&db, email).await {
|
||||||
|
None => {
|
||||||
|
return "Error: failed to read user from database.".to_string();
|
||||||
|
}
|
||||||
|
Some(x) => x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
if details.discord.is_some() {
|
||||||
|
return "Email already verified".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a 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 {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
return format!("Unable to save to db {} {e:?}", &details.email);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
return format!("Unable to send mail to {} {e:?}", &details.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Wolves> {
|
||||||
|
sqlx::query_as::<_, Wolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM wolves
|
||||||
|
WHERE discord = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(user.get() as i64)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_server_member_email(db: &Pool<Sqlite>, email: &str) -> Option<Wolves> {
|
||||||
|
sqlx::query_as::<_, Wolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM wolves
|
||||||
|
WHERE email = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(email)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result<smtp::response::Response, smtp::Error> {
|
||||||
|
let discord = "https://computer.discord.skynet.ie";
|
||||||
|
let sender = format!("UL Computer Society <{}>", &config.mail_user);
|
||||||
|
|
||||||
|
// Create the html we want to send.
|
||||||
|
let html = html! {
|
||||||
|
head {
|
||||||
|
title { "UL Wolves Discord Linker" }
|
||||||
|
style type="text/css" {
|
||||||
|
"h2, h4 { font-family: Arial, Helvetica, sans-serif; }"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
h2 { "UL Wolves Discord Linker" }
|
||||||
|
|
||||||
|
h3 { "Link your UL Wolves Account to Discord" }
|
||||||
|
// Substitute in the name of our recipient.
|
||||||
|
p { "Hi " (user) "," }
|
||||||
|
p {
|
||||||
|
"Please paste this line into Discord (and press enter) to verify your discord account:"
|
||||||
|
br;
|
||||||
|
pre { "/wolves verify code: " (auth)}
|
||||||
|
}
|
||||||
|
hr;
|
||||||
|
h3 { "Help & Support" }
|
||||||
|
p {
|
||||||
|
"If you have issues please refer to our Computer Society Discord Server:"
|
||||||
|
br;
|
||||||
|
a href=(discord) { (discord) }
|
||||||
|
br;
|
||||||
|
"UL Computer Society"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let body_text = format!(
|
||||||
|
r#"
|
||||||
|
UL Wolves Discord Linker
|
||||||
|
Link your UL Wolves Account to Discord
|
||||||
|
|
||||||
|
Link your Account
|
||||||
|
|
||||||
|
Hi {user},
|
||||||
|
|
||||||
|
Please paste this line into Discord (and press enter) to verify your Discord account:
|
||||||
|
/wolves verify code: {auth}
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Help & Support
|
||||||
|
|
||||||
|
If you have issues please refer to our Computer Society Discord Server:
|
||||||
|
{discord}
|
||||||
|
UL Computer Society
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build the message.
|
||||||
|
let email = Message::builder()
|
||||||
|
.from(sender.parse().unwrap())
|
||||||
|
.to(mail.parse().unwrap())
|
||||||
|
.subject("Skynet: Link Discord to Wolves.")
|
||||||
|
.multipart(
|
||||||
|
// This is composed of two parts.
|
||||||
|
// also helps not trip spam settings (uneven number of url's
|
||||||
|
MultiPart::alternative()
|
||||||
|
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text))
|
||||||
|
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())),
|
||||||
|
)
|
||||||
|
.expect("failed to build email");
|
||||||
|
|
||||||
|
let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone());
|
||||||
|
|
||||||
|
// Open a remote connection to gmail using STARTTLS
|
||||||
|
let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build();
|
||||||
|
|
||||||
|
// Send the email
|
||||||
|
mailer.send(&email)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn db_pending_clear_expired(pool: &Pool<Sqlite>) -> Option<WolvesVerify> {
|
||||||
|
sqlx::query_as::<_, WolvesVerify>(
|
||||||
|
r#"
|
||||||
|
DELETE
|
||||||
|
FROM wolves_verify
|
||||||
|
WHERE date_expiry < ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(get_now_iso(true))
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_verify_from_db(db: &Pool<Sqlite>, user: &UserId) -> Option<WolvesVerify> {
|
||||||
|
sqlx::query_as::<_, WolvesVerify>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM wolves_verify
|
||||||
|
WHERE discord = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(user.get() as i64)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_to_db(db: &Pool<Sqlite>, record: &Wolves, auth: &str, user: &UserId) -> Result<Option<WolvesVerify>, sqlx::Error> {
|
||||||
|
sqlx::query_as::<_, WolvesVerify>(
|
||||||
|
"
|
||||||
|
INSERT INTO wolves_verify (email, discord, auth_code, date_expiry)
|
||||||
|
VALUES (?1, ?2, ?3, ?4)
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(record.email.to_owned())
|
||||||
|
.bind(user.get() as i64)
|
||||||
|
.bind(auth.to_owned())
|
||||||
|
.bind(get_now_iso(false))
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_to_db_user(db: &Pool<Sqlite>, id_wolves: i64, email: &str) -> Result<Option<Wolves>, sqlx::Error> {
|
||||||
|
sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
INSERT INTO wolves (id_wolves, email)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(id_wolves)
|
||||||
|
.bind(email)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 sqlx::Error;
|
||||||
|
|
||||||
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
// check if user has used /link_wolves
|
||||||
|
let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await {
|
||||||
|
x
|
||||||
|
} else {
|
||||||
|
return "Please use ''/wolves link'' first".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let sub_options = if let Some(CommandDataOption {
|
||||||
|
value: CommandDataOptionValue::SubCommand(options),
|
||||||
|
..
|
||||||
|
}) = command.data.options.first()
|
||||||
|
{
|
||||||
|
options
|
||||||
|
} else {
|
||||||
|
return "Please provide sub options".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let code = if let Some(x) = sub_options.first() {
|
||||||
|
match &x.value {
|
||||||
|
CommandDataOptionValue::String(y) => y.trim(),
|
||||||
|
_ => return "Please provide a verification code".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Please provide a verification code".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
db_pending_clear_expired(&db).await;
|
||||||
|
|
||||||
|
if details.auth_code != code {
|
||||||
|
return "Invalid verification code".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
match db_pending_clear_successful(&db, &command.user.id).await {
|
||||||
|
Ok(_) => {
|
||||||
|
return match set_discord(&db, &command.user.id, &details.email).await {
|
||||||
|
Ok(_) => {
|
||||||
|
// get teh right roles for the user
|
||||||
|
set_server_roles(&db, &command.user, ctx).await;
|
||||||
|
|
||||||
|
// check if they are a committee member, and on that server
|
||||||
|
set_server_roles_committee(&db, &command.user, ctx).await;
|
||||||
|
|
||||||
|
"Discord username linked to Wolves".to_string()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{:?}", e);
|
||||||
|
"Failed to save, please try /link_wolves again".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(e) => println!("{:?}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
"Failed to verify".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn db_pending_clear_successful(pool: &Pool<Sqlite>, user: &UserId) -> Result<Option<WolvesVerify>, Error> {
|
||||||
|
sqlx::query_as::<_, WolvesVerify>(
|
||||||
|
r#"
|
||||||
|
DELETE
|
||||||
|
FROM wolves_verify
|
||||||
|
WHERE discord = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(user.get() as i64)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_discord(db: &Pool<Sqlite>, discord: &UserId, email: &str) -> Result<Option<Wolves>, Error> {
|
||||||
|
sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
UPDATE wolves
|
||||||
|
SET discord = ?
|
||||||
|
WHERE email = ?
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(discord.get() as i64)
|
||||||
|
.bind(email)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_roles(db: &Pool<Sqlite>, discord: &User, ctx: &Context) {
|
||||||
|
if let Ok(servers) = get_servers(db, &discord.id).await {
|
||||||
|
for server in servers {
|
||||||
|
if let Ok(member) = server.server.member(&ctx.http, &discord.id).await {
|
||||||
|
if let Some(config) = get_server_config(db, &server.server).await {
|
||||||
|
let Servers {
|
||||||
|
role_past,
|
||||||
|
role_current,
|
||||||
|
..
|
||||||
|
} = config;
|
||||||
|
|
||||||
|
let mut roles = vec![];
|
||||||
|
|
||||||
|
if let Some(role) = &role_past {
|
||||||
|
if !member.roles.contains(role) {
|
||||||
|
roles.push(role.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !member.roles.contains(&role_current) {
|
||||||
|
roles.push(role_current.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = member.add_roles(&ctx, &roles).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_committees_id(db: &Pool<Sqlite>, wolves_id: i64) -> Vec<Committees> {
|
||||||
|
sqlx::query_as::<_, Committees>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM committees
|
||||||
|
WHERE committee LIKE ?1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(format!("%{}%", wolves_id))
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
dbg!(e);
|
||||||
|
vec![]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_roles_committee(db: &Pool<Sqlite>, discord: &User, ctx: &Context) {
|
||||||
|
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
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if let Ok(member) = server.member(ctx, &discord.id).await {
|
||||||
|
member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_servers(db: &Pool<Sqlite>, discord: &UserId) -> Result<Vec<ServerMembersWolves>, Error> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE discord = ?
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(discord.get() as i64)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod unlink {
|
||||||
|
use serenity::all::{CommandInteraction, Context, UserId};
|
||||||
|
use skynet_discord_bot::common::database::{DataBase, Wolves};
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||||
|
let db_lock = {
|
||||||
|
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
|
||||||
|
delete_link(&db, &command.user.id).await;
|
||||||
|
|
||||||
|
"Discord link removed".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_link(db: &Pool<Sqlite>, user: &UserId) {
|
||||||
|
match sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
UPDATE wolves
|
||||||
|
SET discord = NULL
|
||||||
|
WHERE discord = ?1;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(user.get() as i64)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register() -> CreateCommand {
|
||||||
|
CreateCommand::new("wolves")
|
||||||
|
.description("Commands related to UL Wolves")
|
||||||
|
// link
|
||||||
|
.add_option(
|
||||||
|
CreateCommandOption::new(CommandOptionType::SubCommand, "link", "Link your Wolves account to your Discord")
|
||||||
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)),
|
||||||
|
)
|
||||||
|
// verify
|
||||||
|
.add_option(
|
||||||
|
CreateCommandOption::new(CommandOptionType::SubCommand, "verify", "Verify Wolves Email")
|
||||||
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)),
|
||||||
|
)
|
||||||
|
// unlink
|
||||||
|
.add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord"))
|
||||||
|
.add_option(
|
||||||
|
CreateCommandOption::new(CommandOptionType::SubCommand, "link_minecraft", "Link your minecraft account")
|
||||||
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true))
|
||||||
|
.add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false)),
|
||||||
|
)
|
||||||
|
}
|
274
src/common/database.rs
Normal file
274
src/common/database.rs
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
pub struct DataBase;
|
||||||
|
impl TypeMapKey for DataBase {
|
||||||
|
type Value = Arc<RwLock<Pool<Sqlite>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ServerMembers {
|
||||||
|
pub server: GuildId,
|
||||||
|
pub id_wolves: i64,
|
||||||
|
pub expiry: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for ServerMembers {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
id_wolves: row.try_get("id_wolves")?,
|
||||||
|
expiry: row.try_get("expiry")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ServerMembersWolves {
|
||||||
|
pub server: GuildId,
|
||||||
|
pub id_wolves: i64,
|
||||||
|
pub expiry: String,
|
||||||
|
pub email: String,
|
||||||
|
pub discord: Option<UserId>,
|
||||||
|
pub minecraft: Option<String>,
|
||||||
|
pub minecraft_uid: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
id_wolves: row.try_get("id_wolves")?,
|
||||||
|
expiry: row.try_get("expiry")?,
|
||||||
|
email: row.try_get("email")?,
|
||||||
|
discord: get_discord_from_row(row),
|
||||||
|
minecraft: row.try_get("minecraft")?,
|
||||||
|
minecraft_uid: row.try_get("minecraft_uid")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_discord_from_row(row: &SqliteRow) -> Option<UserId> {
|
||||||
|
match row.try_get("discord") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
if tmp == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(UserId::from(tmp as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Wolves {
|
||||||
|
pub id_wolves: i64,
|
||||||
|
pub email: String,
|
||||||
|
pub discord: Option<UserId>,
|
||||||
|
pub minecraft: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for Wolves {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
id_wolves: row.try_get("id_wolves")?,
|
||||||
|
email: row.try_get("email")?,
|
||||||
|
discord: get_discord_from_row(row),
|
||||||
|
minecraft: row.try_get("minecraft")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct WolvesVerify {
|
||||||
|
pub email: String,
|
||||||
|
pub discord: UserId,
|
||||||
|
pub auth_code: String,
|
||||||
|
pub date_expiry: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for WolvesVerify {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let user_tmp: i64 = row.try_get("discord")?;
|
||||||
|
let discord = UserId::from(user_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
email: row.try_get("email")?,
|
||||||
|
discord,
|
||||||
|
auth_code: row.try_get("auth_code")?,
|
||||||
|
date_expiry: row.try_get("date_expiry")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Servers {
|
||||||
|
pub server: GuildId,
|
||||||
|
pub wolves_api: String,
|
||||||
|
pub wolves_id: i64,
|
||||||
|
pub role_past: Option<RoleId>,
|
||||||
|
pub role_current: RoleId,
|
||||||
|
pub member_past: i64,
|
||||||
|
pub member_current: i64,
|
||||||
|
pub bot_channel_id: ChannelId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for Servers {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
let role_past = match row.try_get("role_past") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
if tmp == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(RoleId::from(tmp as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let role_current = match row.try_get("role_current") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
RoleId::from(tmp as u64)
|
||||||
|
}
|
||||||
|
_ => RoleId::from(0u64),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?;
|
||||||
|
let bot_channel_id = ChannelId::from(bot_channel_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
wolves_api: row.try_get("wolves_api")?,
|
||||||
|
wolves_id: row.try_get("wolves_id").unwrap_or(0),
|
||||||
|
role_past,
|
||||||
|
role_current,
|
||||||
|
member_past: row.try_get("member_past")?,
|
||||||
|
member_current: row.try_get("member_current")?,
|
||||||
|
bot_channel_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct RoleAdder {
|
||||||
|
pub server: GuildId,
|
||||||
|
pub role_a: RoleId,
|
||||||
|
pub role_b: RoleId,
|
||||||
|
pub role_c: RoleId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for RoleAdder {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
role_a: get_role_from_row(row, "role_a"),
|
||||||
|
role_b: get_role_from_row(row, "role_b"),
|
||||||
|
role_c: get_role_from_row(row, "role_c"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId {
|
||||||
|
let id = match row.try_get(col) {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
tmp as u64
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
RoleId::from(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_channel_from_row(row: &SqliteRow, col: &str) -> ChannelId {
|
||||||
|
let id = match row.try_get(col) {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
tmp as u64
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
ChannelId::from(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
|
||||||
|
let database = format!("{}/{}", &config.home, &config.database);
|
||||||
|
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.connect_with(
|
||||||
|
SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?
|
||||||
|
.foreign_keys(true)
|
||||||
|
.create_if_missing(true),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// migrations are amazing!
|
||||||
|
sqlx::migrate!("./db/migrations").run(&pool).await?;
|
||||||
|
|
||||||
|
Ok(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_config(db: &Pool<Sqlite>, server: &GuildId) -> Option<Servers> {
|
||||||
|
sqlx::query_as::<_, Servers>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM servers
|
||||||
|
WHERE server = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(server.get() as i64)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId, member: &guild::Member) -> Result<ServerMembersWolves, Error> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE server = ? AND discord = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(server.get() as i64)
|
||||||
|
.bind(member.user.id.get() as i64)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> {
|
||||||
|
sqlx::query_as::<_, Servers>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM servers
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
174
src/common/minecraft.rs
Normal file
174
src/common/minecraft.rs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
use crate::common::set_roles::normal::get_server_member_bulk;
|
||||||
|
use crate::Config;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serenity::model::id::GuildId;
|
||||||
|
use sqlx::sqlite::SqliteRow;
|
||||||
|
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Minecraft {
|
||||||
|
pub discord: GuildId,
|
||||||
|
pub minecraft: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for Minecraft {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server_discord")?;
|
||||||
|
let discord = GuildId::from(server_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
discord,
|
||||||
|
minecraft: row.try_get("server_minecraft")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
loop through all members of server
|
||||||
|
get a list of folks with mc accounts that are members
|
||||||
|
and a list that arent members
|
||||||
|
*/
|
||||||
|
pub async fn update_server(server_id: &str, db: &Pool<Sqlite>, g_id: &GuildId, config: &Config) {
|
||||||
|
let mut usernames = vec![];
|
||||||
|
for member in get_server_member_bulk(db, g_id).await {
|
||||||
|
if let Some(x) = member.minecraft {
|
||||||
|
usernames.push((x, true));
|
||||||
|
}
|
||||||
|
if let Some(x) = member.minecraft_uid {
|
||||||
|
usernames.push((x, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !usernames.is_empty() {
|
||||||
|
whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn post<T: Serialize>(url: &str, bearer: &str, data: &T) {
|
||||||
|
match surf::post(url)
|
||||||
|
.header("Authorization", bearer)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||||
|
.body_json(&data)
|
||||||
|
{
|
||||||
|
Ok(req) => {
|
||||||
|
req.await.ok();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct ServerDetailsResSub {
|
||||||
|
pub identifier: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub is_suspended: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct ServerDetailsRes {
|
||||||
|
pub attributes: ServerDetailsResSub,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get<T: Serialize + DeserializeOwned>(url: &str, bearer: &str) -> Option<T> {
|
||||||
|
match surf::get(url)
|
||||||
|
.header("Authorization", bearer)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||||
|
.recv_json()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => Some(res),
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct BodyCommand {
|
||||||
|
command: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct BodyDelete {
|
||||||
|
root: String,
|
||||||
|
files: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn whitelist_wipe(server: &str, token: &str) {
|
||||||
|
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||||
|
let bearer = format!("Bearer {token}");
|
||||||
|
|
||||||
|
// delete whitelist
|
||||||
|
let deletion = BodyDelete {
|
||||||
|
root: "/".to_string(),
|
||||||
|
files: vec!["whitelist.json".to_string()],
|
||||||
|
};
|
||||||
|
post(&format!("{url_base}/files/delete"), &bearer, &deletion).await;
|
||||||
|
|
||||||
|
// recreate teh file, passing in the type here so the compiler knows what type of vec it is
|
||||||
|
post::<Vec<&str>>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await;
|
||||||
|
|
||||||
|
// reload the whitelist
|
||||||
|
let data = BodyCommand {
|
||||||
|
command: "whitelist reload".to_string(),
|
||||||
|
};
|
||||||
|
post(&format!("{url_base}/command"), &bearer, &data).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn server_information(server: &str, token: &str) -> Option<ServerDetailsRes> {
|
||||||
|
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||||
|
let bearer = format!("Bearer {token}");
|
||||||
|
get::<ServerDetailsRes>(&format!("{url_base}/"), &bearer).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_minecraft_config(db: &Pool<Sqlite>) -> Vec<Minecraft> {
|
||||||
|
sqlx::query_as::<_, Minecraft>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM minecraft
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Vec<Minecraft> {
|
||||||
|
sqlx::query_as::<_, Minecraft>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM minecraft
|
||||||
|
WHERE server_discord = ?1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(g_id.get() as i64)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) {
|
||||||
|
println!("Update whitelist for {}", server);
|
||||||
|
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||||
|
let bearer = format!("Bearer {token}");
|
||||||
|
|
||||||
|
for (name, java) in add {
|
||||||
|
let data = if *java {
|
||||||
|
BodyCommand {
|
||||||
|
command: format!("whitelist add {name}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BodyCommand {
|
||||||
|
command: format!("fwhitelist add {name}"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
post(&format!("{url_base}/command"), &bearer, &data).await;
|
||||||
|
}
|
||||||
|
}
|
4
src/common/mod.rs
Normal file
4
src/common/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod database;
|
||||||
|
pub mod minecraft;
|
||||||
|
pub mod set_roles;
|
||||||
|
pub mod wolves;
|
500
src/common/set_roles.rs
Normal file
500
src/common/set_roles.rs
Normal file
|
@ -0,0 +1,500 @@
|
||||||
|
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 sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &[UserId]) {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let Servers {
|
||||||
|
server,
|
||||||
|
role_past,
|
||||||
|
role_current,
|
||||||
|
..
|
||||||
|
} = server;
|
||||||
|
|
||||||
|
let mut roles_set = [0, 0, 0];
|
||||||
|
let mut members = vec![];
|
||||||
|
|
||||||
|
for member in get_server_member_bulk(&db, server).await {
|
||||||
|
if let Some(x) = member.discord {
|
||||||
|
members.push(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut members_all = members.len();
|
||||||
|
|
||||||
|
if let Ok(x) = server.members(ctx, None, None).await {
|
||||||
|
for member in x {
|
||||||
|
// members_changed acts as an override to only deal with teh users in it
|
||||||
|
if !members_changed.is_empty() && !members_changed.contains(&member.user.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if members.contains(&member.user.id) {
|
||||||
|
let mut roles = vec![];
|
||||||
|
|
||||||
|
if let Some(role) = &role_past {
|
||||||
|
if !member.roles.contains(role) {
|
||||||
|
roles_set[0] += 1;
|
||||||
|
roles.push(role.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !member.roles.contains(role_current) {
|
||||||
|
roles_set[1] += 1;
|
||||||
|
roles.push(role_current.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = member.add_roles(ctx, &roles).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// old and never
|
||||||
|
|
||||||
|
if let Some(role) = &role_past {
|
||||||
|
if member.roles.contains(role) {
|
||||||
|
members_all += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if member.roles.contains(role_current) {
|
||||||
|
roles_set[2] += 1;
|
||||||
|
// if theya re not a current member and have the role then remove it
|
||||||
|
if let Err(e) = member.remove_role(ctx, role_current).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for role in remove_roles.iter().flatten() {
|
||||||
|
if let Err(e) = member.remove_role(ctx, role).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_server_numbers(&db, server, members_all as i64, members.len() as i64).await;
|
||||||
|
|
||||||
|
// small bit of logging to note changes over time
|
||||||
|
println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.get(), roles_set[0], roles_set[1], roles_set[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE (
|
||||||
|
server = ?
|
||||||
|
AND discord IS NOT NULL
|
||||||
|
AND expiry > ?
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(server.get() as i64)
|
||||||
|
.bind(get_now_iso(true))
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_numbers(db: &Pool<Sqlite>, server: &GuildId, past: i64, current: i64) {
|
||||||
|
match sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
UPDATE servers
|
||||||
|
SET member_past = ?, member_current = ?
|
||||||
|
WHERE server = ?
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(past)
|
||||||
|
.bind(current)
|
||||||
|
.bind(server.get() as i64)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into {}", server.get());
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 serde::{Deserialize, Serialize};
|
||||||
|
use serenity::all::EditRole;
|
||||||
|
use serenity::builder::CreateChannel;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::model::channel::ChannelType;
|
||||||
|
use serenity::model::guild::Member;
|
||||||
|
use serenity::model::id::ChannelId;
|
||||||
|
use serenity::model::prelude::RoleId;
|
||||||
|
use sqlx::sqlite::SqliteRow;
|
||||||
|
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub async fn check_committee(ctx: Arc<Context>) {
|
||||||
|
let db_lock = {
|
||||||
|
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 = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This function can take a vec of members (or just one) and gives tehm the appropiate roles on teh committee server
|
||||||
|
*/
|
||||||
|
pub async fn update_committees(db: &Pool<Sqlite>, ctx: &Context, config: &Config, members: &mut Vec<Member>) {
|
||||||
|
let server = config.committee_server;
|
||||||
|
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),
|
||||||
|
];
|
||||||
|
|
||||||
|
// information about the server
|
||||||
|
let mut roles_db = HashMap::new();
|
||||||
|
for role in db_roles_get(db).await {
|
||||||
|
roles_db.insert(
|
||||||
|
role.id_wolves,
|
||||||
|
CommitteeRoles {
|
||||||
|
id_wolves: role.id_wolves,
|
||||||
|
id_role: role.id_role,
|
||||||
|
id_channel: role.id_channel,
|
||||||
|
name_role: role.name_role,
|
||||||
|
name_channel: role.name_channel,
|
||||||
|
// always start at 0
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut channels = server.channels(&ctx).await.unwrap_or_default();
|
||||||
|
|
||||||
|
// a map of users and the roles they are goign to be getting
|
||||||
|
let mut users_roles = HashMap::new();
|
||||||
|
|
||||||
|
let mut re_order = false;
|
||||||
|
// we need to create roles and channels if tehy dont already exist
|
||||||
|
let mut category_index = 0;
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
if i >= committees.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let committee = &committees[i];
|
||||||
|
|
||||||
|
// if a club/soc ever changes their name
|
||||||
|
if let Some(x) = roles_db.get_mut(&committee.id) {
|
||||||
|
committee.name_full.clone_into(&mut x.name_role);
|
||||||
|
committee.name_profile.clone_into(&mut x.name_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle new clubs/socs
|
||||||
|
if let std::collections::hash_map::Entry::Vacant(e) = roles_db.entry(committee.id) {
|
||||||
|
// create channel
|
||||||
|
// channel is first as the categories can only contain 50 channels
|
||||||
|
let channel = match server
|
||||||
|
.create_channel(
|
||||||
|
&ctx,
|
||||||
|
CreateChannel::new(&committee.name_profile)
|
||||||
|
.kind(ChannelType::Text)
|
||||||
|
.category(categories[category_index]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(x) => {
|
||||||
|
println!("Created channel: {}", &committee.name_profile);
|
||||||
|
|
||||||
|
x.id
|
||||||
|
}
|
||||||
|
Err(x) => {
|
||||||
|
let tmp = x.to_string();
|
||||||
|
dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)"));
|
||||||
|
if x.to_string().contains("Maximum number of channels in category reached (50)") {
|
||||||
|
category_index += 1;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ChannelId::new(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// create role
|
||||||
|
let role = match server
|
||||||
|
.create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(x) => x.id,
|
||||||
|
Err(_) => RoleId::new(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tmp = CommitteeRoles {
|
||||||
|
id_wolves: committee.id,
|
||||||
|
id_role: role,
|
||||||
|
id_channel: channel,
|
||||||
|
name_role: committee.name_full.to_owned(),
|
||||||
|
name_channel: committee.name_profile.to_owned(),
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// save it to the db in case of crash or error
|
||||||
|
db_role_set(db, &tmp).await;
|
||||||
|
|
||||||
|
// insert it into teh local cache
|
||||||
|
e.insert(tmp);
|
||||||
|
|
||||||
|
re_order = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for committee in &committees {
|
||||||
|
let r = if let Some(x) = roles_db.get(&committee.id) {
|
||||||
|
x.id_role
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for id_wolves in &committee.committee {
|
||||||
|
// ID in this is the wolves ID, so we need to get a matching discord ID (if one exists)
|
||||||
|
if let Some(x) = get_server_member_discord(db, id_wolves).await {
|
||||||
|
if let Some(member_tmp) = x.discord {
|
||||||
|
if server.member(ctx, &member_tmp).await.is_ok() {
|
||||||
|
let values = users_roles.entry(member_tmp).or_insert(vec![]);
|
||||||
|
values.push(r);
|
||||||
|
|
||||||
|
if let Some(x) = roles_db.get_mut(&committee.id) {
|
||||||
|
x.count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we have a map of all users that should get roles time to go through all the folks on teh server
|
||||||
|
for member in members {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut roles_rem = vec![];
|
||||||
|
let mut roles_add = vec![];
|
||||||
|
// get a list of all the roles to remove from someone
|
||||||
|
|
||||||
|
let mut roles_current_id = vec![];
|
||||||
|
for role in &roles_current {
|
||||||
|
roles_current_id.push(role.id.to_owned());
|
||||||
|
if !roles_required.contains(&role.id) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if let Some(x) = roles_db.get_mut(&0) {
|
||||||
|
x.count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for role in &roles_required {
|
||||||
|
if !roles_current_id.contains(role) {
|
||||||
|
roles_add.push(role.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !roles_rem.is_empty() {
|
||||||
|
member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !roles_add.is_empty() {
|
||||||
|
// these roles are flavor roles, only there to make folks mentionable
|
||||||
|
member.add_roles(&ctx, &roles_add).await.unwrap_or_default();
|
||||||
|
} else {
|
||||||
|
member.remove_roles(&ctx, &[committee_member]).await.unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut channel_names = vec![];
|
||||||
|
let mut positions = vec![];
|
||||||
|
for role in roles_db.values() {
|
||||||
|
// save these to db
|
||||||
|
db_role_set(db, role).await;
|
||||||
|
|
||||||
|
if re_order {
|
||||||
|
let channel_id = role.id_channel.to_owned();
|
||||||
|
if let Some(channel) = channels.get_mut(&channel_id) {
|
||||||
|
// record the position of each of teh C&S channels
|
||||||
|
positions.push(channel.position);
|
||||||
|
|
||||||
|
// pull out teh channel names
|
||||||
|
channel_names.push((role.name_channel.to_owned(), channel_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if re_order {
|
||||||
|
// sort by the position and name
|
||||||
|
positions.sort();
|
||||||
|
channel_names.sort_by_key(|(name, _)| name.to_owned());
|
||||||
|
|
||||||
|
let mut new_positions = vec![];
|
||||||
|
for (i, (_, id)) in channel_names.iter().enumerate() {
|
||||||
|
new_positions.push((id.to_owned(), positions[i] as u64));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !new_positions.is_empty() {
|
||||||
|
match server.reorder_channels(&ctx, new_positions).await {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Successfully re-orderd the committee category");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!("Failed to re-order ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct CommitteeRoles {
|
||||||
|
id_wolves: i64,
|
||||||
|
id_role: RoleId,
|
||||||
|
id_channel: ChannelId,
|
||||||
|
pub name_role: String,
|
||||||
|
name_channel: String,
|
||||||
|
pub count: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for CommitteeRoles {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
id_wolves: row.try_get("id_wolves")?,
|
||||||
|
id_role: get_role_from_row(row, "id_role"),
|
||||||
|
id_channel: get_channel_from_row(row, "id_channel"),
|
||||||
|
name_role: row.try_get("name_role")?,
|
||||||
|
name_channel: row.try_get("name_channel")?,
|
||||||
|
count: row.try_get("count")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn db_role_set(db: &Pool<Sqlite>, role: &CommitteeRoles) {
|
||||||
|
// expiry
|
||||||
|
match sqlx::query_as::<_, CommitteeRoles>(
|
||||||
|
"
|
||||||
|
INSERT INTO committee_roles (id_wolves, id_role, id_channel, name_role, name_channel, count)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
|
ON CONFLICT(id_wolves) DO UPDATE SET name_role = $4, name_channel = $5, count = $6
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(role.id_wolves)
|
||||||
|
.bind(role.id_role.get() as i64)
|
||||||
|
.bind(role.id_channel.get() as i64)
|
||||||
|
.bind(&role.name_role)
|
||||||
|
.bind(&role.name_channel)
|
||||||
|
.bind(role.count)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into Wolves {:?}", role);
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn db_roles_get(db: &Pool<Sqlite>) -> Vec<CommitteeRoles> {
|
||||||
|
// expiry
|
||||||
|
sqlx::query_as::<_, CommitteeRoles>(
|
||||||
|
"
|
||||||
|
SELECT *
|
||||||
|
FROM committee_roles
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
println!("Failure to get Roles from committee_roles");
|
||||||
|
println!("{:?}", e);
|
||||||
|
vec![]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
|
||||||
|
sqlx::query_as::<_, Committees>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM committees
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
dbg!(e);
|
||||||
|
vec![]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_server_member_discord(db: &Pool<Sqlite>, user: &i64) -> Option<Wolves> {
|
||||||
|
sqlx::query_as::<_, Wolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM wolves
|
||||||
|
WHERE id_wolves = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(user)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
}
|
278
src/common/wolves.rs
Normal file
278
src/common/wolves.rs
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
use crate::common::database::Wolves;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
/**
|
||||||
|
This file relates to anything that directly interacts with teh wolves API
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResultUserMin {
|
||||||
|
// committee: String,
|
||||||
|
member_id: String,
|
||||||
|
// first_name: String,
|
||||||
|
// last_name: String,
|
||||||
|
contact_email: String,
|
||||||
|
// opt_in_email: String,
|
||||||
|
// student_id: Option<String>,
|
||||||
|
// note: Option<String>,
|
||||||
|
// expiry: String,
|
||||||
|
// requested: String,
|
||||||
|
// approved: String,
|
||||||
|
// sitename: String,
|
||||||
|
// domain: String,
|
||||||
|
}
|
||||||
|
async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUserMin) {
|
||||||
|
// expiry
|
||||||
|
match sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
INSERT INTO wolves (id_wolves, email)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(&user.member_id)
|
||||||
|
.bind(&user.contact_email)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into Wolves {:?}", user);
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
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 sqlx::{Pool, Sqlite};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
impl From<&wolves_oxidised::WolvesUser> for WolvesResultUserMin {
|
||||||
|
fn from(value: &wolves_oxidised::WolvesUser) -> Self {
|
||||||
|
Self {
|
||||||
|
member_id: value.member_id.to_owned(),
|
||||||
|
contact_email: value.contact_email.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_wolves(ctx: &Context) {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let config_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let config = config_lock.read().await;
|
||||||
|
|
||||||
|
// set up teh client
|
||||||
|
let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api));
|
||||||
|
|
||||||
|
for server_config in get_server_config_bulk(&db).await {
|
||||||
|
let Servers {
|
||||||
|
server,
|
||||||
|
// this is the unique api key for each club/soc
|
||||||
|
wolves_api,
|
||||||
|
wolves_id,
|
||||||
|
..
|
||||||
|
} = &server_config;
|
||||||
|
// dbg!(&server_config);
|
||||||
|
|
||||||
|
let existing_tmp = get_server_member(&db, server).await;
|
||||||
|
let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>();
|
||||||
|
|
||||||
|
// list of users that need to be updated for this server
|
||||||
|
let mut user_to_update = vec![];
|
||||||
|
let mut server_name_tmp = None;
|
||||||
|
for user in wolves.get_members(wolves_api).await {
|
||||||
|
// dbg!(&user.committee);
|
||||||
|
if server_name_tmp.is_none() {
|
||||||
|
server_name_tmp = Some(user.committee_id);
|
||||||
|
}
|
||||||
|
let id = user.member_id.parse::<u64>().unwrap_or_default();
|
||||||
|
match existing.get(&(id as i64)) {
|
||||||
|
None => {
|
||||||
|
// user does not exist already, add everything
|
||||||
|
add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
|
||||||
|
add_users_server_members(&db, server, &user).await;
|
||||||
|
}
|
||||||
|
Some(old) => {
|
||||||
|
// always update wolves table, in case data has changed
|
||||||
|
add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
|
||||||
|
if old.expiry != user.expiry {
|
||||||
|
add_users_server_members(&db, server, &user).await;
|
||||||
|
|
||||||
|
if let Some(discord_id) = old.discord {
|
||||||
|
user_to_update.push(discord_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cs_id) = server_name_tmp {
|
||||||
|
if &cs_id != wolves_id {
|
||||||
|
set_server_member(&db, server, cs_id).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !user_to_update.is_empty() {
|
||||||
|
update_server(ctx, &server_config, &[], &user_to_update).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_member(db: &Pool<Sqlite>, server: &GuildId, wolves_id: i64) {
|
||||||
|
match sqlx::query_as::<_, Servers>(
|
||||||
|
"
|
||||||
|
UPDATE servers
|
||||||
|
SET wolves_id = ?
|
||||||
|
WHERE server = ?
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(wolves_id)
|
||||||
|
.bind(server.get() as i64)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to set server name {}", server.get());
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE (
|
||||||
|
server = ?
|
||||||
|
AND discord IS NOT NULL
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(server.get() as i64)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_users_server_members(db: &Pool<Sqlite>, server: &GuildId, user: &wolves_oxidised::WolvesUser) {
|
||||||
|
match sqlx::query_as::<_, ServerMembers>(
|
||||||
|
"
|
||||||
|
INSERT OR REPLACE INTO server_members (server, id_wolves, expiry)
|
||||||
|
VALUES (?1, ?2, ?3)
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(server.get() as i64)
|
||||||
|
.bind(&user.member_id)
|
||||||
|
.bind(&user.expiry)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into ServerMembers {} {:?}", server.get(), user);
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get and store the data on C&S committees
|
||||||
|
*/
|
||||||
|
pub mod committees {
|
||||||
|
use crate::common::database::DataBase;
|
||||||
|
use crate::Config;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
// Database entry for it
|
||||||
|
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||||
|
pub struct Committees {
|
||||||
|
pub id: i64,
|
||||||
|
pub name_full: String,
|
||||||
|
pub name_profile: String,
|
||||||
|
pub name_plain: String,
|
||||||
|
pub link: String,
|
||||||
|
#[sqlx(json)]
|
||||||
|
pub committee: Vec<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<wolves_oxidised::WolvesCNS> for Committees {
|
||||||
|
fn from(value: wolves_oxidised::WolvesCNS) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
name_full: value.name_full,
|
||||||
|
name_profile: value.name_profile,
|
||||||
|
name_plain: value.name_plain,
|
||||||
|
link: value.link,
|
||||||
|
committee: value.committee,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_cns(ctx: &Context) {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let config_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let config = config_lock.read().await;
|
||||||
|
|
||||||
|
let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api));
|
||||||
|
// request data from wolves
|
||||||
|
for committee_wolves in wolves.get_committees().await {
|
||||||
|
let committee = Committees::from(committee_wolves);
|
||||||
|
add_committee(&db, &committee).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_committee(db: &Pool<Sqlite>, committee: &Committees) {
|
||||||
|
match sqlx::query_as::<_, Committees>(
|
||||||
|
"
|
||||||
|
INSERT INTO committees (id, name_profile, name_full, name_plain, link, committee)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET committee = $6
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(committee.id)
|
||||||
|
.bind(&committee.name_profile)
|
||||||
|
.bind(&committee.name_full)
|
||||||
|
.bind(&committee.name_plain)
|
||||||
|
.bind(&committee.link)
|
||||||
|
.bind(serde_json::to_string(&committee.committee).unwrap_or_default())
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into Committees {:?}", committee);
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
800
src/lib.rs
800
src/lib.rs
|
@ -1,27 +1,14 @@
|
||||||
use dotenvy::dotenv;
|
pub mod common;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serenity::{
|
|
||||||
model::{
|
|
||||||
guild,
|
|
||||||
id::{GuildId, RoleId},
|
|
||||||
},
|
|
||||||
prelude::TypeMapKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::set_roles::get_server_member_bulk;
|
|
||||||
use chrono::{Datelike, SecondsFormat, Utc};
|
use chrono::{Datelike, SecondsFormat, Utc};
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use dotenvy::dotenv;
|
||||||
use serde::de::DeserializeOwned;
|
use rand::{distr::Alphanumeric, rng, Rng};
|
||||||
use serenity::client::Context;
|
use serenity::model::id::{ChannelId, GuildId, RoleId};
|
||||||
use serenity::model::id::UserId;
|
use serenity::prelude::TypeMapKey;
|
||||||
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
use std::{env, sync::Arc};
|
||||||
use sqlx::{
|
|
||||||
sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow},
|
|
||||||
Error, FromRow, Pool, Row, Sqlite,
|
|
||||||
};
|
|
||||||
use std::{env, str::FromStr, sync::Arc};
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
// manages where teh database is stored
|
// manages where teh database is stored
|
||||||
pub home: String,
|
pub home: String,
|
||||||
|
@ -30,6 +17,7 @@ pub struct Config {
|
||||||
// tokens for discord and other API's
|
// tokens for discord and other API's
|
||||||
pub discord_token: String,
|
pub discord_token: String,
|
||||||
pub discord_token_minecraft: String,
|
pub discord_token_minecraft: String,
|
||||||
|
pub minecraft_mcprofile: String,
|
||||||
|
|
||||||
// email settings
|
// email settings
|
||||||
pub mail_smtp: String,
|
pub mail_smtp: String,
|
||||||
|
@ -38,16 +26,18 @@ pub struct Config {
|
||||||
|
|
||||||
// wolves API base for clubs/socs
|
// wolves API base for clubs/socs
|
||||||
pub wolves_url: String,
|
pub wolves_url: String,
|
||||||
|
// API key for accessing more general resources
|
||||||
|
pub wolves_api: String,
|
||||||
|
|
||||||
|
// discord server for committee
|
||||||
|
pub committee_server: GuildId,
|
||||||
|
pub committee_role: RoleId,
|
||||||
|
pub committee_category: ChannelId,
|
||||||
}
|
}
|
||||||
impl TypeMapKey for Config {
|
impl TypeMapKey for Config {
|
||||||
type Value = Arc<RwLock<Config>>;
|
type Value = Arc<RwLock<Config>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DataBase;
|
|
||||||
impl TypeMapKey for DataBase {
|
|
||||||
type Value = Arc<RwLock<Pool<Sqlite>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_config() -> Config {
|
pub fn get_config() -> Config {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
|
@ -55,6 +45,7 @@ pub fn get_config() -> Config {
|
||||||
let mut config = Config {
|
let mut config = Config {
|
||||||
discord_token: "".to_string(),
|
discord_token: "".to_string(),
|
||||||
discord_token_minecraft: "".to_string(),
|
discord_token_minecraft: "".to_string(),
|
||||||
|
minecraft_mcprofile: "".to_string(),
|
||||||
|
|
||||||
home: ".".to_string(),
|
home: ".".to_string(),
|
||||||
database: "database.db".to_string(),
|
database: "database.db".to_string(),
|
||||||
|
@ -63,9 +54,13 @@ pub fn get_config() -> Config {
|
||||||
mail_user: "".to_string(),
|
mail_user: "".to_string(),
|
||||||
mail_pass: "".to_string(),
|
mail_pass: "".to_string(),
|
||||||
wolves_url: "".to_string(),
|
wolves_url: "".to_string(),
|
||||||
|
wolves_api: "".to_string(),
|
||||||
|
committee_server: GuildId::new(1),
|
||||||
|
committee_role: RoleId::new(1),
|
||||||
|
committee_category: ChannelId::new(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(x) = env::var("HOME") {
|
if let Ok(x) = env::var("DATABASE_HOME") {
|
||||||
config.home = x.trim().to_string();
|
config.home = x.trim().to_string();
|
||||||
}
|
}
|
||||||
if let Ok(x) = env::var("DATABASE") {
|
if let Ok(x) = env::var("DATABASE") {
|
||||||
|
@ -78,6 +73,9 @@ pub fn get_config() -> Config {
|
||||||
if let Ok(x) = env::var("DISCORD_TOKEN_MINECRAFT") {
|
if let Ok(x) = env::var("DISCORD_TOKEN_MINECRAFT") {
|
||||||
config.discord_token_minecraft = x.trim().to_string();
|
config.discord_token_minecraft = x.trim().to_string();
|
||||||
}
|
}
|
||||||
|
if let Ok(x) = env::var("MINECRAFT_MCPROFILE_KEY") {
|
||||||
|
config.minecraft_mcprofile = x.trim().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(x) = env::var("EMAIL_SMTP") {
|
if let Ok(x) = env::var("EMAIL_SMTP") {
|
||||||
config.mail_smtp = x.trim().to_string();
|
config.mail_smtp = x.trim().to_string();
|
||||||
|
@ -89,263 +87,32 @@ pub fn get_config() -> Config {
|
||||||
config.mail_pass = x.trim().to_string();
|
config.mail_pass = x.trim().to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(x) = env::var("WOLVES_URL") {
|
if let Ok(x) = env::var("WOLVES_URL_BASE") {
|
||||||
config.wolves_url = x.trim().to_string();
|
config.wolves_url = x.trim().to_string();
|
||||||
}
|
}
|
||||||
|
if let Ok(x) = env::var("WOLVES_API") {
|
||||||
|
config.wolves_api = x.trim().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(x) = env::var("COMMITTEE_DISCORD") {
|
||||||
|
if let Ok(x) = x.trim().parse::<u64>() {
|
||||||
|
config.committee_server = GuildId::new(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(x) = env::var("COMMITTEE_DISCORD") {
|
||||||
|
if let Ok(x) = x.trim().parse::<u64>() {
|
||||||
|
config.committee_role = RoleId::new(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(x) = env::var("COMMITTEE_CATEGORY") {
|
||||||
|
if let Ok(x) = x.trim().parse::<u64>() {
|
||||||
|
config.committee_category = ChannelId::new(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct ServerMembers {
|
|
||||||
pub server: GuildId,
|
|
||||||
pub id_wolves: i64,
|
|
||||||
pub expiry: String,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for ServerMembers {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server")?;
|
|
||||||
let server = GuildId::from(server_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
server,
|
|
||||||
id_wolves: row.try_get("id_wolves")?,
|
|
||||||
expiry: row.try_get("expiry")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct ServerMembersWolves {
|
|
||||||
pub server: GuildId,
|
|
||||||
pub id_wolves: i64,
|
|
||||||
pub expiry: String,
|
|
||||||
pub email: String,
|
|
||||||
pub discord: Option<UserId>,
|
|
||||||
pub minecraft: Option<String>,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server")?;
|
|
||||||
let server = GuildId::from(server_tmp as u64);
|
|
||||||
let discord = match row.try_get("discord") {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
if tmp == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(UserId::from(tmp as u64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
server,
|
|
||||||
id_wolves: row.try_get("id_wolves")?,
|
|
||||||
expiry: row.try_get("expiry")?,
|
|
||||||
email: row.try_get("email")?,
|
|
||||||
discord,
|
|
||||||
minecraft: row.try_get("minecraft")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct Wolves {
|
|
||||||
pub id_wolves: i64,
|
|
||||||
pub email: String,
|
|
||||||
pub discord: Option<UserId>,
|
|
||||||
pub minecraft: Option<String>,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for Wolves {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let discord = match row.try_get("discord") {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
if tmp == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(UserId::from(tmp as u64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
id_wolves: row.try_get("id_wolves")?,
|
|
||||||
email: row.try_get("email")?,
|
|
||||||
discord,
|
|
||||||
minecraft: row.try_get("minecraft")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct WolvesVerify {
|
|
||||||
pub email: String,
|
|
||||||
pub discord: UserId,
|
|
||||||
pub auth_code: String,
|
|
||||||
pub date_expiry: String,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for WolvesVerify {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let user_tmp: i64 = row.try_get("discord")?;
|
|
||||||
let discord = UserId::from(user_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
email: row.try_get("email")?,
|
|
||||||
discord,
|
|
||||||
auth_code: row.try_get("auth_code")?,
|
|
||||||
date_expiry: row.try_get("date_expiry")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct Committee {
|
|
||||||
pub email: String,
|
|
||||||
pub discord: UserId,
|
|
||||||
pub auth_code: String,
|
|
||||||
pub committee: i64,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for Committee {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let user_tmp: i64 = row.try_get("discord")?;
|
|
||||||
let discord = UserId::from(user_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
email: row.try_get("email")?,
|
|
||||||
discord,
|
|
||||||
auth_code: row.try_get("auth_code")?,
|
|
||||||
committee: row.try_get("committee")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct Servers {
|
|
||||||
pub server: GuildId,
|
|
||||||
pub wolves_api: String,
|
|
||||||
pub role_past: Option<RoleId>,
|
|
||||||
pub role_current: Option<RoleId>,
|
|
||||||
pub member_past: i64,
|
|
||||||
pub member_current: i64,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for Servers {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server")?;
|
|
||||||
let server = GuildId::from(server_tmp as u64);
|
|
||||||
let role_past = match row.try_get("role_past") {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
if tmp == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(RoleId::from(tmp as u64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let role_current = match row.try_get("role_current") {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
if tmp == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(RoleId::from(tmp as u64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
server,
|
|
||||||
wolves_api: row.try_get("wolves_api")?,
|
|
||||||
role_past,
|
|
||||||
role_current,
|
|
||||||
member_past: row.try_get("member_past")?,
|
|
||||||
member_current: row.try_get("member_current")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct Minecraft {
|
|
||||||
pub discord: GuildId,
|
|
||||||
pub minecraft: String,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for Minecraft {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server_discord")?;
|
|
||||||
let discord = GuildId::from(server_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
discord,
|
|
||||||
minecraft: row.try_get("server_minecraft")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
|
|
||||||
let database = format!("{}/{}", &config.home, &config.database);
|
|
||||||
|
|
||||||
let pool = SqlitePoolOptions::new()
|
|
||||||
.max_connections(5)
|
|
||||||
.connect_with(
|
|
||||||
SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?
|
|
||||||
.foreign_keys(true)
|
|
||||||
.create_if_missing(true),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// migrations are amazing!
|
|
||||||
sqlx::migrate!("./db/migrations").run(&pool).await.unwrap();
|
|
||||||
|
|
||||||
Ok(pool)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_config(db: &Pool<Sqlite>, server: &GuildId) -> Option<Servers> {
|
|
||||||
sqlx::query_as::<_, Servers>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM servers
|
|
||||||
WHERE server = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId, member: &guild::Member) -> Result<ServerMembersWolves, Error> {
|
|
||||||
sqlx::query_as::<_, ServerMembersWolves>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE server = ? AND discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.bind(*member.user.id.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> {
|
|
||||||
sqlx::query_as::<_, Servers>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM servers
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_now_iso(short: bool) -> String {
|
pub fn get_now_iso(short: bool) -> String {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
if short {
|
if short {
|
||||||
|
@ -356,482 +123,5 @@ pub fn get_now_iso(short: bool) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_string(len: usize) -> String {
|
pub fn random_string(len: usize) -> String {
|
||||||
thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect()
|
rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect()
|
||||||
}
|
|
||||||
|
|
||||||
pub mod set_roles {
|
|
||||||
use super::*;
|
|
||||||
pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &[UserId]) {
|
|
||||||
let db_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let db = db_lock.read().await;
|
|
||||||
|
|
||||||
let Servers {
|
|
||||||
server,
|
|
||||||
role_past,
|
|
||||||
role_current,
|
|
||||||
..
|
|
||||||
} = server;
|
|
||||||
|
|
||||||
let mut roles_set = [0, 0, 0];
|
|
||||||
let mut members = vec![];
|
|
||||||
|
|
||||||
for member in get_server_member_bulk(&db, server).await {
|
|
||||||
if let Some(x) = member.discord {
|
|
||||||
members.push(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut members_all = members.len();
|
|
||||||
|
|
||||||
if let Ok(x) = server.members(ctx, None, None).await {
|
|
||||||
for mut member in x {
|
|
||||||
// members_changed acts as an override to only deal with teh users in it
|
|
||||||
if !members_changed.is_empty() && !members_changed.contains(&member.user.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if members.contains(&member.user.id) {
|
|
||||||
let mut roles = vec![];
|
|
||||||
|
|
||||||
if let Some(role) = &role_past {
|
|
||||||
if !member.roles.contains(role) {
|
|
||||||
roles_set[0] += 1;
|
|
||||||
roles.push(role.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(role) = &role_current {
|
|
||||||
if !member.roles.contains(role) {
|
|
||||||
roles_set[1] += 1;
|
|
||||||
roles.push(role.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = member.add_roles(ctx, &roles).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// old and never
|
|
||||||
|
|
||||||
if let Some(role) = &role_past {
|
|
||||||
if member.roles.contains(role) {
|
|
||||||
members_all += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(role) = &role_current {
|
|
||||||
if member.roles.contains(role) {
|
|
||||||
roles_set[2] += 1;
|
|
||||||
// if theya re not a current member and have the role then remove it
|
|
||||||
if let Err(e) = member.remove_role(ctx, role).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for role in remove_roles.iter().flatten() {
|
|
||||||
if let Err(e) = member.remove_role(ctx, role).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set_server_numbers(&db, server, members_all as i64, members.len() as i64).await;
|
|
||||||
|
|
||||||
// small bit of logging to note changes over time
|
|
||||||
println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
|
||||||
sqlx::query_as::<_, ServerMembersWolves>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE (
|
|
||||||
server = ?
|
|
||||||
AND discord IS NOT NULL
|
|
||||||
AND expiry > ?
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.bind(get_now_iso(true))
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_server_numbers(db: &Pool<Sqlite>, server: &GuildId, past: i64, current: i64) {
|
|
||||||
match sqlx::query_as::<_, Wolves>(
|
|
||||||
"
|
|
||||||
UPDATE servers
|
|
||||||
SET member_past = ?, member_current = ?
|
|
||||||
WHERE server = ?
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(past)
|
|
||||||
.bind(current)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failure to insert into {}", server.as_u64());
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod get_data {
|
|
||||||
use super::*;
|
|
||||||
use crate::set_roles::update_server;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct WolvesResultUser {
|
|
||||||
committee: String,
|
|
||||||
member_id: String,
|
|
||||||
first_name: String,
|
|
||||||
last_name: String,
|
|
||||||
contact_email: String,
|
|
||||||
opt_in_email: String,
|
|
||||||
student_id: Option<String>,
|
|
||||||
note: Option<String>,
|
|
||||||
expiry: String,
|
|
||||||
requested: String,
|
|
||||||
approved: String,
|
|
||||||
sitename: String,
|
|
||||||
domain: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct WolvesResult {
|
|
||||||
success: i8,
|
|
||||||
result: Vec<WolvesResultUser>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct WolvesResultLocal {
|
|
||||||
pub id_wolves: String,
|
|
||||||
pub email: String,
|
|
||||||
pub expiry: String,
|
|
||||||
}
|
|
||||||
pub async fn get_wolves(ctx: &Context) {
|
|
||||||
let db_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let db = db_lock.read().await;
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
for server_config in get_server_config_bulk(&db).await {
|
|
||||||
let Servers {
|
|
||||||
server,
|
|
||||||
wolves_api,
|
|
||||||
..
|
|
||||||
} = &server_config;
|
|
||||||
|
|
||||||
let existing_tmp = get_server_member(&db, server).await;
|
|
||||||
let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
// list of users that need to be updated for this server
|
|
||||||
let mut user_to_update = vec![];
|
|
||||||
for user in get_wolves_sub(&config, wolves_api).await {
|
|
||||||
let id = user.member_id.parse::<u64>().unwrap_or_default();
|
|
||||||
match existing.get(&(id as i64)) {
|
|
||||||
None => {
|
|
||||||
// user does not exist already, add everything
|
|
||||||
add_users_wolves(&db, &user).await;
|
|
||||||
add_users_server_members(&db, server, &user).await;
|
|
||||||
}
|
|
||||||
Some(old) => {
|
|
||||||
// always update wolves table, in case data has changed
|
|
||||||
add_users_wolves(&db, &user).await;
|
|
||||||
if old.expiry != user.expiry {
|
|
||||||
add_users_server_members(&db, server, &user).await;
|
|
||||||
|
|
||||||
if let Some(discord_id) = old.discord {
|
|
||||||
user_to_update.push(discord_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !user_to_update.is_empty() {
|
|
||||||
update_server(ctx, &server_config, &[], &user_to_update).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
|
||||||
sqlx::query_as::<_, ServerMembersWolves>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE (
|
|
||||||
server = ?
|
|
||||||
AND discord IS NOT NULL
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec<WolvesResultUser> {
|
|
||||||
if config.wolves_url.is_empty() {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
// get wolves data
|
|
||||||
if let Ok(mut res) = surf::post(&config.wolves_url).header("X-AM-Identity", wolves_api).await {
|
|
||||||
if let Ok(WolvesResult {
|
|
||||||
success,
|
|
||||||
result,
|
|
||||||
}) = res.body_json().await
|
|
||||||
{
|
|
||||||
if success != 1 {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUser) {
|
|
||||||
// expiry
|
|
||||||
match sqlx::query_as::<_, Wolves>(
|
|
||||||
"
|
|
||||||
INSERT INTO wolves (id_wolves, email)
|
|
||||||
VALUES ($1, $2)
|
|
||||||
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(&user.member_id)
|
|
||||||
.bind(&user.contact_email)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failure to insert into Wolves {:?}", user);
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async fn add_users_server_members(db: &Pool<Sqlite>, server: &GuildId, user: &WolvesResultUser) {
|
|
||||||
match sqlx::query_as::<_, ServerMembers>(
|
|
||||||
"
|
|
||||||
INSERT OR REPLACE INTO server_members (server, id_wolves, expiry)
|
|
||||||
VALUES (?1, ?2, ?3)
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.bind(&user.member_id)
|
|
||||||
.bind(&user.expiry)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user);
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
For any time ye need to check if a user who calls a command has admin privlages
|
|
||||||
*/
|
|
||||||
pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> Option<String> {
|
|
||||||
let mut admin = false;
|
|
||||||
|
|
||||||
let g_id = match command.guild_id {
|
|
||||||
None => return Some("Not in a server".to_string()),
|
|
||||||
Some(x) => x,
|
|
||||||
};
|
|
||||||
|
|
||||||
let roles_server = g_id.roles(&ctx.http).await.unwrap_or_default();
|
|
||||||
|
|
||||||
if let Ok(member) = g_id.member(&ctx.http, command.user.id).await {
|
|
||||||
if let Some(permissions) = member.permissions {
|
|
||||||
if permissions.administrator() {
|
|
||||||
admin = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for role_id in member.roles {
|
|
||||||
if admin {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if let Some(role) = roles_server.get(&role_id) {
|
|
||||||
if role.permissions.administrator() {
|
|
||||||
admin = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !admin {
|
|
||||||
Some("Administrator permission required".to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
loop through all members of server
|
|
||||||
get a list of folks with mc accounts that are members
|
|
||||||
and a list that arent members
|
|
||||||
*/
|
|
||||||
pub async fn update_server(server_id: &str, db: &Pool<Sqlite>, g_id: &GuildId, config: &Config) {
|
|
||||||
let mut usernames = vec![];
|
|
||||||
for member in get_server_member_bulk(db, g_id).await {
|
|
||||||
if let Some(x) = member.minecraft {
|
|
||||||
usernames.push(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !usernames.is_empty() {
|
|
||||||
whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn post<T: Serialize>(url: &str, bearer: &str, data: &T) {
|
|
||||||
match surf::post(url)
|
|
||||||
.header("Authorization", bearer)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
|
||||||
.body_json(&data)
|
|
||||||
{
|
|
||||||
Ok(req) => {
|
|
||||||
req.await.ok();
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct ServerDetailsResSub {
|
|
||||||
pub identifier: String,
|
|
||||||
pub name: String,
|
|
||||||
pub description: String,
|
|
||||||
pub is_suspended: bool,
|
|
||||||
}
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct ServerDetailsRes {
|
|
||||||
pub attributes: ServerDetailsResSub,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get<T: Serialize + DeserializeOwned>(url: &str, bearer: &str) -> Option<T> {
|
|
||||||
match surf::get(url)
|
|
||||||
.header("Authorization", bearer)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
|
||||||
.recv_json()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct BodyCommand {
|
|
||||||
command: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct BodyDelete {
|
|
||||||
root: String,
|
|
||||||
files: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn whitelist_update(add: &Vec<String>, server: &str, token: &str) {
|
|
||||||
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
|
||||||
let bearer = format!("Bearer {token}");
|
|
||||||
|
|
||||||
for name in add {
|
|
||||||
let data = BodyCommand {
|
|
||||||
command: format!("whitelist add {name}"),
|
|
||||||
};
|
|
||||||
post(&format!("{url_base}/command"), &bearer, &data).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn whitelist_wipe(server: &str, token: &str) {
|
|
||||||
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
|
||||||
let bearer = format!("Bearer {token}");
|
|
||||||
|
|
||||||
// delete whitelist
|
|
||||||
let deletion = BodyDelete {
|
|
||||||
root: "/".to_string(),
|
|
||||||
files: vec!["whitelist.json".to_string()],
|
|
||||||
};
|
|
||||||
post(&format!("{url_base}/files/delete"), &bearer, &deletion).await;
|
|
||||||
|
|
||||||
// recreate teh file, passing in the type here so the compiler knows what type of vec it is
|
|
||||||
post::<Vec<&str>>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await;
|
|
||||||
|
|
||||||
// reload the whitelist
|
|
||||||
let data = BodyCommand {
|
|
||||||
command: "whitelist reload".to_string(),
|
|
||||||
};
|
|
||||||
post(&format!("{url_base}/command"), &bearer, &data).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn server_information(server: &str, token: &str) -> Option<ServerDetailsRes> {
|
|
||||||
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
|
||||||
let bearer = format!("Bearer {token}");
|
|
||||||
get::<ServerDetailsRes>(&format!("{url_base}/"), &bearer).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_minecraft_config(db: &Pool<Sqlite>) -> Vec<Minecraft> {
|
|
||||||
sqlx::query_as::<_, Minecraft>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM minecraft
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Vec<Minecraft> {
|
|
||||||
sqlx::query_as::<_, Minecraft>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM minecraft
|
|
||||||
WHERE server_discord = ?1
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*g_id.as_u64() as i64)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
|
|
200
src/main.rs
200
src/main.rs
|
@ -1,34 +1,52 @@
|
||||||
pub mod commands;
|
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::{
|
use serenity::{
|
||||||
async_trait,
|
async_trait,
|
||||||
client::{Context, EventHandler},
|
client::{Context, EventHandler},
|
||||||
model::{
|
model::{
|
||||||
application::{command::Command, interaction::Interaction},
|
|
||||||
gateway::{GatewayIntents, Ready},
|
gateway::{GatewayIntents, Ready},
|
||||||
guild,
|
|
||||||
prelude::Activity,
|
|
||||||
user::OnlineStatus,
|
user::OnlineStatus,
|
||||||
},
|
},
|
||||||
Client,
|
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 sqlx::{Pool, Sqlite};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, DataBase};
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
struct Handler;
|
struct Handler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
async fn guild_member_addition(&self, ctx: Context, mut new_member: guild::Member) {
|
// handles previously linked accounts joining the server
|
||||||
|
async fn guild_member_addition(&self, ctx: Context, new_member: Member) {
|
||||||
let db_lock = {
|
let db_lock = {
|
||||||
let data_read = ctx.data.read().await;
|
let data_read = ctx.data.read().await;
|
||||||
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = db_lock.read().await;
|
let db = db_lock.read().await;
|
||||||
let config = match get_server_config(&db, &new_member.guild_id).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;
|
||||||
|
|
||||||
|
// committee server takes priority
|
||||||
|
if new_member.guild_id.eq(&config_global.committee_server) {
|
||||||
|
let mut member = vec![new_member.clone()];
|
||||||
|
update_committees(&db, &ctx, &config_global, &mut member).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_server = match get_server_config(&db, &new_member.guild_id).await {
|
||||||
None => return,
|
None => return,
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
};
|
};
|
||||||
|
@ -36,67 +54,99 @@ impl EventHandler for Handler {
|
||||||
if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() {
|
if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() {
|
||||||
let mut roles = vec![];
|
let mut roles = vec![];
|
||||||
|
|
||||||
if let Some(role) = &config.role_past {
|
if let Some(role) = &config_server.role_past {
|
||||||
if !new_member.roles.contains(role) {
|
if !new_member.roles.contains(role) {
|
||||||
roles.push(role.to_owned());
|
roles.push(role.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(role) = &config.role_current {
|
if !new_member.roles.contains(&config_server.role_current) {
|
||||||
if !new_member.roles.contains(role) {
|
roles.push(config_server.role_current.to_owned());
|
||||||
roles.push(role.to_owned());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = new_member.add_roles(&ctx, &roles).await {
|
if let Err(e) = new_member.add_roles(&ctx, &roles).await {
|
||||||
println!("{:?}", e);
|
println!("{:?}", e);
|
||||||
}
|
}
|
||||||
} else if let Some(server_name) = new_member.guild_id.name(&ctx) {
|
} else {
|
||||||
let ulwolves_link = "";
|
let tmp = get_committee(&db, config_server.wolves_id).await;
|
||||||
let mut bot_channel = String::new();
|
if !tmp.is_empty() {
|
||||||
|
let committee = &tmp[0];
|
||||||
if let Ok(channels) = new_member.guild_id.channels(&ctx).await {
|
|
||||||
if let Some(channel) = channels.values().find(|c| c.name == "bot-commands") {
|
|
||||||
bot_channel = channel.id.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let bot_channel_message = match bot_channel.is_empty() {
|
|
||||||
true => "go to the bot channel".to_string(),
|
|
||||||
false => format!("go to https://discord.com/channels/{}/{}", new_member.guild_id, bot_channel),
|
|
||||||
};
|
|
||||||
|
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"Welcome {} to the {} server! \n\
|
r#"
|
||||||
Sign up on [UL Wolves]({}) and {} and use ``/link_wolves`` to get full access",
|
Welcome {} to the {} server!
|
||||||
|
Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/link_wolves`` to get full access.
|
||||||
|
"#,
|
||||||
new_member.display_name(),
|
new_member.display_name(),
|
||||||
server_name,
|
committee.name_full,
|
||||||
ulwolves_link,
|
committee.link,
|
||||||
bot_channel_message
|
&config_server.server,
|
||||||
|
&config_server.bot_channel_id
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await {
|
if let Err(err) = new_member.user.direct_message(&ctx, CreateMessage::new().content(&msg)).await {
|
||||||
dbg!(err);
|
dbg!(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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
|
||||||
|
if let Some(x) = new_data {
|
||||||
|
on_role_change(&db, &ctx, x).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn ready(&self, ctx: Context, ready: Ready) {
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||||
println!("[Main] {} is connected!", ready.user.name);
|
println!("[Main] {} is connected!", ready.user.name);
|
||||||
ctx.set_presence(Some(Activity::playing("with humanity's fate")), OnlineStatus::Online).await;
|
ctx.set_presence(Some(ActivityData::playing("with humanity's fate")), OnlineStatus::Online);
|
||||||
|
|
||||||
match Command::set_global_application_commands(&ctx.http, |commands| {
|
match Command::set_global_commands(
|
||||||
commands
|
&ctx.http,
|
||||||
.create_application_command(|command| commands::add_server::register(command))
|
vec![
|
||||||
.create_application_command(|command| commands::link_email::link::register(command))
|
commands::wolves::register(),
|
||||||
.create_application_command(|command| commands::link_email::verify::register(command))
|
commands::committee::register(),
|
||||||
.create_application_command(|command| commands::minecraft::server::add::register(command))
|
commands::minecraft::server::add::register(),
|
||||||
.create_application_command(|command| commands::minecraft::server::list::register(command))
|
commands::minecraft::server::list::register(),
|
||||||
.create_application_command(|command| commands::minecraft::server::delete::register(command))
|
commands::minecraft::server::delete::register(),
|
||||||
.create_application_command(|command| commands::minecraft::user::add::register(command))
|
],
|
||||||
// for committee server, temp
|
)
|
||||||
.create_application_command(|command| commands::committee::link::register(command))
|
.await
|
||||||
.create_application_command(|command| commands::committee::verify::register(command))
|
{
|
||||||
})
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{:?}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
match &config_global
|
||||||
|
.committee_server
|
||||||
|
.set_commands(&ctx.http, vec![commands::count::committee::register()])
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{:?}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match GuildId::new(689189992417067052)
|
||||||
|
.set_commands(&ctx.http, vec![commands::count::servers::register()])
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
|
@ -107,39 +157,79 @@ impl EventHandler for Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
if let Interaction::ApplicationCommand(command) = interaction {
|
if let Interaction::Command(command) = interaction {
|
||||||
let _ = command.defer_ephemeral(&ctx.http).await;
|
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() {
|
let content = match command.data.name.as_str() {
|
||||||
// user commands
|
// user commands
|
||||||
"link_wolves" => commands::link_email::link::run(&command, &ctx).await,
|
"wolves" => match command.data.options.first() {
|
||||||
"verify" => commands::link_email::verify::run(&command, &ctx).await,
|
None => "Invalid Command".to_string(),
|
||||||
|
Some(x) => match x.name.as_str() {
|
||||||
|
"link" => commands::wolves::link::run(&command, &ctx).await,
|
||||||
|
"verify" => commands::wolves::verify::run(&command, &ctx).await,
|
||||||
|
"unlink" => commands::wolves::unlink::run(&command, &ctx).await,
|
||||||
"link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await,
|
"link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await,
|
||||||
|
// "link" => commands::count::servers::run(&command, &ctx).await,
|
||||||
|
&_ => format!("not implemented :( wolves {}", x.name.as_str()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// admin commands
|
// admin commands
|
||||||
|
"committee" => match command.data.options.first() {
|
||||||
|
None => "Invalid Command".to_string(),
|
||||||
|
Some(x) => match x.name.as_str() {
|
||||||
"add" => commands::add_server::run(&command, &ctx).await,
|
"add" => commands::add_server::run(&command, &ctx).await,
|
||||||
|
"roles_adder" => commands::role_adder::edit::run(&command, &ctx).await,
|
||||||
|
// "link" => commands::count::servers::run(&command, &ctx).await,
|
||||||
|
&_ => format!("not implemented :( committee {}", x.name.as_str()),
|
||||||
|
},
|
||||||
|
},
|
||||||
"minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await,
|
"minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await,
|
||||||
"minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await,
|
"minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await,
|
||||||
"minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await,
|
"minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await,
|
||||||
// for teh committee server, temporary
|
// sub command
|
||||||
"link_committee" => commands::committee::link::run(&command, &ctx).await,
|
"count" => match command.data.options.first() {
|
||||||
"verify_committee" => commands::committee::verify::run(&command, &ctx).await,
|
None => "Invalid Command".to_string(),
|
||||||
_ => "not implemented :(".to_string(),
|
Some(x) => match x.name.as_str() {
|
||||||
|
"committee" => commands::count::committee::run(&command, &ctx).await,
|
||||||
|
"servers" => commands::count::servers::run(&command, &ctx).await,
|
||||||
|
&_ => format!("not implemented :( count {}", x.name.as_str()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => format!("not implemented :( {}", command.data.name.as_str()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(why) = command.edit_original_interaction_response(&ctx.http, |response| response.content(content)).await {
|
if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await {
|
||||||
println!("Cannot respond to slash command: {}", why);
|
println!("Cannot respond to slash command: {}", why);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_committee(db: &Pool<Sqlite>, wolves_id: i64) -> Vec<Committees> {
|
||||||
|
sqlx::query_as::<_, Committees>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM committees
|
||||||
|
WHERE id = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(wolves_id)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let config = get_config();
|
let config = get_config();
|
||||||
let db = match db_init(&config).await {
|
let db = match db_init(&config).await {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(_) => return,
|
Err(err) => {
|
||||||
|
dbg!(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use
|
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use
|
||||||
|
|
Loading…
Add table
Reference in a new issue