Compare commits
No commits in common. "main" and "#21_Normalise-email-inputs" have entirely different histories.
main
...
#21_Normal
49 changed files with 2207 additions and 5891 deletions
|
@ -1,10 +0,0 @@
|
||||||
on:
|
|
||||||
- push
|
|
||||||
- workflow_dispatch
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check_lfs:
|
|
||||||
# nix/docker
|
|
||||||
runs-on: nix
|
|
||||||
steps:
|
|
||||||
- uses: https://github.com/MPLew-is/lfs-check-action@1
|
|
|
@ -1,51 +0,0 @@
|
||||||
on:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check_lfs:
|
|
||||||
# nix/docker
|
|
||||||
runs-on: nix
|
|
||||||
steps:
|
|
||||||
- uses: https://github.com/MPLew-is/lfs-check-action@1
|
|
||||||
|
|
||||||
# rust code must be formatted for standardisation
|
|
||||||
lint_fmt:
|
|
||||||
# build it using teh base nixos system, helps with caching
|
|
||||||
runs-on: nix
|
|
||||||
steps:
|
|
||||||
# get the repo first
|
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
|
||||||
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8
|
|
||||||
with:
|
|
||||||
server_url: ${{ gitea.server_url }}
|
|
||||||
repository: ${{ gitea.repository }}
|
|
||||||
- run: nix build .#fmt --verbose
|
|
||||||
|
|
||||||
# clippy is incredibly useful for making yer code better
|
|
||||||
lint_clippy:
|
|
||||||
# build it using teh base nixos system, helps with caching
|
|
||||||
runs-on: nix
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
steps:
|
|
||||||
# get the repo first
|
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
|
||||||
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8
|
|
||||||
with:
|
|
||||||
server_url: ${{ gitea.server_url }}
|
|
||||||
repository: ${{ gitea.repository }}
|
|
||||||
- run: nix build .#clippy --verbose
|
|
||||||
|
|
||||||
build:
|
|
||||||
# build it using teh base nixos system, helps with caching
|
|
||||||
runs-on: nix
|
|
||||||
needs: [ lint_fmt, lint_clippy ]
|
|
||||||
steps:
|
|
||||||
# get the repo first
|
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
|
||||||
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8
|
|
||||||
with:
|
|
||||||
server_url: ${{ gitea.server_url }}
|
|
||||||
repository: ${{ gitea.repository }}
|
|
||||||
- name: "Build it locally"
|
|
||||||
run: nix build --verbose
|
|
|
@ -19,10 +19,6 @@ 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
|
||||||
|
@ -34,10 +30,6 @@ 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:
|
||||||
|
@ -47,10 +39,6 @@ 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
|
||||||
|
|
||||||
|
@ -61,7 +49,7 @@ jobs:
|
||||||
needs: [ build ]
|
needs: [ build ]
|
||||||
steps:
|
steps:
|
||||||
- name: "Deploy to Skynet"
|
- name: "Deploy to Skynet"
|
||||||
uses: https://forgejo.skynet.ie/Skynet/actions/deploy@v3
|
uses: https://forgejo.skynet.ie/Skynet/actions-deploy-to-skynet@v2
|
||||||
with:
|
with:
|
||||||
input: 'skynet_discord_bot'
|
input: 'skynet_discord_bot'
|
||||||
token: ${{ secrets.API_TOKEN_FORGEJO }}
|
token: ${{ secrets.API_TOKEN_FORGEJO }}
|
|
@ -1,2 +0,0 @@
|
||||||
# Fix typos
|
|
||||||
7e90f451965b0edbd331765ad295a02f31d2bf24
|
|
37
.gitattributes
vendored
37
.gitattributes
vendored
|
@ -1,37 +0,0 @@
|
||||||
# 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
|
|
|
@ -7,4 +7,3 @@ fn_params_layout = "Compressed"
|
||||||
struct_lit_width = 0
|
struct_lit_width = 0
|
||||||
tab_spaces = 2
|
tab_spaces = 2
|
||||||
use_small_heuristics = "Max"
|
use_small_heuristics = "Max"
|
||||||
imports_granularity = "Crate"
|
|
|
@ -1,6 +0,0 @@
|
||||||
# this file controls the
|
|
||||||
|
|
||||||
[source]
|
|
||||||
repo = "https://forgejo.skynet.ie/Computer_Society/open-goverance"
|
|
||||||
directory = "Resources/Logo_Variants"
|
|
||||||
file = "_festivals.toml"
|
|
|
@ -1,2 +0,0 @@
|
||||||
[formatting]
|
|
||||||
column_width = 120
|
|
1890
Cargo.lock
generated
1890
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
43
Cargo.toml
43
Cargo.toml
|
@ -4,56 +4,37 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "update_data"
|
name = "update_data"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "update_committee"
|
name = "update_users"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "update_minecraft"
|
name = "update_minecraft"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "update_server-icon"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "cleanup_committee"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# discord library
|
# discord library
|
||||||
serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "full"] }
|
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|
||||||
# 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"
|
surf = "2.3.2"
|
||||||
|
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15.7"
|
||||||
|
|
||||||
# For sqlite
|
# For sqlite
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "migrate"] }
|
sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] }
|
||||||
serde_json = { version = "1.0", features = ["raw_value"] }
|
|
||||||
|
|
||||||
# create random strings
|
# create random strings
|
||||||
rand = "0.9"
|
rand = "0.8.5"
|
||||||
|
|
||||||
# fancy time stuff
|
# fancy time stuff
|
||||||
chrono = "0.4"
|
chrono = "0.4.26"
|
||||||
|
|
||||||
# for email
|
# for email
|
||||||
lettre = "0.11"
|
lettre = "0.10.4"
|
||||||
maud = "0.27"
|
maud = "0.25.0"
|
||||||
|
|
||||||
toml = "0.8.23"
|
serde = "1.0.188"
|
||||||
serde = "1.0"
|
|
||||||
|
|
||||||
# for image conversion
|
|
||||||
eyre = "0.6.8"
|
|
||||||
color-eyre = "0.6.2"
|
|
||||||
usvg-text-layout = "0.29.0"
|
|
||||||
usvg = "0.29.0"
|
|
||||||
resvg = "0.29.0"
|
|
||||||
tiny-skia = "0.8.3"
|
|
74
README.md
74
README.md
|
@ -1,10 +1,70 @@
|
||||||
# Skynet Discord Bot
|
# Skynet Discord Bot
|
||||||
The Skynet bot is designed to manage users on Discord.
|
This bots core purpose is to give members roles based on their status on <ulwolves.ie>.
|
||||||
It allows users to link their UL Wolves account with Wolves in a GDPR compliant manner.
|
It uses an api key provided by wolves to get member lists.
|
||||||
Skynet (bot) is hosted by the Computer Society on Skynet (computer cluster).
|
|
||||||
|
|
||||||
## Documentation
|
Users are able to link their wolves account to the bot and that works across discord servers.
|
||||||
We have split up the documentation into different segments depending on who the user is.
|
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.
|
||||||
|
|
||||||
* [Committees](./doc/Committee.md)
|
## Commands - Admin
|
||||||
* [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``
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS committee_roles (
|
|
||||||
id_wolves integer PRIMARY KEY,
|
|
||||||
id_role integer DEFAULT 1,
|
|
||||||
id_channel integer DEFAULT 1,
|
|
||||||
-- not strictly required but for readability and debugging
|
|
||||||
name_role text NOT NULL DEFAULT '',
|
|
||||||
name_channel text NOT NULL DEFAULT '',
|
|
||||||
count integer DEFAULT 0
|
|
||||||
);
|
|
|
@ -1,9 +0,0 @@
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS server_icons (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
path TEXT NOT NULL,
|
|
||||||
date TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS index_name ON server_icons (name);
|
|
|
@ -1,11 +0,0 @@
|
||||||
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);
|
|
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
-- 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;
|
|
|
@ -1,14 +0,0 @@
|
||||||
-- 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;
|
|
|
@ -1,6 +0,0 @@
|
||||||
-- No need for this col since it is goign to be in "committees" anyways
|
|
||||||
ALTER TABLE servers DROP COLUMN server_name;
|
|
||||||
|
|
||||||
-- we do care about teh ID of the club/soc though
|
|
||||||
ALTER TABLE servers ADD COLUMN wolves_id integer DEFAULT 0;
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
# 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
26
doc/User.md
|
@ -1,26 +0,0 @@
|
||||||
# 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.lock
generated
17
flake.lock
generated
|
@ -32,22 +32,6 @@
|
||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-mozilla": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1744624473,
|
|
||||||
"narHash": "sha256-S6zT/w5SyAkJ//dYdjbrXgm+6Vkd/k7qqUl4WgZ6jjk=",
|
|
||||||
"owner": "mozilla",
|
|
||||||
"repo": "nixpkgs-mozilla",
|
|
||||||
"rev": "2292d4b35aa854e312ad2e95c4bb5c293656f21a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "mozilla",
|
|
||||||
"repo": "nixpkgs-mozilla",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1722995383,
|
"lastModified": 1722995383,
|
||||||
|
@ -67,7 +51,6 @@
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"naersk": "naersk",
|
"naersk": "naersk",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"nixpkgs-mozilla": "nixpkgs-mozilla",
|
|
||||||
"utils": "utils"
|
"utils": "utils"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
56
flake.nix
56
flake.nix
|
@ -4,10 +4,6 @@
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
naersk.url = "github:nix-community/naersk";
|
naersk.url = "github:nix-community/naersk";
|
||||||
nixpkgs-mozilla = {
|
|
||||||
url = "github:mozilla/nixpkgs-mozilla";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
utils.url = "github:numtide/flake-utils";
|
utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,33 +17,15 @@
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
utils,
|
utils,
|
||||||
naersk,
|
naersk,
|
||||||
nixpkgs-mozilla,
|
|
||||||
}:
|
}:
|
||||||
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) {
|
naersk' = pkgs.callPackage naersk {};
|
||||||
inherit system;
|
|
||||||
|
|
||||||
overlays = [
|
|
||||||
(import nixpkgs-mozilla)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
toolchain = (pkgs.rustChannelOf {
|
|
||||||
rustToolchain = ./rust-toolchain.toml;
|
|
||||||
sha256 = "sha256-KUm16pHj+cRedf8vxs/Hd2YWxpOrWZ7UOrwhILdSJBU=";
|
|
||||||
}).rust;
|
|
||||||
|
|
||||||
naersk' = pkgs.callPackage naersk {
|
|
||||||
cargo = toolchain;
|
|
||||||
rustc = toolchain;
|
|
||||||
};
|
|
||||||
package_name = "skynet_discord_bot";
|
package_name = "skynet_discord_bot";
|
||||||
desc = "Skynet Discord Bot";
|
desc = "Skynet Discord Bot";
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
openssl
|
openssl
|
||||||
glib
|
|
||||||
gdk-pixbuf
|
|
||||||
pkg-config
|
pkg-config
|
||||||
rustfmt
|
rustfmt
|
||||||
];
|
];
|
||||||
|
@ -58,10 +36,6 @@
|
||||||
pname = "${package_name}";
|
pname = "${package_name}";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
buildInputs = buildInputs;
|
buildInputs = buildInputs;
|
||||||
postInstall = ''
|
|
||||||
mkdir $out/config
|
|
||||||
cp .server-icons.toml $out/config
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
# Run `nix build .#fmt` to run tests
|
# Run `nix build .#fmt` to run tests
|
||||||
fmt = naersk'.buildPackage {
|
fmt = naersk'.buildPackage {
|
||||||
|
@ -88,15 +62,7 @@
|
||||||
|
|
||||||
# `nix develop`
|
# `nix develop`
|
||||||
devShell = pkgs.mkShell {
|
devShell = pkgs.mkShell {
|
||||||
nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook];
|
nativeBuildInputs = with pkgs; [rustc cargo pkg-config openssl rustfmt];
|
||||||
# libraries here
|
|
||||||
buildInputs = buildInputs;
|
|
||||||
RUSTC_VERSION = overrides.toolchain.channel;
|
|
||||||
# https://github.com/rust-lang/rust-bindgen#environment-variables
|
|
||||||
shellHook = ''
|
|
||||||
export PATH="''${CARGO_HOME:-~/.cargo}/bin":"$PATH"
|
|
||||||
export PATH="''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-${pkgs.stdenv.hostPlatform.rust.rustcTarget}/bin":"$PATH"
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
nixosModule = {
|
nixosModule = {
|
||||||
|
@ -122,14 +88,12 @@
|
||||||
wantedBy = [];
|
wantedBy = [];
|
||||||
after = ["network-online.target"];
|
after = ["network-online.target"];
|
||||||
environment = environment_config;
|
environment = environment_config;
|
||||||
path = with pkgs; [ git git-lfs ];
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
User = "${cfg.user}";
|
User = "${cfg.user}";
|
||||||
Group = "${cfg.user}";
|
Group = "${cfg.user}";
|
||||||
ExecStart = "${self.defaultPackage."${system}"}/bin/${script}";
|
ExecStart = "${self.defaultPackage."${system}"}/bin/${script}";
|
||||||
# kill each service if its ran for 9 min
|
|
||||||
TimeoutStartSec=540;
|
|
||||||
EnvironmentFile = [
|
EnvironmentFile = [
|
||||||
"${cfg.env.discord}"
|
"${cfg.env.discord}"
|
||||||
"${cfg.env.mail}"
|
"${cfg.env.mail}"
|
||||||
|
@ -154,17 +118,12 @@
|
||||||
|
|
||||||
# modify these
|
# modify these
|
||||||
scripts = {
|
scripts = {
|
||||||
# every 10 min
|
# every 20 min
|
||||||
"update_data" = "*:0,10,20,30,40,50";
|
"update_data" = "*:0,20,40";
|
||||||
# groups are updated every hour, offset from teh ldap
|
# groups are updated every hour, offset from teh ldap
|
||||||
"update_users" = "*:5,15,25,35,45,55";
|
"update_users" = "*:05:00";
|
||||||
# Committee server has its own timer
|
|
||||||
"update_committee" = "*:5,15,25,35,45,55";
|
|
||||||
# minecraft stuff is updated at 5am
|
# minecraft stuff is updated at 5am
|
||||||
# this service does not depend on teh discord cache
|
|
||||||
"update_minecraft" = "5:10:00";
|
"update_minecraft" = "5:10:00";
|
||||||
# server icon gets updated daily at midnight
|
|
||||||
"update_server-icon" = "0:01:00";
|
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
options.services."${package_name}" = {
|
options.services."${package_name}" = {
|
||||||
|
@ -224,7 +183,6 @@
|
||||||
after = ["network-online.target"];
|
after = ["network-online.target"];
|
||||||
wants = [];
|
wants = [];
|
||||||
environment = environment_config;
|
environment = environment_config;
|
||||||
path = with pkgs; [ git git-lfs ];
|
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
User = "${cfg.user}";
|
User = "${cfg.user}";
|
||||||
|
|
BIN
media/setup_user_01.png
(Stored with Git LFS)
BIN
media/setup_user_01.png
(Stored with Git LFS)
Binary file not shown.
BIN
media/setup_user_02.png
(Stored with Git LFS)
BIN
media/setup_user_02.png
(Stored with Git LFS)
Binary file not shown.
BIN
media/setup_user_03.png
(Stored with Git LFS)
BIN
media/setup_user_03.png
(Stored with Git LFS)
Binary file not shown.
|
@ -1,2 +1,2 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.87"
|
channel = "1.80"
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
use serenity::{
|
|
||||||
all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent},
|
|
||||||
async_trait,
|
|
||||||
client::{Context, EventHandler},
|
|
||||||
model::gateway::GatewayIntents,
|
|
||||||
Client,
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::{
|
|
||||||
database::{db_init, DataBase},
|
|
||||||
set_roles::committee::db_roles_get,
|
|
||||||
},
|
|
||||||
get_config, Config,
|
|
||||||
};
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
use std::{process, sync::Arc};
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
|
|
||||||
/// Cleanup teh Committee server
|
|
||||||
///
|
|
||||||
/// This removes any invalid roles/channels which have been set up accidentally
|
|
||||||
/// DO NOT run this locally unless you have a fresh copy of the live database handy.
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let config = get_config();
|
|
||||||
let db = match db_init(&config).await {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use
|
|
||||||
let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
|
|
||||||
// Build our client.
|
|
||||||
let mut client = Client::builder(&config.discord_token, intents)
|
|
||||||
.event_handler(Handler {})
|
|
||||||
.cache_settings(serenity::cache::Settings::default())
|
|
||||||
.await
|
|
||||||
.expect("Error creating client");
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut data = client.data.write().await;
|
|
||||||
|
|
||||||
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
|
||||||
data.insert::<DataBase>(Arc::new(db));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(why) = client.start().await {
|
|
||||||
println!("Client error: {why:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Handler;
|
|
||||||
#[async_trait]
|
|
||||||
impl EventHandler for Handler {
|
|
||||||
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config_global = config_lock.read().await;
|
|
||||||
|
|
||||||
let server = config_global.committee_server;
|
|
||||||
|
|
||||||
ctx.shard.chunk_guild(server, Some(2000), false, ChunkGuildFilter::None, None);
|
|
||||||
|
|
||||||
println!("Cache loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) {
|
|
||||||
if (chunk.chunk_index + 1) == chunk.chunk_count {
|
|
||||||
println!("Cache built successfully!");
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
cleanup(&db, &ctx, &config).await;
|
|
||||||
// finish up
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cleanup(db: &Pool<Sqlite>, ctx: &Context, config: &Config) {
|
|
||||||
let server = config.committee_server;
|
|
||||||
let committees = db_roles_get(db).await;
|
|
||||||
|
|
||||||
if let Ok(channels) = server.channels(ctx).await {
|
|
||||||
for (id, channel) in &channels {
|
|
||||||
let name = &channel.name;
|
|
||||||
let committee_tmp = committees.iter().filter(|x| &x.name_channel == name).collect::<Vec<_>>();
|
|
||||||
let committee = match committee_tmp.first() {
|
|
||||||
// if there are no committees which match then this is not a channel we care about
|
|
||||||
None => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some(x) => x,
|
|
||||||
};
|
|
||||||
|
|
||||||
// if the id of the channel does not match then remove it
|
|
||||||
if id != &committee.id_channel {
|
|
||||||
println!("Deleting Channel - ID: {} Name: {}", id, &channel.name);
|
|
||||||
if let Err(e) = channel.delete(ctx).await {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(mut roles) = server.roles(ctx).await {
|
|
||||||
for (id, role) in &mut roles {
|
|
||||||
let name = &role.name;
|
|
||||||
let committee_tmp = committees.iter().filter(|x| &x.name_role == name).collect::<Vec<_>>();
|
|
||||||
let committee = match committee_tmp.first() {
|
|
||||||
// if there are no committees which match then this is not a channel we care about
|
|
||||||
None => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some(x) => x,
|
|
||||||
};
|
|
||||||
|
|
||||||
// if the id of the role does not match then remove it
|
|
||||||
if id != &committee.id_role {
|
|
||||||
println!("Deleting Role - ID: {} Name: {}", id, &role.name);
|
|
||||||
if let Err(e) = role.delete(ctx).await {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
use serenity::{
|
|
||||||
all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent},
|
|
||||||
async_trait,
|
|
||||||
client::{Context, EventHandler},
|
|
||||||
model::gateway::GatewayIntents,
|
|
||||||
Client,
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::{
|
|
||||||
database::{db_init, DataBase},
|
|
||||||
set_roles::committee,
|
|
||||||
},
|
|
||||||
get_config, Config,
|
|
||||||
};
|
|
||||||
use std::{process, sync::Arc};
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let config = get_config();
|
|
||||||
let db = match db_init(&config).await {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use
|
|
||||||
let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
|
|
||||||
// Build our client.
|
|
||||||
let mut client = Client::builder(&config.discord_token, intents)
|
|
||||||
.event_handler(Handler {})
|
|
||||||
.cache_settings(serenity::cache::Settings::default())
|
|
||||||
.await
|
|
||||||
.expect("Error creating client");
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut data = client.data.write().await;
|
|
||||||
|
|
||||||
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
|
||||||
data.insert::<DataBase>(Arc::new(db));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(why) = client.start().await {
|
|
||||||
println!("Client error: {why:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Handler;
|
|
||||||
#[async_trait]
|
|
||||||
impl EventHandler for Handler {
|
|
||||||
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config_global = config_lock.read().await;
|
|
||||||
|
|
||||||
let server = config_global.committee_server;
|
|
||||||
|
|
||||||
ctx.shard.chunk_guild(server, Some(2000), false, ChunkGuildFilter::None, None);
|
|
||||||
|
|
||||||
println!("Cache loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) {
|
|
||||||
if (chunk.chunk_index + 1) == chunk.chunk_count {
|
|
||||||
println!("Cache built successfully!");
|
|
||||||
// u[date committee server
|
|
||||||
committee::check_committee(&ctx).await;
|
|
||||||
|
|
||||||
// finish up
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,13 +4,7 @@ use serenity::{
|
||||||
model::gateway::{GatewayIntents, Ready},
|
model::gateway::{GatewayIntents, Ready},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{
|
use skynet_discord_bot::{db_init, get_config, get_data::get_wolves, Config, DataBase};
|
||||||
common::{
|
|
||||||
database::{db_init, DataBase},
|
|
||||||
wolves::{cns::get_wolves, committees::get_cns},
|
|
||||||
},
|
|
||||||
get_config, Config,
|
|
||||||
};
|
|
||||||
use std::{process, sync::Arc};
|
use std::{process, sync::Arc};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
@ -19,10 +13,7 @@ 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(e) => {
|
Err(_) => return,
|
||||||
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
|
||||||
|
@ -30,7 +21,6 @@ async fn main() {
|
||||||
// Build our client.
|
// Build our client.
|
||||||
let mut client = Client::builder(&config.discord_token, intents)
|
let mut client = Client::builder(&config.discord_token, intents)
|
||||||
.event_handler(Handler {})
|
.event_handler(Handler {})
|
||||||
.cache_settings(serenity::cache::Settings::default())
|
|
||||||
.await
|
.await
|
||||||
.expect("Error creating client");
|
.expect("Error creating client");
|
||||||
|
|
||||||
|
@ -38,11 +28,11 @@ async fn main() {
|
||||||
let mut data = client.data.write().await;
|
let mut data = client.data.write().await;
|
||||||
|
|
||||||
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
||||||
data.insert::<DataBase>(Arc::new(db));
|
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(why) = client.start().await {
|
if let Err(why) = client.start().await {
|
||||||
println!("Client error: {why:?}");
|
println!("Client error: {:?}", why);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,12 +43,8 @@ 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,10 +1,4 @@
|
||||||
use skynet_discord_bot::{
|
use skynet_discord_bot::{db_init, get_config, get_minecraft_config, update_server, whitelist_wipe};
|
||||||
common::{
|
|
||||||
database::db_init,
|
|
||||||
minecraft::{get_minecraft_config, update_server, whitelist_wipe},
|
|
||||||
},
|
|
||||||
get_config,
|
|
||||||
};
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
use serenity::{
|
|
||||||
async_trait,
|
|
||||||
client::{Context, EventHandler},
|
|
||||||
model::gateway::{GatewayIntents, Ready},
|
|
||||||
Client,
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::{
|
|
||||||
database::{db_init, DataBase},
|
|
||||||
server_icon::{get_config_icons, update_icon},
|
|
||||||
},
|
|
||||||
get_config, Config,
|
|
||||||
};
|
|
||||||
use std::{process, sync::Arc};
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let config = get_config();
|
|
||||||
let db = match db_init(&config).await {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use
|
|
||||||
let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
|
|
||||||
// Build our client.
|
|
||||||
let mut client = Client::builder(&config.discord_token, intents)
|
|
||||||
.event_handler(Handler {})
|
|
||||||
.cache_settings(serenity::cache::Settings::default())
|
|
||||||
.await
|
|
||||||
.expect("Error creating client");
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut data = client.data.write().await;
|
|
||||||
|
|
||||||
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
|
||||||
data.insert::<DataBase>(Arc::new(db));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(why) = client.start().await {
|
|
||||||
println!("Client error: {why:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Handler;
|
|
||||||
#[async_trait]
|
|
||||||
impl EventHandler for Handler {
|
|
||||||
async fn ready(&self, ctx: Context, ready: Ready) {
|
|
||||||
let ctx = Arc::new(ctx);
|
|
||||||
println!("{} is connected!", ready.user.name);
|
|
||||||
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_global = config_lock.read().await;
|
|
||||||
let config_toml = get_config_icons::minimal();
|
|
||||||
|
|
||||||
update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await;
|
|
||||||
|
|
||||||
// finish up
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +1,11 @@
|
||||||
use serenity::{
|
use serenity::{
|
||||||
all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent},
|
|
||||||
async_trait,
|
async_trait,
|
||||||
client::{Context, EventHandler},
|
client::{Context, EventHandler},
|
||||||
model::gateway::GatewayIntents,
|
model::gateway::{GatewayIntents, Ready},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{
|
use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles, Config, DataBase};
|
||||||
common::{
|
use std::{process, sync::Arc};
|
||||||
database::{db_init, get_server_config_bulk, DataBase},
|
|
||||||
set_roles::normal,
|
|
||||||
},
|
|
||||||
get_config, Config,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
process,
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicUsize, Ordering},
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -33,11 +20,7 @@ async fn main() {
|
||||||
let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
|
let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
|
||||||
// Build our client.
|
// Build our client.
|
||||||
let mut client = Client::builder(&config.discord_token, intents)
|
let mut client = Client::builder(&config.discord_token, intents)
|
||||||
.event_handler(Handler {
|
.event_handler(Handler {})
|
||||||
server_count: Default::default(),
|
|
||||||
server_cached: Default::default(),
|
|
||||||
})
|
|
||||||
.cache_settings(serenity::cache::Settings::default())
|
|
||||||
.await
|
.await
|
||||||
.expect("Error creating client");
|
.expect("Error creating client");
|
||||||
|
|
||||||
|
@ -45,51 +28,37 @@ async fn main() {
|
||||||
let mut data = client.data.write().await;
|
let mut data = client.data.write().await;
|
||||||
|
|
||||||
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
||||||
data.insert::<DataBase>(Arc::new(db));
|
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(why) = client.start().await {
|
if let Err(why) = client.start().await {
|
||||||
println!("Client error: {why:?}");
|
println!("Client error: {:?}", why);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Handler {
|
struct Handler;
|
||||||
server_count: AtomicUsize,
|
|
||||||
server_cached: AtomicUsize,
|
|
||||||
}
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
async fn cache_ready(&self, ctx: Context, guilds: Vec<GuildId>) {
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||||
self.server_count.swap(guilds.len(), Ordering::SeqCst);
|
let ctx = Arc::new(ctx);
|
||||||
for guild in guilds {
|
println!("{} is connected!", ready.user.name);
|
||||||
ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None);
|
|
||||||
}
|
|
||||||
println!("Cache loaded {}", &self.server_count.load(Ordering::SeqCst));
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) {
|
bulk_check(Arc::clone(&ctx)).await;
|
||||||
if (chunk.chunk_index + 1) == chunk.chunk_count {
|
|
||||||
self.server_cached.fetch_add(1, Ordering::SeqCst);
|
|
||||||
if (self.server_cached.load(Ordering::SeqCst) + 1) == self.server_count.load(Ordering::SeqCst) {
|
|
||||||
println!("Cache built successfully!");
|
|
||||||
|
|
||||||
// this goes into each server and sets roles for each wolves member
|
|
||||||
check_bulk(&ctx).await;
|
|
||||||
|
|
||||||
// finish up
|
// finish up
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_bulk(ctx: &Context) {
|
async fn bulk_check(ctx: Arc<Context>) {
|
||||||
let db = {
|
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;
|
||||||
|
|
||||||
for server_config in get_server_config_bulk(&db).await {
|
for server_config in get_server_config_bulk(&db).await {
|
||||||
normal::update_server(ctx, &server_config, &[], &[]).await;
|
set_roles::update_server(&ctx, &server_config, &[], &[]).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +1,196 @@
|
||||||
use serenity::{
|
use serenity::{
|
||||||
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction},
|
builder::CreateApplicationCommand,
|
||||||
client::Context,
|
client::Context,
|
||||||
|
model::{
|
||||||
|
application::interaction::application_command::ApplicationCommandInteraction,
|
||||||
|
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::common::{
|
use skynet_discord_bot::get_data::get_wolves;
|
||||||
database::{get_server_config, DataBase, Servers},
|
use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers};
|
||||||
set_roles::normal::update_server,
|
|
||||||
wolves::cns::get_wolves,
|
|
||||||
};
|
|
||||||
use sqlx::{Error, Pool, Sqlite};
|
use sqlx::{Error, Pool, Sqlite};
|
||||||
|
|
||||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||||
let sub_options = if let Some(CommandDataOption {
|
// check if user has high enough permisssions
|
||||||
value: CommandDataOptionValue::SubCommand(options),
|
if let Some(msg) = is_admin(command, ctx).await {
|
||||||
..
|
return msg;
|
||||||
}) = command.data.options.first()
|
|
||||||
{
|
|
||||||
options
|
|
||||||
} else {
|
|
||||||
return "Please provide sub options".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
let wolves_api = if let Some(x) = sub_options.first() {
|
|
||||||
match &x.value {
|
|
||||||
CommandDataOptionValue::String(key) => key.to_string(),
|
|
||||||
_ => return "Please provide a wolves API key".to_string(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let api_key = if let CommandDataOptionValue::String(key) = command
|
||||||
|
.data
|
||||||
|
.options
|
||||||
|
.first()
|
||||||
|
.expect("Expected user option")
|
||||||
|
.resolved
|
||||||
|
.as_ref()
|
||||||
|
.expect("Expected user object")
|
||||||
|
{
|
||||||
|
key
|
||||||
} 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 Some(x) = sub_options.get(1) {
|
let role_current = if let CommandDataOptionValue::Role(role) = command
|
||||||
match &x.value {
|
.data
|
||||||
CommandDataOptionValue::Role(role) => role.to_owned(),
|
.options
|
||||||
_ => return "Please provide a valid role for ``Role Current``".to_string(),
|
.get(1)
|
||||||
}
|
.expect("Expected role option")
|
||||||
|
.resolved
|
||||||
|
.as_ref()
|
||||||
|
.expect("Expected role object")
|
||||||
|
{
|
||||||
|
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 role_past = if let Some(x) = sub_options.get(5) {
|
let mut role_past = None;
|
||||||
match &x.value {
|
if let Some(x) = command.data.options.get(5) {
|
||||||
CommandDataOptionValue::Role(role) => Some(role.to_owned()),
|
if let Some(CommandDataOptionValue::Role(role)) = &x.resolved {
|
||||||
_ => None,
|
role_past = Some(role.id.to_owned());
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let bot_channel_id = if let Some(x) = sub_options.get(2) {
|
let bot_channel_id = if let CommandDataOptionValue::Channel(channel) = command
|
||||||
match &x.value {
|
.data
|
||||||
CommandDataOptionValue::Channel(channel) => channel.to_owned(),
|
.options
|
||||||
_ => return "Please provide a valid channel for ``Bot Channel``".to_string(),
|
.get(2)
|
||||||
}
|
.expect("Expected channel option")
|
||||||
|
.resolved
|
||||||
|
.as_ref()
|
||||||
|
.expect("Expected channel object")
|
||||||
|
{
|
||||||
|
channel.id.to_owned()
|
||||||
} else {
|
} else {
|
||||||
return "Please provide a valid channel for ``Bot Channel``".to_string();
|
return "Please provide a valid channel for ``Bot Channel``".to_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = {
|
let server_name = if let CommandDataOptionValue::String(name) = command
|
||||||
|
.data
|
||||||
|
.options
|
||||||
|
.get(3)
|
||||||
|
.expect("Expected Server Name option")
|
||||||
|
.resolved
|
||||||
|
.as_ref()
|
||||||
|
.expect("Expected Server Name object")
|
||||||
|
{
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
&"UL Computer Society".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let wolves_link = if let CommandDataOptionValue::String(wolves) = command
|
||||||
|
.data
|
||||||
|
.options
|
||||||
|
.get(4)
|
||||||
|
.expect("Expected Wolves Link option")
|
||||||
|
.resolved
|
||||||
|
.as_ref()
|
||||||
|
.expect("Expected Server Name object")
|
||||||
|
{
|
||||||
|
wolves
|
||||||
|
} else {
|
||||||
|
&"https://ulwolves.ie/society/computer".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
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()
|
||||||
};
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
let server_data = Servers {
|
let server_data = Servers {
|
||||||
server: command.guild_id.unwrap_or_default(),
|
server: command.guild_id.unwrap_or_default(),
|
||||||
wolves_api,
|
wolves_api: api_key.to_owned(),
|
||||||
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,
|
bot_channel_id,
|
||||||
|
server_name: server_name.to_owned(),
|
||||||
|
wolves_link: wolves_link.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match add_server(&db, ctx, &server_data).await {
|
match add_server(&db, ctx, &server_data).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{e:?}");
|
println!("{:?}", e);
|
||||||
return format!("Failure to insert into Servers {server_data:?}");
|
return format!("Failure to insert into Servers {:?}", server_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"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("bot_channel")
|
||||||
|
.description("Safe space for folks to use the bot commands.")
|
||||||
|
.kind(CommandOptionType::Channel)
|
||||||
|
.required(true)
|
||||||
|
})
|
||||||
|
.create_option(|option| {
|
||||||
|
option
|
||||||
|
.name("server_name")
|
||||||
|
.description("Name of the Discord Server.")
|
||||||
|
.kind(CommandOptionType::String)
|
||||||
|
.required(true)
|
||||||
|
})
|
||||||
|
.create_option(|option| {
|
||||||
|
option
|
||||||
|
.name("wolves_link")
|
||||||
|
.description("Link to the Club/Society on UL Wolves.")
|
||||||
|
.kind(CommandOptionType::String)
|
||||||
|
.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.get() as i64);
|
let role_past = server.role_past.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, bot_channel_id)
|
INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current, bot_channel_id, server_name, wolves_link)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(server.server.get() as i64)
|
.bind(*server.server.as_u64() as i64)
|
||||||
.bind(&server.wolves_api)
|
.bind(&server.wolves_api)
|
||||||
.bind(role_past)
|
.bind(role_past)
|
||||||
.bind(server.role_current.get() as i64)
|
.bind(*server.role_current.as_u64() as i64)
|
||||||
.bind(server.bot_channel_id.get() as i64)
|
.bind(*server.bot_channel_id.as_u64() as i64)
|
||||||
|
.bind(&server.server_name)
|
||||||
|
.bind(&server.wolves_link)
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// if the entry does not exist already then do a user update
|
// if the entry does not exist already tehn do a user update
|
||||||
let (update, current_remove, current_role, past_remove, past_role) = match &existing {
|
let (update, current_remove, current_role, past_remove, past_role) = match &existing {
|
||||||
None => (true, false, None, false, None),
|
None => (true, false, None, false, None),
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
|
|
|
@ -1,27 +1,311 @@
|
||||||
use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption};
|
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::{random_string, Config, DataBase};
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
pub mod link {
|
||||||
CreateCommand::new("committee")
|
use super::*;
|
||||||
.description("Commands related to what committees can do")
|
use serenity::model::id::GuildId;
|
||||||
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
use skynet_discord_bot::Committee;
|
||||||
.add_option(
|
|
||||||
CreateCommandOption::new(CommandOptionType::SubCommand, "add", "Enable the bot for this discord")
|
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true))
|
let committee_server = GuildId(1220150752656363520);
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true))
|
match command.guild_id {
|
||||||
.add_sub_option(
|
None => {
|
||||||
CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true),
|
return "Not in correct discord server.".to_string();
|
||||||
)
|
}
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false)),
|
Some(x) => {
|
||||||
)
|
if x != committee_server {
|
||||||
.add_option(
|
return "Not in correct discord server.".to_string();
|
||||||
CreateCommandOption::new(CommandOptionType::SubCommand, "roles_adder", "Combine roles together to an new one")
|
}
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true))
|
}
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true))
|
}
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true))
|
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)),
|
let option = command
|
||||||
)
|
.data
|
||||||
.add_option(
|
.options
|
||||||
CreateCommandOption::new(CommandOptionType::SubCommandGroup, "icon", "Committee commands for the server icon")
|
.first()
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "change", "Change the server icon.")),
|
.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)
|
||||||
|
.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");
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,259 +0,0 @@
|
||||||
pub mod committee {
|
|
||||||
|
|
||||||
// get the list of all the current clubs/socs members
|
|
||||||
|
|
||||||
use serenity::all::{
|
|
||||||
CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption,
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::common::{database::DataBase, set_roles::committee::db_roles_get};
|
|
||||||
|
|
||||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
|
||||||
let sub_options = if let Some(CommandDataOption {
|
|
||||||
value: CommandDataOptionValue::SubCommand(key),
|
|
||||||
..
|
|
||||||
}) = command.data.options.first()
|
|
||||||
{
|
|
||||||
key
|
|
||||||
} else {
|
|
||||||
return "Please provide a wolves API key".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
let all = if let Some(x) = sub_options.first() {
|
|
||||||
match x.value {
|
|
||||||
CommandDataOptionValue::Boolean(y) => y,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut cs = vec![];
|
|
||||||
// pull it from a DB
|
|
||||||
for committee in db_roles_get(&db).await {
|
|
||||||
if !all && committee.count == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cs.push((committee.count, committee.name_role.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
cs.sort_by_key(|(count, _)| *count);
|
|
||||||
cs.reverse();
|
|
||||||
|
|
||||||
// msg can be a max 2000 chars long
|
|
||||||
let mut limit = 2000 - 3;
|
|
||||||
|
|
||||||
let mut response = vec!["```".to_string()];
|
|
||||||
for (count, name) in cs {
|
|
||||||
let leading = if count < 10 { " " } else { "" };
|
|
||||||
|
|
||||||
let line = format!("{leading}{count} {name}");
|
|
||||||
|
|
||||||
let length = line.len() + 1;
|
|
||||||
|
|
||||||
if length < (limit + 3) {
|
|
||||||
response.push(line);
|
|
||||||
limit -= length;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response.push("```".to_string());
|
|
||||||
|
|
||||||
response.join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
|
||||||
CreateCommand::new("count")
|
|
||||||
.description("Count Committee Members")
|
|
||||||
// All Committee members are able to add reactions to posts
|
|
||||||
.default_member_permissions(serenity::model::Permissions::ADD_REACTIONS)
|
|
||||||
.add_option(
|
|
||||||
CreateCommandOption::new(CommandOptionType::SubCommand, "committee", "List out the Committee Roles Numbers")
|
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod servers {
|
|
||||||
// get the list of all the current clubs/socs
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption};
|
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::{
|
|
||||||
database::{get_server_config_bulk, DataBase},
|
|
||||||
set_roles::committee::get_committees,
|
|
||||||
},
|
|
||||||
get_now_iso,
|
|
||||||
};
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String {
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut committees = HashMap::new();
|
|
||||||
if let Some(x) = get_committees(&db).await {
|
|
||||||
for committee in x {
|
|
||||||
committees.insert(committee.id, committee.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cs = vec![];
|
|
||||||
// pull it from a DB
|
|
||||||
for server_config in get_server_config_bulk(&db).await {
|
|
||||||
if let Some(x) = committees.get(&server_config.wolves_id) {
|
|
||||||
cs.push((server_config.member_current, server_config.member_past, x.name_full.to_owned()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all members
|
|
||||||
let (wolves_current, wolves_past, total) = get_wolves_total(&db).await;
|
|
||||||
cs.push((total, total, String::from("Skynet Network")));
|
|
||||||
cs.push((wolves_current, wolves_past, String::from("Clubs/Socs Servers")));
|
|
||||||
|
|
||||||
// treat teh committee server as its own thing
|
|
||||||
let committee_current = get_wolves_committee(&db).await;
|
|
||||||
cs.push((committee_current, committee_current, String::from("Committee Server")));
|
|
||||||
|
|
||||||
cs.sort_by_key(|(current, _, _)| *current);
|
|
||||||
cs.reverse();
|
|
||||||
|
|
||||||
// msg can be a max 2000 chars long
|
|
||||||
let mut limit = 2000 - 3;
|
|
||||||
|
|
||||||
let mut response = vec!["```".to_string()];
|
|
||||||
for (current, past, name) in cs {
|
|
||||||
let current_leading = if current < 10 {
|
|
||||||
" "
|
|
||||||
} else if current < 100 {
|
|
||||||
" "
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
let past_leading = if past < 10 {
|
|
||||||
" "
|
|
||||||
} else if past < 100 {
|
|
||||||
" "
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let line = format!("{current_leading}{current} {past_leading}{past} {name}");
|
|
||||||
|
|
||||||
let length = line.len() + 1;
|
|
||||||
|
|
||||||
// +3 is to account for the closing fence
|
|
||||||
if length < (limit + 3) {
|
|
||||||
response.push(line);
|
|
||||||
limit -= length;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response.push("```".to_string());
|
|
||||||
|
|
||||||
response.join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)]
|
|
||||||
pub struct Count {
|
|
||||||
pub count: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_wolves_total(db: &Pool<Sqlite>) -> (i64, i64, i64) {
|
|
||||||
let current = match sqlx::query_as::<_, Count>(
|
|
||||||
r#"
|
|
||||||
SELECT COUNT(DISTINCT id_wolves) as count
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE (
|
|
||||||
wolves.discord IS NOT NULL
|
|
||||||
AND server_members.expiry > ?
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(get_now_iso(true))
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) => res.count,
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let cns = match sqlx::query_as::<_, Count>(
|
|
||||||
r#"
|
|
||||||
SELECT COUNT(DISTINCT id_wolves) as count
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE wolves.discord IS NOT NULL
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(get_now_iso(true))
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) => res.count,
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let total = match sqlx::query_as::<_, Count>(
|
|
||||||
r#"
|
|
||||||
SELECT COUNT(DISTINCT id_wolves) as count
|
|
||||||
FROM wolves
|
|
||||||
WHERE discord IS NOT NULL
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) => res.count,
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(current, cns, total)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_wolves_committee(db: &Pool<Sqlite>) -> i64 {
|
|
||||||
// expiry
|
|
||||||
match sqlx::query_as::<_, Count>(
|
|
||||||
"
|
|
||||||
SELECT count
|
|
||||||
FROM committee_roles
|
|
||||||
WHERE id_wolves = '0'
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) => res.count,
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
|
||||||
CreateCommand::new("count")
|
|
||||||
.description("Count the servers")
|
|
||||||
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
|
||||||
.add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "servers", "List out all servers using the skynet bot"))
|
|
||||||
}
|
|
||||||
}
|
|
381
src/commands/link_email.rs
Normal file
381
src/commands/link_email.rs
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
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 matches (including case) 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)?.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 !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_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,32 +1,40 @@
|
||||||
use serenity::client::Context;
|
use serenity::{
|
||||||
|
builder::CreateApplicationCommand,
|
||||||
|
client::Context,
|
||||||
|
model::{
|
||||||
|
application::interaction::application_command::ApplicationCommandInteraction,
|
||||||
|
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use skynet_discord_bot::common::database::DataBase;
|
use skynet_discord_bot::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::wolves::link::get_server_member_discord;
|
use crate::commands::link_email::link::get_server_member_discord;
|
||||||
use serde::{Deserialize, Serialize};
|
use serenity::model::id::UserId;
|
||||||
use serenity::{
|
use skynet_discord_bot::{whitelist_update, Config, Minecraft, Wolves};
|
||||||
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction},
|
|
||||||
model::id::UserId,
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::{
|
|
||||||
database::Wolves,
|
|
||||||
minecraft::{whitelist_update, Minecraft},
|
|
||||||
},
|
|
||||||
Config,
|
|
||||||
};
|
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
|
|
||||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
let db = {
|
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 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()
|
||||||
};
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
let config_lock = {
|
let config_lock = {
|
||||||
let data_read = ctx.data.read().await;
|
let data_read = ctx.data.read().await;
|
||||||
|
@ -36,71 +44,36 @@ pub(crate) mod user {
|
||||||
|
|
||||||
// user has to have previously linked with wolves
|
// user has to have previously linked with wolves
|
||||||
if get_server_member_discord(&db, &command.user.id).await.is_none() {
|
if get_server_member_discord(&db, &command.user.id).await.is_none() {
|
||||||
return "Not linked with wolves, please use ``/wolves link`` with your wolves email.".to_string();
|
return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let sub_options = if let Some(CommandDataOption {
|
let username = if let CommandDataOptionValue::String(username) = command
|
||||||
value: CommandDataOptionValue::SubCommand(options),
|
.data
|
||||||
..
|
.options
|
||||||
}) = command.data.options.first()
|
.first()
|
||||||
|
.expect("Expected username option")
|
||||||
|
.resolved
|
||||||
|
.as_ref()
|
||||||
|
.expect("Expected username object")
|
||||||
{
|
{
|
||||||
options
|
username.trim()
|
||||||
} 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(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
dbg!("{:?}", e);
|
dbg!("{:?}", e);
|
||||||
return format!("Failure to minecraft username {username:?}");
|
return format!("Failure to minecraft username {:?}", username);
|
||||||
}
|
|
||||||
}
|
|
||||||
username_mc = username.to_string();
|
|
||||||
} else {
|
|
||||||
match get_minecraft_bedrock(username, &config.minecraft_mcprofile).await {
|
|
||||||
None => {
|
|
||||||
return format!("No UID found for {username:?}");
|
|
||||||
}
|
|
||||||
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_mc.to_owned(), java)], &server.minecraft, &config.discord_token_minecraft).await;
|
whitelist_update(&vec![username.to_string()], &server.minecraft, &config.discord_token_minecraft).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,52 +88,7 @@ pub(crate) mod user {
|
||||||
WHERE discord = ?1;
|
WHERE discord = ?1;
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(user.get() as i64)
|
.bind(*user.as_u64() 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
|
||||||
|
@ -179,7 +107,7 @@ pub(crate) mod user {
|
||||||
) sub on minecraft.server_discord = sub.server
|
) sub on minecraft.server_discord = sub.server
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(discord.get() as i64)
|
.bind(*discord.as_u64() as i64)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -190,53 +118,56 @@ pub(crate) mod server {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub(crate) mod add {
|
pub(crate) mod add {
|
||||||
use serenity::{
|
use serenity::model::id::GuildId;
|
||||||
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption},
|
|
||||||
model::id::GuildId,
|
|
||||||
};
|
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
// this is to manage the server side of commands related to minecraft
|
// this is to managfe the server side of commands related to minecraft
|
||||||
use super::*;
|
use super::*;
|
||||||
use skynet_discord_bot::{
|
use skynet_discord_bot::{is_admin, update_server, Config, Minecraft};
|
||||||
common::minecraft::{update_server, Minecraft},
|
|
||||||
Config,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
CreateCommand::new("minecraft_add")
|
command.name("minecraft_add").description("Add a minecraft server").create_option(|option| {
|
||||||
.description("Add a minecraft server")
|
option
|
||||||
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
.name("server_id")
|
||||||
.add_option(
|
.description("ID of the Minecraft server hosted by the Computer Society")
|
||||||
CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society")
|
.kind(CommandOptionType::String)
|
||||||
.required(true),
|
.required(true)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &ApplicationCommandInteraction, 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 Some(CommandDataOption {
|
let server_minecraft = if let CommandDataOptionValue::String(id) = command
|
||||||
value: CommandDataOptionValue::String(id),
|
.data
|
||||||
..
|
.options
|
||||||
}) = command.data.options.first()
|
.first()
|
||||||
|
.expect("Expected server_id option")
|
||||||
|
.resolved
|
||||||
|
.as_ref()
|
||||||
|
.expect("Expected server_id object")
|
||||||
{
|
{
|
||||||
id.to_string()
|
id.to_owned()
|
||||||
} else {
|
} else {
|
||||||
return String::from("Expected Server ID");
|
return String::from("Expected Server ID");
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = {
|
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()
|
||||||
};
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
match add_server(&db, &g_id, &server_minecraft).await {
|
match add_server(&db, &g_id, &server_minecraft).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{e:?}");
|
println!("{:?}", e);
|
||||||
return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft);
|
return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,7 +190,7 @@ pub(crate) mod server {
|
||||||
VALUES (?1, ?2)
|
VALUES (?1, ?2)
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(discord.get() as i64)
|
.bind(*discord.as_u64() as i64)
|
||||||
.bind(minecraft)
|
.bind(minecraft)
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await
|
.await
|
||||||
|
@ -267,31 +198,29 @@ pub(crate) mod server {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod list {
|
pub(crate) mod list {
|
||||||
use serenity::{all::CommandInteraction, builder::CreateCommand, client::Context};
|
use serenity::builder::CreateApplicationCommand;
|
||||||
use skynet_discord_bot::{
|
use serenity::client::Context;
|
||||||
common::{
|
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
||||||
database::DataBase,
|
use skynet_discord_bot::{get_minecraft_config_server, is_admin, server_information, Config, DataBase};
|
||||||
minecraft::{get_minecraft_config_server, server_information},
|
|
||||||
},
|
|
||||||
Config,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
CreateCommand::new("minecraft_list")
|
command.name("minecraft_list").description("List your minecraft servers")
|
||||||
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
|
||||||
.description("List your minecraft servers")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &ApplicationCommandInteraction, 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = {
|
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()
|
||||||
};
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
let servers = get_minecraft_config_server(&db, g_id).await;
|
let servers = get_minecraft_config_server(&db, g_id).await;
|
||||||
|
|
||||||
|
@ -314,7 +243,7 @@ pub(crate) mod server {
|
||||||
ID: {id}
|
ID: {id}
|
||||||
Online: {online}
|
Online: {online}
|
||||||
Info: {description}
|
Info: {description}
|
||||||
Link: <https://panel.games.skynet.ie/server/{id}>
|
Link: <http://panel.games.skynet.ie/server/{id}>
|
||||||
"#,
|
"#,
|
||||||
name = &x.attributes.name,
|
name = &x.attributes.name,
|
||||||
online = !x.attributes.is_suspended,
|
online = !x.attributes.is_suspended,
|
||||||
|
@ -328,50 +257,58 @@ pub(crate) mod server {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod delete {
|
pub(crate) mod delete {
|
||||||
use serenity::{
|
use serenity::builder::CreateApplicationCommand;
|
||||||
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption},
|
use serenity::client::Context;
|
||||||
builder::CreateCommand,
|
use serenity::model::application::command::CommandOptionType;
|
||||||
client::Context,
|
use serenity::model::id::GuildId;
|
||||||
model::id::GuildId,
|
use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue};
|
||||||
};
|
use skynet_discord_bot::{is_admin, DataBase, Minecraft};
|
||||||
use skynet_discord_bot::common::{database::DataBase, minecraft::Minecraft};
|
|
||||||
use sqlx::{Error, Pool, Sqlite};
|
use sqlx::{Error, Pool, Sqlite};
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
CreateCommand::new("minecraft_delete")
|
command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| {
|
||||||
.description("Delete a minecraft server")
|
option
|
||||||
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
|
.name("server_id")
|
||||||
.add_option(
|
.description("ID of the Minecraft server hosted by the Computer Society")
|
||||||
CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society")
|
.kind(CommandOptionType::String)
|
||||||
.required(true),
|
.required(true)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &ApplicationCommandInteraction, 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 Some(CommandDataOption {
|
let server_minecraft = if let CommandDataOptionValue::String(id) = command
|
||||||
value: CommandDataOptionValue::String(id),
|
.data
|
||||||
..
|
.options
|
||||||
}) = command.data.options.first()
|
.first()
|
||||||
|
.expect("Expected server_id option")
|
||||||
|
.resolved
|
||||||
|
.as_ref()
|
||||||
|
.expect("Expected server_id object")
|
||||||
{
|
{
|
||||||
id.to_string()
|
id.to_owned()
|
||||||
} else {
|
} else {
|
||||||
return String::from("Expected Server ID");
|
return String::from("Expected Server ID");
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = {
|
let db_lock = {
|
||||||
let data = ctx.data.read().await;
|
let data_read = ctx.data.read().await;
|
||||||
data.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||||
};
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
match server_remove(&db, &g_id, &server_minecraft).await {
|
match server_remove(&db, &g_id, &server_minecraft).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{e:?}");
|
println!("{:?}", e);
|
||||||
return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft);
|
return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -388,7 +325,7 @@ pub(crate) mod server {
|
||||||
WHERE server_discord = ?1 AND server_minecraft = ?2
|
WHERE server_discord = ?1 AND server_minecraft = ?2
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(discord.get() as i64)
|
.bind(*discord.as_u64() as i64)
|
||||||
.bind(minecraft)
|
.bind(minecraft)
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
pub mod add_server;
|
pub mod add_server;
|
||||||
pub mod committee;
|
pub mod committee;
|
||||||
pub mod count;
|
pub mod link_email;
|
||||||
pub mod minecraft;
|
pub mod minecraft;
|
||||||
pub mod role_adder;
|
|
||||||
pub mod server_icon;
|
|
||||||
pub mod wolves;
|
|
||||||
|
|
|
@ -1,192 +0,0 @@
|
||||||
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 = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
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, 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 ones 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 and B give them C
|
|
||||||
if new_data.roles.contains(&role_adder.role_a) && new_data.roles.contains(&role_adder.role_b) && !new_data.roles.contains(&role_adder.role_c)
|
|
||||||
{
|
|
||||||
roles_add.push(role_adder.role_c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,223 +0,0 @@
|
||||||
use serenity::all::{CommandInteraction, Context};
|
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::{
|
|
||||||
database::DataBase,
|
|
||||||
server_icon::{get_config_icons, update_icon::update_icon_main, ServerIcons},
|
|
||||||
},
|
|
||||||
Config,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption};
|
|
||||||
|
|
||||||
// commands that server mods are able to use
|
|
||||||
pub(crate) mod admin {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
// Moderators can force a icon change
|
|
||||||
pub(crate) mod change {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String {
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config_global = config_lock.read().await;
|
|
||||||
|
|
||||||
let config_toml = get_config_icons::minimal();
|
|
||||||
update_icon_main(ctx, &db, &config_global, &config_toml).await;
|
|
||||||
|
|
||||||
"Changed server Icon".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// commands for general users
|
|
||||||
pub(crate) mod user {
|
|
||||||
use super::*;
|
|
||||||
use skynet_discord_bot::common::server_icon::get_config_icons::ConfigTomlLocal;
|
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
|
||||||
CreateCommand::new("icon")
|
|
||||||
.description("Commands related to the Server Icon")
|
|
||||||
.add_option(
|
|
||||||
CreateCommandOption::new(CommandOptionType::SubCommandGroup, "current", "Information on current items.")
|
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "icon", "Information on current icon."))
|
|
||||||
.add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "festival", "Information on current festival.")),
|
|
||||||
)
|
|
||||||
.add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "stats", "How many times the particular icon has been used"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_logo_url(config_toml: &ConfigTomlLocal, logo_name: &str) -> String {
|
|
||||||
format!("{}/src/branch/main/{}/{}", &config_toml.source.repo, &config_toml.source.directory, logo_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Regular users can get teh link to teh current icon
|
|
||||||
pub(crate) mod current {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub(crate) mod icon {
|
|
||||||
use super::*;
|
|
||||||
use serenity::all::{CreateAttachment, EditInteractionResponse};
|
|
||||||
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
|
|
||||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_toml = get_config_icons::minimal();
|
|
||||||
|
|
||||||
if let Some(logo) = get_current_icon(&db).await {
|
|
||||||
if let Ok(attachment) = CreateAttachment::path(&logo.path).await {
|
|
||||||
match command.edit_response(&ctx.http, EditInteractionResponse::new().new_attachment(attachment)).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("[{}]({})", &logo.name, get_logo_url(&config_toml, &logo.name))
|
|
||||||
} else {
|
|
||||||
"Could not find current icon".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_current_icon(db: &Pool<Sqlite>) -> Option<ServerIcons> {
|
|
||||||
match sqlx::query_as::<_, ServerIcons>(
|
|
||||||
"
|
|
||||||
SELECT * from server_icons ORDER BY id DESC LIMIT 1
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) mod festival {
|
|
||||||
use serenity::all::{CommandInteraction, Context};
|
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::server_icon::{get_config_icons, update_icon::get_festival},
|
|
||||||
Config,
|
|
||||||
};
|
|
||||||
|
|
||||||
// use this to return what current festivals are active?
|
|
||||||
pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String {
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
let config_toml = get_config_icons::full(&config);
|
|
||||||
|
|
||||||
let response = get_festival(&config_toml).current;
|
|
||||||
|
|
||||||
if response.is_empty() {
|
|
||||||
"No festival currently active".to_string()
|
|
||||||
} else {
|
|
||||||
format!("Festivals active: {}", response.join(", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the statistics of the icons
|
|
||||||
pub(crate) mod stats {
|
|
||||||
use super::*;
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
|
|
||||||
pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String {
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_toml = get_config_icons::minimal();
|
|
||||||
|
|
||||||
let totals = get_totals(&db).await;
|
|
||||||
|
|
||||||
fmt_msg(&config_toml, &totals)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
|
||||||
pub struct CountResult {
|
|
||||||
pub name: String,
|
|
||||||
pub times: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_totals(db: &Pool<Sqlite>) -> Vec<CountResult> {
|
|
||||||
sqlx::query_as::<_, CountResult>(
|
|
||||||
"
|
|
||||||
SELECT
|
|
||||||
DISTINCT name,
|
|
||||||
COUNT(*) OVER(PARTITION BY name) AS times
|
|
||||||
FROM server_icons
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
dbg!(e);
|
|
||||||
vec![]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_msg(config_toml: &ConfigTomlLocal, totals: &[CountResult]) -> String {
|
|
||||||
let mut totals_local = totals.to_owned();
|
|
||||||
totals_local.sort_by_key(|x| x.times);
|
|
||||||
totals_local.reverse();
|
|
||||||
|
|
||||||
// msg can be a max 2000 chars long
|
|
||||||
let mut limit = 2000 - 3;
|
|
||||||
|
|
||||||
let mut response = vec![];
|
|
||||||
for CountResult {
|
|
||||||
name,
|
|
||||||
times,
|
|
||||||
} in &totals_local
|
|
||||||
{
|
|
||||||
let current_leading = if times < &10 {
|
|
||||||
"00"
|
|
||||||
} else if times < &100 {
|
|
||||||
"0"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let url = get_logo_url(config_toml, name);
|
|
||||||
|
|
||||||
// the `` is so that the numbers will be rendered in monospaced font
|
|
||||||
// the <> is to suppress the URL embed
|
|
||||||
let line = format!("``{current_leading}{times}`` [{name}](<{url}>)");
|
|
||||||
|
|
||||||
let length = line.len() + 1;
|
|
||||||
|
|
||||||
// +3 is to account for the closing fence
|
|
||||||
if length < (limit + 3) {
|
|
||||||
response.push(line);
|
|
||||||
limit -= length;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.join("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,547 +0,0 @@
|
||||||
use lettre::{
|
|
||||||
message::{header, MultiPart, SinglePart},
|
|
||||||
transport::smtp::{self, authentication::Credentials},
|
|
||||||
Message, SmtpTransport, Transport,
|
|
||||||
};
|
|
||||||
use maud::html;
|
|
||||||
use serenity::{
|
|
||||||
all::CommandOptionType,
|
|
||||||
builder::{CreateCommand, CreateCommandOption},
|
|
||||||
client::Context,
|
|
||||||
model::id::UserId,
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::database::{DataBase, Wolves, WolvesVerify},
|
|
||||||
get_now_iso, random_string, Config,
|
|
||||||
};
|
|
||||||
use 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 = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = 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 an auth key
|
|
||||||
let auth = random_string(20);
|
|
||||||
match send_mail(&config, &details.email, &auth, &command.user.name) {
|
|
||||||
Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await {
|
|
||||||
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 {email}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Wolves> {
|
|
||||||
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 urls)
|
|
||||||
MultiPart::alternative()
|
|
||||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text))
|
|
||||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())),
|
|
||||||
)
|
|
||||||
.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 link_docs {
|
|
||||||
use super::*;
|
|
||||||
pub mod users {
|
|
||||||
use super::*;
|
|
||||||
use serenity::all::CommandInteraction;
|
|
||||||
|
|
||||||
pub async fn run(_command: &CommandInteraction, _ctx: &Context) -> String {
|
|
||||||
"https://forgejo.skynet.ie/Skynet/discord-bot/src/branch/main/doc/User.md".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub mod committee {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod verify {
|
|
||||||
use super::*;
|
|
||||||
use crate::commands::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db};
|
|
||||||
use serenity::{
|
|
||||||
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction},
|
|
||||||
model::user::User,
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::common::{
|
|
||||||
database::{get_server_config, ServerMembersWolves, Servers},
|
|
||||||
wolves::committees::Committees,
|
|
||||||
};
|
|
||||||
use sqlx::Error;
|
|
||||||
|
|
||||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
if let Some(x) = get_server_member_discord(db, &discord.id).await {
|
|
||||||
// if they are a member of one or more committees, and in teh committee server then give them the general committee role
|
|
||||||
// they will get teh more specific vanity role later
|
|
||||||
if !get_committees_id(db, x.id_wolves).await.is_empty() {
|
|
||||||
let server = config.committee_server;
|
|
||||||
let committee_member = config.committee_role;
|
|
||||||
|
|
||||||
if let Ok(member) = server.member(ctx, &discord.id).await {
|
|
||||||
member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_servers(db: &Pool<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 = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// doesn't matter if there is one or not, it will be removed regardless
|
|
||||||
delete_link(&db, &command.user.id).await;
|
|
||||||
|
|
||||||
"Discord link removed".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_link(db: &Pool<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)),
|
|
||||||
)
|
|
||||||
.add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "docs", "Link to where the documentation can be found."))
|
|
||||||
}
|
|
|
@ -1,278 +0,0 @@
|
||||||
use crate::Config;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serenity::{
|
|
||||||
model::{
|
|
||||||
guild,
|
|
||||||
id::{ChannelId, GuildId, RoleId, UserId},
|
|
||||||
},
|
|
||||||
prelude::TypeMapKey,
|
|
||||||
};
|
|
||||||
use sqlx::{
|
|
||||||
sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow},
|
|
||||||
Error, FromRow, Pool, Row, Sqlite,
|
|
||||||
};
|
|
||||||
use std::{str::FromStr, sync::Arc};
|
|
||||||
|
|
||||||
pub struct DataBase;
|
|
||||||
impl TypeMapKey for DataBase {
|
|
||||||
type Value = Arc<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()
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
use crate::{common::set_roles::normal::get_server_member_bulk, Config};
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
||||||
use serenity::model::id::GuildId;
|
|
||||||
use sqlx::{sqlite::SqliteRow, 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 aren't 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 the file, passing in the type here so the compiler knows what type of Vec it is
|
|
||||||
post::<Vec<&str>>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await;
|
|
||||||
|
|
||||||
// reload the whitelist
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
pub mod database;
|
|
||||||
pub mod minecraft;
|
|
||||||
pub mod set_roles;
|
|
||||||
pub mod wolves;
|
|
||||||
|
|
||||||
pub mod renderer;
|
|
||||||
pub mod server_icon;
|
|
|
@ -1,193 +0,0 @@
|
||||||
// this code is taken from https://github.com/MCorange99/svg2colored-png/tree/main
|
|
||||||
// I was unable to figure out how to use usvg myself so yoinked it from here.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
ffi::OsStr,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use color_eyre::{eyre::bail, Result};
|
|
||||||
use usvg_text_layout::TreeTextToPath;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Args {
|
|
||||||
pub input: PathBuf,
|
|
||||||
|
|
||||||
/// Output folder where the PNG's will be placed
|
|
||||||
pub output: PathBuf,
|
|
||||||
|
|
||||||
/// Comma separated colors that will be used in HEX Eg. 000000,ffffff
|
|
||||||
/// Can be like an object: black:000000,white:ffffff
|
|
||||||
pub colors: String,
|
|
||||||
|
|
||||||
/// Width of the generated PNG's
|
|
||||||
pub width: u32,
|
|
||||||
|
|
||||||
/// Height of the generated PNG's
|
|
||||||
pub height: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum ColorType {
|
|
||||||
Array(Vec<String>),
|
|
||||||
Object(Vec<(String, String)>),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Renderer {
|
|
||||||
fontdb: usvg_text_layout::fontdb::Database,
|
|
||||||
colors: ColorType,
|
|
||||||
size: (u32, u32),
|
|
||||||
pub count: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Renderer {
|
|
||||||
pub fn new(args: &Args) -> Result<Self> {
|
|
||||||
let mut db = usvg_text_layout::fontdb::Database::new();
|
|
||||||
db.load_system_fonts();
|
|
||||||
|
|
||||||
let mut this = Self {
|
|
||||||
fontdb: db,
|
|
||||||
colors: ColorType::None,
|
|
||||||
size: (args.width, args.height),
|
|
||||||
count: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let colors = if args.colors.contains(':') {
|
|
||||||
//? object
|
|
||||||
let obj = args
|
|
||||||
.colors
|
|
||||||
.split(',')
|
|
||||||
.map(|s| {
|
|
||||||
let s = s.split(':').collect::<Vec<&str>>();
|
|
||||||
|
|
||||||
if s.len() < 2 {
|
|
||||||
dbg!("Invalid color object, try checking help");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((s[0].to_string(), s[1].to_string()))
|
|
||||||
})
|
|
||||||
.collect::<Vec<Option<(String, String)>>>();
|
|
||||||
|
|
||||||
let mut colors = Vec::new();
|
|
||||||
|
|
||||||
for c in obj.into_iter().flatten() {
|
|
||||||
std::fs::create_dir_all(args.output.join(&c.0))?;
|
|
||||||
|
|
||||||
colors.push(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorType::Object(colors)
|
|
||||||
} else {
|
|
||||||
//? list
|
|
||||||
// let colors = args.colors.split(",").map(|s| {
|
|
||||||
// s.to_string()
|
|
||||||
// })
|
|
||||||
// .collect::<Vec<String>>();
|
|
||||||
|
|
||||||
let mut colors = Vec::new();
|
|
||||||
|
|
||||||
for color in args.colors.split(',') {
|
|
||||||
std::fs::create_dir_all(args.output.join(color))?;
|
|
||||||
colors.push(color.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorType::Array(colors)
|
|
||||||
};
|
|
||||||
|
|
||||||
this.colors = colors;
|
|
||||||
Ok(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(&mut self, fi: &Path, args: &Args) -> Result<()> {
|
|
||||||
match fi.extension() {
|
|
||||||
Some(e) if e.to_str() == Some("svg") => {}
|
|
||||||
Some(_) | None => {
|
|
||||||
dbg!("Filer {:?} is not of type SVG", fi);
|
|
||||||
// util::logger::warning(format!("File '{}' is not of SVG type", fi.clone().to_str().unwrap()));
|
|
||||||
bail!("Failed to render");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.colors.clone() {
|
|
||||||
ColorType::Array(c) => {
|
|
||||||
for color in c {
|
|
||||||
// log::info!("Rendering the color {color:?}");
|
|
||||||
let fo = self.get_out_file(fi, &color, args);
|
|
||||||
self.render_one(fi, &fo, &color)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ColorType::Object(c) => {
|
|
||||||
for o in c {
|
|
||||||
// log::info!("Rendering the color {:?}", o);
|
|
||||||
let fo = self.get_out_file(fi, &o.0, args);
|
|
||||||
self.render_one(fi, &fo, &o.1)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ColorType::None => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_one(&mut self, fi: &Path, fo: &Path, color: &String) -> Result<()> {
|
|
||||||
if fo.exists() {
|
|
||||||
dbg!("File {fo:?} exists, skipping");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let svg = self.set_color(&self.get_svg_data(fi)?, color);
|
|
||||||
|
|
||||||
let opt = usvg::Options {
|
|
||||||
// Get file's absolute directory.
|
|
||||||
resources_dir: std::fs::canonicalize(fi).ok().and_then(|p| p.parent().map(|p| p.to_path_buf())),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tree = match usvg::Tree::from_data(svg.as_bytes(), &opt) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => {
|
|
||||||
dbg!("Failed to parse {fi:?}");
|
|
||||||
bail!("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tree.convert_text(&self.fontdb);
|
|
||||||
|
|
||||||
let mut pixmap = tiny_skia::Pixmap::new(self.size.0, self.size.1).unwrap();
|
|
||||||
|
|
||||||
// log::info!("Rendering {fo:?}");
|
|
||||||
|
|
||||||
//? maybe handle this and possibly throw error if its none
|
|
||||||
let _ = resvg::render(&tree, usvg::FitTo::Size(self.size.0, self.size.1), tiny_skia::Transform::default(), pixmap.as_mut());
|
|
||||||
|
|
||||||
pixmap.save_png(fo)?;
|
|
||||||
self.count += 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_out_file(&mut self, fi: &Path, _sub_folder: &str, args: &Args) -> PathBuf {
|
|
||||||
let mut fo: std::path::PathBuf = args.output.clone();
|
|
||||||
// fo.push(sub_folder);
|
|
||||||
fo.push(fi.file_name().unwrap_or(OsStr::new("default")).to_str().unwrap_or("default").replace(".svg", ""));
|
|
||||||
fo.set_extension("png");
|
|
||||||
fo
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_color(&self, svg: &str, color: &String) -> String {
|
|
||||||
svg.replace("fill=\"currentColor\"", &format!("fill=\"#{color}\""))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_svg_data(&self, fi: &Path) -> Result<String> {
|
|
||||||
match std::fs::read_to_string(fi) {
|
|
||||||
Ok(d) => Ok(d),
|
|
||||||
Err(_) => {
|
|
||||||
dbg!("File {fi:?} does not exist");
|
|
||||||
bail!("File {fi:?} does not exist");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,404 +0,0 @@
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{ffi::OsString, fs, path::PathBuf};
|
|
||||||
|
|
||||||
pub mod get_config_icons {
|
|
||||||
use super::*;
|
|
||||||
use crate::Config;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ConfigToml {
|
|
||||||
pub source: ConfigTomlSource,
|
|
||||||
pub festivals: Vec<ConfigTomlFestivals>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ConfigTomlLocal {
|
|
||||||
pub source: ConfigTomlSource,
|
|
||||||
}
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ConfigTomlRemote {
|
|
||||||
pub festivals: Vec<ConfigTomlFestivals>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct ConfigTomlSource {
|
|
||||||
pub repo: String,
|
|
||||||
pub directory: String,
|
|
||||||
pub file: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct ConfigTomlFestivals {
|
|
||||||
pub name: String,
|
|
||||||
pub all_year: bool,
|
|
||||||
pub start: ConfigTomlFestivalsTime,
|
|
||||||
pub end: ConfigTomlFestivalsTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct ConfigTomlFestivalsTime {
|
|
||||||
pub day: u32,
|
|
||||||
pub month: u32,
|
|
||||||
pub year: i32,
|
|
||||||
}
|
|
||||||
pub fn minimal() -> ConfigTomlLocal {
|
|
||||||
let toml_raw_min = include_str!("../../.server-icons.toml");
|
|
||||||
toml::from_str::<ConfigTomlLocal>(toml_raw_min).unwrap_or_else(|e| {
|
|
||||||
dbg!(e);
|
|
||||||
ConfigTomlLocal {
|
|
||||||
source: ConfigTomlSource {
|
|
||||||
repo: "".to_string(),
|
|
||||||
directory: "".to_string(),
|
|
||||||
file: "".to_string(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// since a copy of the festival file is in the repo we just need to get to it
|
|
||||||
pub fn full(config: &Config) -> ConfigToml {
|
|
||||||
let config_source = minimal();
|
|
||||||
|
|
||||||
let file_path = format!("{}/open-governance/{}/{}", &config.home, &config_source.source.directory, &config_source.source.file);
|
|
||||||
let contents = fs::read_to_string(file_path).unwrap_or_else(|e| {
|
|
||||||
dbg!(e);
|
|
||||||
"".to_string()
|
|
||||||
});
|
|
||||||
let festivals = match toml::from_str::<ConfigTomlRemote>(&contents) {
|
|
||||||
Ok(config_festivals) => config_festivals.festivals,
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigToml {
|
|
||||||
source: config_source.source,
|
|
||||||
festivals,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LogoData {
|
|
||||||
pub name: OsString,
|
|
||||||
pub path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
|
||||||
pub struct ServerIcons {
|
|
||||||
pub id: i64,
|
|
||||||
pub name: String,
|
|
||||||
pub path: String,
|
|
||||||
pub date: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod update_icon {
|
|
||||||
use super::*;
|
|
||||||
use crate::{
|
|
||||||
common::{
|
|
||||||
renderer::{Args, Renderer},
|
|
||||||
server_icon::get_config_icons::{self, ConfigToml, ConfigTomlLocal},
|
|
||||||
},
|
|
||||||
get_now_iso, Config,
|
|
||||||
};
|
|
||||||
use chrono::{Datelike, Utc};
|
|
||||||
use rand::{rngs::SmallRng, seq::IndexedRandom, SeedableRng};
|
|
||||||
use serenity::{
|
|
||||||
all::GuildId,
|
|
||||||
builder::{CreateAttachment, EditGuild},
|
|
||||||
client::Context,
|
|
||||||
};
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
/// Update the server icon, pulling from open governance.
|
|
||||||
pub async fn update_icon_main(ctx: &Context, db: &Pool<Sqlite>, config_global: &Config, config_toml_local: &ConfigTomlLocal) {
|
|
||||||
let server = config_global.compsoc_server;
|
|
||||||
|
|
||||||
// clone repo into local folder
|
|
||||||
clone_repo(config_global, config_toml_local);
|
|
||||||
|
|
||||||
// now the repo has been downloaded/updated we can now access the festivals
|
|
||||||
let config_toml = get_config_icons::full(config_global);
|
|
||||||
|
|
||||||
// see if there is a current festival
|
|
||||||
let festival_data = get_festival(&config_toml);
|
|
||||||
|
|
||||||
// get a list of all the graphics files
|
|
||||||
let logos = get_logos(config_global, &config_toml);
|
|
||||||
|
|
||||||
// filter them so only the current season (if any) are active
|
|
||||||
let logos_filtered = logos_filter(&festival_data, logos);
|
|
||||||
|
|
||||||
let mut rng = SmallRng::from_os_rng();
|
|
||||||
let logo_selected = logos_filtered.choose(&mut rng).unwrap();
|
|
||||||
|
|
||||||
logo_set(ctx, db, &server, logo_selected).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FestivalData {
|
|
||||||
pub current: Vec<String>,
|
|
||||||
exclusions: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_festival(config_toml: &ConfigToml) -> FestivalData {
|
|
||||||
let today = Utc::now();
|
|
||||||
let day = today.day();
|
|
||||||
let month = today.month();
|
|
||||||
let year = today.year();
|
|
||||||
|
|
||||||
let mut result = FestivalData {
|
|
||||||
current: vec![],
|
|
||||||
exclusions: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
for festival in &config_toml.festivals {
|
|
||||||
if (day >= festival.start.day && day <= festival.end.day) && (month >= festival.start.month && month <= festival.end.month) {
|
|
||||||
if festival.start.year == 0 || festival.end.year == 0 || (year >= festival.start.year && year <= festival.end.year) {
|
|
||||||
result.current.push(festival.name.to_owned());
|
|
||||||
}
|
|
||||||
} else if !festival.all_year {
|
|
||||||
result.exclusions.push(festival.name.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_repo(config: &Config, config_toml: &ConfigTomlLocal) {
|
|
||||||
let url = &config_toml.source.repo;
|
|
||||||
let folder = format!("{}/open-governance", &config.home);
|
|
||||||
|
|
||||||
if let Err(e) = Command::new("git")
|
|
||||||
// clone the repo, gracefully "fails"
|
|
||||||
.arg("clone")
|
|
||||||
.arg(url)
|
|
||||||
.arg(&folder)
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = Command::new("git")
|
|
||||||
// Update the repo
|
|
||||||
.arg("pull")
|
|
||||||
.arg("origin")
|
|
||||||
.arg("main")
|
|
||||||
.current_dir(&folder)
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = Command::new("git")
|
|
||||||
// Install LFS for the repo
|
|
||||||
.arg("lfs")
|
|
||||||
.arg("install")
|
|
||||||
.current_dir(&folder)
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = Command::new("git")
|
|
||||||
// clone the repo, gracefully "fails"
|
|
||||||
.arg("lfs")
|
|
||||||
.arg("pull")
|
|
||||||
.arg("origin")
|
|
||||||
.arg("main")
|
|
||||||
.current_dir(&folder)
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec<LogoData> {
|
|
||||||
let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory);
|
|
||||||
let folder_path = PathBuf::from(&folder);
|
|
||||||
let mut folder_output = folder_path.clone();
|
|
||||||
folder_output.push("converted");
|
|
||||||
let paths = match fs::read_dir(folder) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let args = Args {
|
|
||||||
input: folder_path.clone(),
|
|
||||||
output: folder_output,
|
|
||||||
colors: String::from(""),
|
|
||||||
width: 1024,
|
|
||||||
height: 1024,
|
|
||||||
};
|
|
||||||
let mut r = match Renderer::new(&args) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(e) => {
|
|
||||||
let _ = dbg!(e);
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut logos = vec![];
|
|
||||||
|
|
||||||
for tmp in paths.flatten() {
|
|
||||||
let path_local = tmp.path().to_owned();
|
|
||||||
let path_local2 = tmp.path().to_owned();
|
|
||||||
let name = match path_local2.file_name() {
|
|
||||||
None => {
|
|
||||||
dbg!(path_local2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some(x) => x.to_owned(),
|
|
||||||
};
|
|
||||||
let mut path = tmp.path();
|
|
||||||
|
|
||||||
if path.is_dir() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
match tmp.path().extension() {
|
|
||||||
None => {}
|
|
||||||
Some(ext) => {
|
|
||||||
if ext == "svg" {
|
|
||||||
let mut path_new = path_local.clone();
|
|
||||||
path_new.set_extension("png");
|
|
||||||
let filename_tmp = path_new.clone();
|
|
||||||
let filename = match filename_tmp.file_name() {
|
|
||||||
None => {
|
|
||||||
dbg!(filename_tmp);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some(x) => x,
|
|
||||||
};
|
|
||||||
path_new.pop();
|
|
||||||
path_new.push("converted");
|
|
||||||
path_new.push(filename);
|
|
||||||
|
|
||||||
// check if exists
|
|
||||||
if !path_new.exists() {
|
|
||||||
// convert if it hasn't been converted already
|
|
||||||
match r.render(&path_local, &args) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(_e) => {
|
|
||||||
dbg!("Failed to render {path_local:?}: {}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path = path_new;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
logos.push(LogoData {
|
|
||||||
name,
|
|
||||||
path,
|
|
||||||
});
|
|
||||||
|
|
||||||
// println!("Name: {}", &tmp.path().display());
|
|
||||||
}
|
|
||||||
|
|
||||||
logos
|
|
||||||
}
|
|
||||||
|
|
||||||
fn logos_filter(festival_data: &FestivalData, existing: Vec<LogoData>) -> Vec<LogoData> {
|
|
||||||
let mut filtered: Vec<LogoData> = vec![];
|
|
||||||
|
|
||||||
let allowed_files = vec![".png", ".jpeg", ".gif", ".svg"];
|
|
||||||
'outer: for logo in existing {
|
|
||||||
let name_lowercase0 = logo.name.to_ascii_lowercase();
|
|
||||||
let name_lowercase = name_lowercase0.to_str().unwrap_or_default();
|
|
||||||
let mut allowed = false;
|
|
||||||
for allowed_type in &allowed_files {
|
|
||||||
if name_lowercase.ends_with(allowed_type) {
|
|
||||||
allowed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !allowed {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !festival_data.current.is_empty() {
|
|
||||||
// if its a current festival filter based on it
|
|
||||||
for festival in &festival_data.current {
|
|
||||||
if name_lowercase.contains(festival) {
|
|
||||||
filtered.push(logo);
|
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// else filter using the excluded ones
|
|
||||||
let mut excluded = false;
|
|
||||||
for festival in &festival_data.exclusions {
|
|
||||||
if name_lowercase.contains(festival) {
|
|
||||||
excluded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !excluded {
|
|
||||||
filtered.push(logo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn logo_set(ctx: &Context, db: &Pool<Sqlite>, server: &GuildId, logo_selected: &LogoData) {
|
|
||||||
// add to teh database
|
|
||||||
if !logo_set_db(db, logo_selected).await {
|
|
||||||
// something went wrong
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(logo_path) = logo_selected.path.to_str() {
|
|
||||||
match CreateAttachment::path(logo_path).await {
|
|
||||||
Ok(icon) => {
|
|
||||||
// assuming a `guild` has already been bound
|
|
||||||
let builder = EditGuild::new().icon(Some(&icon));
|
|
||||||
if let Err(e) = server.edit(ctx, builder).await {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn logo_set_db(db: &Pool<Sqlite>, logo_selected: &LogoData) -> bool {
|
|
||||||
let name = match logo_selected.name.to_str() {
|
|
||||||
None => return false,
|
|
||||||
Some(x) => x,
|
|
||||||
};
|
|
||||||
let path = match logo_selected.path.to_str() {
|
|
||||||
None => return false,
|
|
||||||
Some(x) => x,
|
|
||||||
};
|
|
||||||
|
|
||||||
match sqlx::query_as::<_, ServerIcons>(
|
|
||||||
"
|
|
||||||
INSERT OR REPLACE INTO server_icons (name, date, path)
|
|
||||||
VALUES (?1, ?2, ?3)
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(name)
|
|
||||||
.bind(get_now_iso(false))
|
|
||||||
.bind(path)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,533 +0,0 @@
|
||||||
pub mod normal {
|
|
||||||
use crate::{
|
|
||||||
common::database::{DataBase, ServerMembersWolves, Servers, Wolves},
|
|
||||||
get_now_iso,
|
|
||||||
};
|
|
||||||
use serenity::{
|
|
||||||
client::Context,
|
|
||||||
model::id::{GuildId, RoleId, UserId},
|
|
||||||
};
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
|
|
||||||
struct RolesChange {
|
|
||||||
total: i32,
|
|
||||||
new: i32,
|
|
||||||
current_add: i32,
|
|
||||||
current_rem: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &[UserId]) {
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let Servers {
|
|
||||||
server,
|
|
||||||
role_past,
|
|
||||||
role_current,
|
|
||||||
..
|
|
||||||
} = server;
|
|
||||||
|
|
||||||
let mut roles_set = RolesChange {
|
|
||||||
total: 0,
|
|
||||||
new: 0,
|
|
||||||
current_add: 0,
|
|
||||||
current_rem: 0,
|
|
||||||
};
|
|
||||||
let mut members = vec![];
|
|
||||||
|
|
||||||
for member in get_server_member_bulk(&db, server).await {
|
|
||||||
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 the users in it
|
|
||||||
if !members_changed.is_empty() && !members_changed.contains(&member.user.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if members.contains(&member.user.id) {
|
|
||||||
roles_set.total += 1;
|
|
||||||
|
|
||||||
let mut roles = vec![];
|
|
||||||
|
|
||||||
if let Some(role) = &role_past {
|
|
||||||
if !member.roles.contains(role) {
|
|
||||||
roles_set.new += 1;
|
|
||||||
roles.push(role.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !member.roles.contains(role_current) {
|
|
||||||
roles_set.current_add += 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.current_rem += 1;
|
|
||||||
// if they're not a current member and have the role then remove it
|
|
||||||
if let Err(e) = member.remove_role(ctx, role_current).await {
|
|
||||||
println!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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!(
|
|
||||||
"{:?} Total: {} Changes: New: +{}, Current: +{}/-{}",
|
|
||||||
server.get(),
|
|
||||||
roles_set.total,
|
|
||||||
roles_set.new,
|
|
||||||
roles_set.current_add,
|
|
||||||
roles_set.current_rem
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
|
||||||
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},
|
|
||||||
wolves::committees::Committees,
|
|
||||||
},
|
|
||||||
Config,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serenity::{
|
|
||||||
all::EditRole,
|
|
||||||
builder::CreateChannel,
|
|
||||||
client::Context,
|
|
||||||
model::{channel::ChannelType, guild::Member, id::ChannelId, prelude::RoleId},
|
|
||||||
};
|
|
||||||
use sqlx::{sqlite::SqliteRow, Error, FromRow, Pool, Row, Sqlite};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub async fn check_committee(ctx: &Context) {
|
|
||||||
let db = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let 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 them the appropriate roles on teh committee server
|
|
||||||
*/
|
|
||||||
pub async fn update_committees(db: &Pool<Sqlite>, ctx: &Context, config: &Config, members: &mut Vec<Member>) {
|
|
||||||
let server = config.committee_server;
|
|
||||||
let committee_member = config.committee_role;
|
|
||||||
let committees = match get_committees(db).await {
|
|
||||||
None => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Some(x) => x,
|
|
||||||
};
|
|
||||||
let categories = config.committee_category.clone();
|
|
||||||
|
|
||||||
// information about the server
|
|
||||||
let mut roles_db = HashMap::new();
|
|
||||||
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 going to be getting
|
|
||||||
let mut users_roles = HashMap::new();
|
|
||||||
|
|
||||||
let mut re_order = false;
|
|
||||||
// we need to create roles and channels if they don't already exist
|
|
||||||
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 {
|
|
||||||
// if member.user.id != 136522490632601600 {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
let roles_current = member.roles(ctx).unwrap_or_default();
|
|
||||||
|
|
||||||
let roles_required = match users_roles.get(&member.user.id) {
|
|
||||||
None => {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
Some(x) => x.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let on_committee = !roles_required.is_empty();
|
|
||||||
|
|
||||||
let mut roles_rem = vec![];
|
|
||||||
let mut roles_add = vec![];
|
|
||||||
// get a list of all the roles to remove from someone
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if role.id == committee_member && on_committee {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
roles_rem.push(role.id.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_committee_role = roles_current_id.contains(&committee_member);
|
|
||||||
|
|
||||||
if on_committee && !has_committee_role {
|
|
||||||
// if there are committee roles then give the general purpose role
|
|
||||||
roles_add.push(committee_member);
|
|
||||||
}
|
|
||||||
if !on_committee && has_committee_role {
|
|
||||||
roles_rem.push(committee_member);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !roles_required.is_empty() {
|
|
||||||
if let Some(x) = roles_db.get_mut(&0) {
|
|
||||||
x.count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
pub id_role: RoleId,
|
|
||||||
pub id_channel: ChannelId,
|
|
||||||
pub name_role: String,
|
|
||||||
pub name_channel: String,
|
|
||||||
pub count: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for CommitteeRoles {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<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>) -> Option<Vec<Committees>> {
|
|
||||||
match sqlx::query_as::<_, Committees>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM committees
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(x) => Some(x),
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,269 +0,0 @@
|
||||||
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},
|
|
||||||
wolves::{add_users_wolves, WolvesResultUserMin},
|
|
||||||
},
|
|
||||||
Config,
|
|
||||||
};
|
|
||||||
use serenity::{client::Context, 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 = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
// 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 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(cs_id) = server_name_tmp {
|
|
||||||
if &cs_id != wolves_id {
|
|
||||||
set_server_member(&db, server, cs_id).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, 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 = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
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:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
810
src/lib.rs
810
src/lib.rs
|
@ -1,16 +1,27 @@
|
||||||
pub mod common;
|
|
||||||
|
|
||||||
use chrono::{Datelike, SecondsFormat, Utc};
|
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use rand::{distr::Alphanumeric, rng, Rng};
|
use serde::{Deserialize, Serialize};
|
||||||
use serenity::{
|
use serenity::{
|
||||||
model::id::{ChannelId, GuildId, RoleId},
|
model::{
|
||||||
|
guild,
|
||||||
|
id::{ChannelId, GuildId, RoleId},
|
||||||
|
},
|
||||||
prelude::TypeMapKey,
|
prelude::TypeMapKey,
|
||||||
};
|
};
|
||||||
use std::{env, sync::Arc};
|
|
||||||
|
use crate::set_roles::get_server_member_bulk;
|
||||||
|
use chrono::{Datelike, SecondsFormat, Utc};
|
||||||
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::model::id::UserId;
|
||||||
|
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
||||||
|
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,
|
||||||
|
@ -19,7 +30,6 @@ 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,
|
||||||
|
@ -28,21 +38,16 @@ 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: Vec<ChannelId>,
|
|
||||||
|
|
||||||
// items pertaining to CompSoc only
|
|
||||||
pub compsoc_server: GuildId,
|
|
||||||
}
|
}
|
||||||
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();
|
||||||
|
|
||||||
|
@ -50,7 +55,6 @@ 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(),
|
||||||
|
@ -59,11 +63,6 @@ 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: vec![],
|
|
||||||
compsoc_server: GuildId::new(1),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(x) = env::var("DATABASE_HOME") {
|
if let Ok(x) = env::var("DATABASE_HOME") {
|
||||||
|
@ -79,9 +78,6 @@ 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();
|
||||||
|
@ -93,40 +89,269 @@ 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_BASE") {
|
if let Ok(x) = env::var("WOLVES_URL") {
|
||||||
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_ROLE") {
|
|
||||||
if let Ok(x) = x.trim().parse::<u64>() {
|
|
||||||
config.committee_role = RoleId::new(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Ok(x) = env::var("COMMITTEE_CATEGORY") {
|
|
||||||
for part in x.split(',') {
|
|
||||||
if let Ok(x) = part.trim().parse::<u64>() {
|
|
||||||
config.committee_category.push(ChannelId::new(x));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(x) = env::var("COMPSOC_DISCORD") {
|
|
||||||
if let Ok(x) = x.trim().parse::<u64>() {
|
|
||||||
config.compsoc_server = GuildId::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: RoleId,
|
||||||
|
pub member_past: i64,
|
||||||
|
pub member_current: i64,
|
||||||
|
pub bot_channel_id: ChannelId,
|
||||||
|
// these can be removed in teh future with an API update
|
||||||
|
pub server_name: String,
|
||||||
|
pub wolves_link: String,
|
||||||
|
}
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for Servers {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
let role_past = match row.try_get("role_past") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
if tmp == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(RoleId::from(tmp as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let role_current = match row.try_get("role_current") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
RoleId::from(tmp as u64)
|
||||||
|
}
|
||||||
|
_ => RoleId::from(0u64),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?;
|
||||||
|
let bot_channel_id = ChannelId::from(bot_channel_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
wolves_api: row.try_get("wolves_api")?,
|
||||||
|
role_past,
|
||||||
|
role_current,
|
||||||
|
member_past: row.try_get("member_past")?,
|
||||||
|
member_current: row.try_get("member_current")?,
|
||||||
|
bot_channel_id,
|
||||||
|
server_name: row.try_get("server_name")?,
|
||||||
|
wolves_link: row.try_get("wolves_link")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Minecraft {
|
||||||
|
pub discord: GuildId,
|
||||||
|
pub minecraft: String,
|
||||||
|
}
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for Minecraft {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server_discord")?;
|
||||||
|
let discord = GuildId::from(server_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
discord,
|
||||||
|
minecraft: row.try_get("server_minecraft")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
|
||||||
|
let database = format!("{}/{}", &config.home, &config.database);
|
||||||
|
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.connect_with(
|
||||||
|
SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?
|
||||||
|
.foreign_keys(true)
|
||||||
|
.create_if_missing(true),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// migrations are amazing!
|
||||||
|
sqlx::migrate!("./db/migrations").run(&pool).await?;
|
||||||
|
|
||||||
|
Ok(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_config(db: &Pool<Sqlite>, server: &GuildId) -> Option<Servers> {
|
||||||
|
sqlx::query_as::<_, Servers>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM servers
|
||||||
|
WHERE server = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId, member: &guild::Member) -> Result<ServerMembersWolves, Error> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE server = ? AND discord = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.bind(*member.user.id.as_u64() as i64)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> {
|
||||||
|
sqlx::query_as::<_, Servers>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM servers
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_now_iso(short: bool) -> String {
|
pub fn get_now_iso(short: bool) -> String {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
if short {
|
if short {
|
||||||
|
@ -137,5 +362,478 @@ pub fn get_now_iso(short: bool) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_string(len: usize) -> String {
|
pub fn random_string(len: usize) -> String {
|
||||||
rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect()
|
thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod set_roles {
|
||||||
|
use super::*;
|
||||||
|
pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &[UserId]) {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let Servers {
|
||||||
|
server,
|
||||||
|
role_past,
|
||||||
|
role_current,
|
||||||
|
..
|
||||||
|
} = server;
|
||||||
|
|
||||||
|
let mut roles_set = [0, 0, 0];
|
||||||
|
let mut members = vec![];
|
||||||
|
|
||||||
|
for member in get_server_member_bulk(&db, server).await {
|
||||||
|
if let Some(x) = member.discord {
|
||||||
|
members.push(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut members_all = members.len();
|
||||||
|
|
||||||
|
if let Ok(x) = server.members(ctx, None, None).await {
|
||||||
|
for mut member in x {
|
||||||
|
// members_changed acts as an override to only deal with teh users in it
|
||||||
|
if !members_changed.is_empty() && !members_changed.contains(&member.user.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if members.contains(&member.user.id) {
|
||||||
|
let mut roles = vec![];
|
||||||
|
|
||||||
|
if let Some(role) = &role_past {
|
||||||
|
if !member.roles.contains(role) {
|
||||||
|
roles_set[0] += 1;
|
||||||
|
roles.push(role.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !member.roles.contains(role_current) {
|
||||||
|
roles_set[1] += 1;
|
||||||
|
roles.push(role_current.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = member.add_roles(ctx, &roles).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// old and never
|
||||||
|
|
||||||
|
if let Some(role) = &role_past {
|
||||||
|
if member.roles.contains(role) {
|
||||||
|
members_all += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if member.roles.contains(role_current) {
|
||||||
|
roles_set[2] += 1;
|
||||||
|
// if theya re not a current member and have the role then remove it
|
||||||
|
if let Err(e) = member.remove_role(ctx, role_current).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for role in remove_roles.iter().flatten() {
|
||||||
|
if let Err(e) = member.remove_role(ctx, role).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_server_numbers(&db, server, members_all as i64, members.len() as i64).await;
|
||||||
|
|
||||||
|
// small bit of logging to note changes over time
|
||||||
|
println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE (
|
||||||
|
server = ?
|
||||||
|
AND discord IS NOT NULL
|
||||||
|
AND expiry > ?
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.bind(get_now_iso(true))
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_numbers(db: &Pool<Sqlite>, server: &GuildId, past: i64, current: i64) {
|
||||||
|
match sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
UPDATE servers
|
||||||
|
SET member_past = ?, member_current = ?
|
||||||
|
WHERE server = ?
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(past)
|
||||||
|
.bind(current)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into {}", server.as_u64());
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod get_data {
|
||||||
|
use super::*;
|
||||||
|
use crate::set_roles::update_server;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[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()
|
||||||
}
|
}
|
||||||
|
|
247
src/main.rs
247
src/main.rs
|
@ -1,66 +1,34 @@
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
|
||||||
use crate::commands::role_adder::tools::on_role_change;
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
all::{Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, Interaction},
|
|
||||||
async_trait,
|
async_trait,
|
||||||
client::{Context, EventHandler},
|
client::{Context, EventHandler},
|
||||||
gateway::{ActivityData, ChunkGuildFilter},
|
|
||||||
model::{
|
model::{
|
||||||
event::GuildMemberUpdateEvent,
|
application::{command::Command, interaction::Interaction},
|
||||||
gateway::{GatewayIntents, Ready},
|
gateway::{GatewayIntents, Ready},
|
||||||
guild::Member,
|
guild,
|
||||||
id::GuildId,
|
prelude::Activity,
|
||||||
user::OnlineStatus,
|
user::OnlineStatus,
|
||||||
},
|
},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{
|
|
||||||
common::{
|
|
||||||
database::{db_init, get_server_config, get_server_member, DataBase},
|
|
||||||
set_roles::committee::update_committees,
|
|
||||||
wolves::committees::Committees,
|
|
||||||
},
|
|
||||||
get_config, Config,
|
|
||||||
};
|
|
||||||
use 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 {
|
||||||
// this caches members of all servers teh bot is in
|
async fn guild_member_addition(&self, ctx: Context, mut new_member: guild::Member) {
|
||||||
async fn cache_ready(&self, ctx: Context, guilds: Vec<GuildId>) {
|
let db_lock = {
|
||||||
for guild in guilds {
|
|
||||||
ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None);
|
|
||||||
}
|
|
||||||
println!("Cache built successfully!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// handles previously linked accounts joining the server
|
|
||||||
async fn guild_member_addition(&self, ctx: Context, new_member: Member) {
|
|
||||||
let db = {
|
|
||||||
let 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 config_lock = {
|
let db = db_lock.read().await;
|
||||||
let data_read = ctx.data.read().await;
|
let config = match get_server_config(&db, &new_member.guild_id).await {
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config_global = config_lock.read().await;
|
|
||||||
|
|
||||||
// committee server takes priority
|
|
||||||
let committee_server = config_global.committee_server;
|
|
||||||
if new_member.guild_id.get() == committee_server.get() {
|
|
||||||
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,
|
||||||
};
|
};
|
||||||
|
@ -68,214 +36,92 @@ 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_server.role_past {
|
if let Some(role) = &config.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 !new_member.roles.contains(&config_server.role_current) {
|
if !new_member.roles.contains(&config.role_current) {
|
||||||
roles.push(config_server.role_current.to_owned());
|
roles.push(config.role_current.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 {
|
} else {
|
||||||
let tmp = get_committee(&db, config_server.wolves_id).await;
|
|
||||||
if !tmp.is_empty() {
|
|
||||||
let committee = &tmp[0];
|
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
r#"
|
r#"
|
||||||
Welcome {} to the {} server!
|
Welcome {} to the {} server!
|
||||||
Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/wolves link email_here`` with the email associated with your wolves account, to get full access.
|
Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/link_wolves`` to get full access.
|
||||||
"#,
|
"#,
|
||||||
new_member.display_name(),
|
new_member.display_name(),
|
||||||
committee.name_full,
|
&config.server_name,
|
||||||
committee.link,
|
&config.wolves_link,
|
||||||
&config_server.server,
|
&config.server,
|
||||||
&config_server.bot_channel_id
|
&config.bot_channel_id
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(err) = new_member.user.direct_message(&ctx, CreateMessage::new().content(&msg)).await {
|
if let Err(err) = new_member.user.direct_message(&ctx, |m| m.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 = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// check if the role changed is part of the ones for this server
|
|
||||||
if let Some(x) = new_data {
|
|
||||||
on_role_change(&db, &ctx, x).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(ActivityData::playing("with humanity's fate")), OnlineStatus::Online);
|
ctx.set_presence(Some(Activity::playing("with humanity's fate")), OnlineStatus::Online).await;
|
||||||
|
|
||||||
let config_lock = {
|
match Command::set_global_application_commands(&ctx.http, |commands| {
|
||||||
let data_read = ctx.data.read().await;
|
commands
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
.create_application_command(|command| commands::add_server::register(command))
|
||||||
};
|
.create_application_command(|command| commands::link_email::link::register(command))
|
||||||
let config = config_lock.read().await;
|
.create_application_command(|command| commands::link_email::verify::register(command))
|
||||||
|
.create_application_command(|command| commands::minecraft::server::add::register(command))
|
||||||
match Command::set_global_commands(
|
.create_application_command(|command| commands::minecraft::server::list::register(command))
|
||||||
&ctx.http,
|
.create_application_command(|command| commands::minecraft::server::delete::register(command))
|
||||||
vec![
|
.create_application_command(|command| commands::minecraft::user::add::register(command))
|
||||||
commands::wolves::register(),
|
// for committee server, temp
|
||||||
commands::committee::register(),
|
.create_application_command(|command| commands::committee::link::register(command))
|
||||||
commands::minecraft::server::add::register(),
|
.create_application_command(|command| commands::committee::verify::register(command))
|
||||||
commands::minecraft::server::list::register(),
|
})
|
||||||
commands::minecraft::server::delete::register(),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{e:?}")
|
println!("{:?}", e)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inter-Committee server
|
|
||||||
match config.committee_server.set_commands(&ctx.http, vec![commands::count::committee::register()]).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
println!("{e:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompSoc Server
|
|
||||||
match config
|
|
||||||
.compsoc_server
|
|
||||||
.set_commands(
|
|
||||||
&ctx.http,
|
|
||||||
vec![
|
|
||||||
// commands just for the CompSoc server
|
|
||||||
commands::count::servers::register(),
|
|
||||||
commands::server_icon::user::register(),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
println!("{e:?}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
if let Interaction::Command(command) = interaction {
|
if let Interaction::ApplicationCommand(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
|
||||||
"wolves" => match command.data.options.first() {
|
"link_wolves" => commands::link_email::link::run(&command, &ctx).await,
|
||||||
None => "Invalid Command".to_string(),
|
"verify" => commands::link_email::verify::run(&command, &ctx).await,
|
||||||
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,
|
||||||
"docs" => commands::wolves::link_docs::users::run(&command, &ctx).await,
|
|
||||||
// "link" => commands::count::servers::run(&command, &ctx).await,
|
|
||||||
&_ => format!("not implemented :( wolves {}", x.name.as_str()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
"icon" => match &x.value {
|
|
||||||
CommandDataOptionValue::SubCommandGroup(y) => match y.first() {
|
|
||||||
None => "error".to_string(),
|
|
||||||
Some(z) => match z.name.as_str() {
|
|
||||||
"change" => commands::server_icon::admin::change::run(&command, &ctx).await,
|
|
||||||
&_ => format!("not implemented :( count {}", x.name.as_str()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
format!("not implemented :( committee {}", x.name.as_str())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// TODO: move teh minecraft commands in here as a subgroup
|
|
||||||
// "link" => commands::count::servers::run(&command, &ctx).await,
|
|
||||||
&_ => format!("not implemented :( committee {}", x.name.as_str()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"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,
|
||||||
// sub command
|
// for teh committee server, temporary
|
||||||
"count" => match command.data.options.first() {
|
"link_committee" => commands::committee::link::run(&command, &ctx).await,
|
||||||
None => "Invalid Command".to_string(),
|
"verify_committee" => commands::committee::verify::run(&command, &ctx).await,
|
||||||
Some(x) => match x.name.as_str() {
|
_ => "not implemented :(".to_string(),
|
||||||
"committee" => commands::count::committee::run(&command, &ctx).await,
|
|
||||||
"servers" => commands::count::servers::run(&command, &ctx).await,
|
|
||||||
&_ => format!("not implemented :( count {}", x.name.as_str()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"icon" => match command.data.options.first() {
|
|
||||||
None => "Invalid Command".to_string(),
|
|
||||||
Some(x) => match x.name.as_str() {
|
|
||||||
"current" => {
|
|
||||||
let result = match &x.value {
|
|
||||||
CommandDataOptionValue::SubCommandGroup(y) => match y.first() {
|
|
||||||
None => "error".to_string(),
|
|
||||||
Some(z) => match z.name.as_str() {
|
|
||||||
"icon" => commands::server_icon::user::current::icon::run(&command, &ctx).await,
|
|
||||||
"festival" => commands::server_icon::user::current::festival::run(&command, &ctx).await,
|
|
||||||
&_ => format!("not implemented :( count {}", x.name.as_str()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&_ => format!("not implemented :( {}", command.data.name.as_str()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
result
|
if let Err(why) = command.edit_original_interaction_response(&ctx.http, |response| response.content(content)).await {
|
||||||
}
|
println!("Cannot respond to slash command: {}", why);
|
||||||
"stats" => commands::server_icon::user::stats::run(&command, &ctx).await,
|
|
||||||
&_ => format!("not implemented :( count {}", x.name.as_str()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_ => format!("not implemented :( {}", command.data.name.as_str()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await {
|
|
||||||
println!("Cannot respond to slash command: {why}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
@ -291,8 +137,7 @@ async fn main() {
|
||||||
let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
|
let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
|
||||||
// Build our client.
|
// Build our client.
|
||||||
let mut client = Client::builder(&config.discord_token, intents)
|
let mut client = Client::builder(&config.discord_token, intents)
|
||||||
.event_handler(Handler)
|
.event_handler(Handler {})
|
||||||
.cache_settings(serenity::cache::Settings::default())
|
|
||||||
.await
|
.await
|
||||||
.expect("Error creating client");
|
.expect("Error creating client");
|
||||||
|
|
||||||
|
@ -300,7 +145,7 @@ async fn main() {
|
||||||
let mut data = client.data.write().await;
|
let mut data = client.data.write().await;
|
||||||
|
|
||||||
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
data.insert::<Config>(Arc::new(RwLock::new(config)));
|
||||||
data.insert::<DataBase>(Arc::new(db));
|
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, start a single shard, and start listening to events.
|
// Finally, start a single shard, and start listening to events.
|
||||||
|
@ -308,6 +153,6 @@ async fn main() {
|
||||||
// Shards will automatically attempt to reconnect, and will perform
|
// Shards will automatically attempt to reconnect, and will perform
|
||||||
// exponential backoff until it reconnects.
|
// exponential backoff until it reconnects.
|
||||||
if let Err(why) = client.start().await {
|
if let Err(why) = client.start().await {
|
||||||
println!("Client error: {why:?}");
|
println!("Client error: {:?}", why);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue