forked from Skynet/discord-bot
Compare commits
53 commits
#6-incrime
...
main
Author | SHA1 | Date | |
---|---|---|---|
9452c0ac2e | |||
8ba92cc47e | |||
50d2923425 | |||
5c2502f726 | |||
bda3fbe2ad | |||
439d49db43 | |||
7e40b862d3 | |||
905aaa9620 | |||
c447577eee | |||
7ac8b90f27 | |||
48b52f3c09 | |||
394d6b4545 | |||
bee41c192f | |||
1d14499400 | |||
d8b232b546 | |||
7c2d392e35 | |||
9654963198 | |||
0541a70714 | |||
15720a1f13 | |||
86bb566e5e | |||
c90186295c | |||
acb6432129 | |||
55b2e534d4 | |||
9481358068 | |||
33cebe7782 | |||
c2a6407ef0 | |||
2970549eb0 | |||
d549627714 | |||
87e836619f | |||
0f774258a1 | |||
c446c10f2d | |||
9c284f2a5c | |||
d58d837940 | |||
ed4c46e81d | |||
982b9defd4 | |||
6cbbab80bd | |||
d0b63190b3 | |||
3d925fcfff | |||
cf2c7683d2 | |||
7e6d892b67 | |||
bbd55202bd | |||
bd74cdd09b | |||
2c28f3edcc | |||
f417b9993a | |||
0d0a50c84b | |||
4a1b1cc7f9 | |||
d5877e99e6 | |||
2761098c8d | |||
a9f55da04d | |||
480fc9b1a0 | |||
4bd23e7638 | |||
9dafba03b5 | |||
c6eaa8ad9a |
22 changed files with 2156 additions and 1035 deletions
55
.forgejo/workflows/push.yaml
Normal file
55
.forgejo/workflows/push.yaml
Normal file
|
@ -0,0 +1,55 @@
|
|||
name: On_Push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- flake.*
|
||||
- src/**/*
|
||||
- Cargo.*
|
||||
- .forgejo/**/*
|
||||
- rust-toolchain.toml
|
||||
|
||||
jobs:
|
||||
# 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
|
||||
- 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
|
||||
- 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
|
||||
- name: "Build it locally"
|
||||
run: nix build --verbose
|
||||
|
||||
# deploy it upstream
|
||||
deploy:
|
||||
# runs on teh default docker container
|
||||
runs-on: docker
|
||||
needs: [ build ]
|
||||
steps:
|
||||
- name: "Deploy to Skynet"
|
||||
uses: https://forgejo.skynet.ie/Skynet/actions-deploy-to-skynet@v2
|
||||
with:
|
||||
input: 'skynet_discord_bot'
|
||||
token: ${{ secrets.API_TOKEN_FORGEJO }}
|
|
@ -1,86 +0,0 @@
|
|||
# copied a good chunk from my bfom config
|
||||
image: rust:latest
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- build
|
||||
- deploy
|
||||
|
||||
cache:
|
||||
key: "$CI_JOB_NAME"
|
||||
paths:
|
||||
- target/
|
||||
|
||||
# Set any required environment variables here
|
||||
variables:
|
||||
RUST_BACKTRACE: FULL
|
||||
|
||||
|
||||
# clippy and fmt are magic
|
||||
# runs on all commits/branches
|
||||
lint-clippy:
|
||||
stage: lint
|
||||
script:
|
||||
- rustup component add clippy
|
||||
- rustc --version
|
||||
- cargo version
|
||||
- cargo clippy
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- changes:
|
||||
- src/**/*
|
||||
- cargo.*
|
||||
when: always
|
||||
|
||||
lint-fmt:
|
||||
stage: lint
|
||||
script:
|
||||
- rustup component add rustfmt
|
||||
- rustc --version
|
||||
- cargo version
|
||||
- cargo fmt -- --check
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- changes:
|
||||
- src/**/*
|
||||
- cargo.*
|
||||
when: always
|
||||
|
||||
|
||||
# has to actually compile
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- rustc --version
|
||||
- cargo version
|
||||
- cargo build --verbose
|
||||
- RUST_BACKTRACE=1 cargo test --verbose
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- changes:
|
||||
- src/**/*
|
||||
- cargo.*
|
||||
when: on_success
|
||||
|
||||
|
||||
# from https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html
|
||||
# so simple to deploy now
|
||||
nixos:
|
||||
stage: deploy
|
||||
variables:
|
||||
PACKAGE_NAME: "skynet_discord_bot"
|
||||
UPDATE_FLAKE: "yes"
|
||||
trigger: compsoc1/skynet/nixos
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
when: on_success
|
||||
|
||||
|
||||
|
||||
|
||||
|
1430
Cargo.lock
generated
1430
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,11 @@ name = "update_data"
|
|||
[[bin]]
|
||||
name = "update_users"
|
||||
|
||||
[[bin]]
|
||||
name = "update_minecraft"
|
||||
|
||||
[dependencies]
|
||||
# discord library
|
||||
serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
|
@ -21,7 +25,7 @@ surf = "2.3.2"
|
|||
dotenvy = "0.15.7"
|
||||
|
||||
# For sqlite
|
||||
sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite" ] }
|
||||
sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] }
|
||||
|
||||
# create random strings
|
||||
rand = "0.8.5"
|
||||
|
|
70
README.md
Normal file
70
README.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Skynet Discord Bot
|
||||
This bots core purpose is to give members roles based on their status on <ulwolves.ie>.
|
||||
It uses an api key provided by wolves to get member lists.
|
||||
|
||||
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.
|
||||
|
||||
## Commands - Admin
|
||||
|
||||
You need admin access to run any of the commands in this section.
|
||||
Either the server owner or a suer with the ``Administrator`` permission
|
||||
|
||||
### Getting the Skynet Discord bot
|
||||
1. Email ``keith@assurememberships.com`` from committee email and say ye want an api key for ``193.1.99.74``
|
||||
2. Create a role for current members (maybe call it ``current-member`` ?)
|
||||
3. (Optional) create a role for all past and current members (ye can use the existing ``member`` role for this, )
|
||||
4. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot
|
||||
5. Make sure the bot role ``@skynet`` is above these two roles (so it can manage them)
|
||||
6. Make sure that you have a role that gives ye administrator powers
|
||||
7. Use the command ``/add`` and insert the api key, role current and role all (desktop recommended)
|
||||
|
||||
The reason for both roles is ye have one for active members while the second is for all current and past members.
|
||||
|
||||
### Minecraft
|
||||
The bot is able to manage the whitelist of a Minecraft server managed by the Computer Society.
|
||||
Talk to us to get a server.
|
||||
|
||||
#### Add
|
||||
This links a minecraft server with your club/society.
|
||||
|
||||
``/minecraft_add SERVER_ID``
|
||||
|
||||
|
||||
#### List
|
||||
List the servers linked to your club/society.
|
||||
It is possible to have more than one minecraft server
|
||||
|
||||
``/minecraft_list``
|
||||
|
||||
#### Delete
|
||||
This unlinks a minecraft server from your club/society.
|
||||
|
||||
``/minecraft_delete SERVER_ID``
|
||||
|
||||
## Commands - User
|
||||
|
||||
### Setup
|
||||
* Start the process using ``/link_wolves WOLVES_EMAIL``
|
||||
* The email that is in the Contact Email here: <https://ulwolves.ie/memberships/profile>
|
||||
* An email will be sent to them that they need to verify using ``/verify CODE``
|
||||
* This will only have to be done once.
|
||||
|
||||
* If the user is an active member on wolves
|
||||
* If they are in any servers with teh Skynet Bot
|
||||
* They will get relevant roles.
|
||||
* If they Join a server with teh bot enabled.
|
||||
* They will be granted the roles automatically
|
||||
* If the user is **not** an active member on wolves.
|
||||
* If they have no Roles
|
||||
* No change
|
||||
* If they have Past Member Role
|
||||
* No change
|
||||
* If they have both Roles
|
||||
* The current-member role will be removed from them
|
||||
* Past Member role will remain unchanged
|
||||
|
||||
### Minecraft
|
||||
Users can link their Minecraft username to grant them access to any servers where teh whitelist is managed by teh bot.
|
||||
|
||||
``/link_minecraft MINECRAFT_USERNAME``
|
38
db/migrations/1_setup.sql
Normal file
38
db/migrations/1_setup.sql
Normal file
|
@ -0,0 +1,38 @@
|
|||
-- setup initial tables
|
||||
|
||||
-- this handles the users "floating" account
|
||||
CREATE TABLE IF NOT EXISTS wolves (
|
||||
id_wolves integer PRIMARY KEY,
|
||||
email text not null,
|
||||
discord integer,
|
||||
minecraft text
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS index_discord ON wolves (discord);
|
||||
|
||||
-- used to verify the users email address
|
||||
CREATE TABLE IF NOT EXISTS wolves_verify (
|
||||
discord integer PRIMARY KEY,
|
||||
email text not null,
|
||||
auth_code text not null,
|
||||
date_expiry text not null
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS index_date_expiry ON wolves_verify (date_expiry);
|
||||
|
||||
-- information on teh server the bot is registered on
|
||||
CREATE TABLE IF NOT EXISTS servers (
|
||||
server integer PRIMARY KEY,
|
||||
wolves_api text not null,
|
||||
role_past integer,
|
||||
role_current integer,
|
||||
member_past integer DEFAULT 0,
|
||||
member_current integer DEFAULT 0
|
||||
);
|
||||
|
||||
-- keep track of the members on the server
|
||||
CREATE TABLE IF NOT EXISTS server_members (
|
||||
server integer not null,
|
||||
id_wolves integer not null,
|
||||
expiry text not null,
|
||||
PRIMARY KEY(server,id_wolves),
|
||||
FOREIGN KEY (id_wolves) REFERENCES wolves (id_wolves)
|
||||
);
|
3
db/migrations/2_minecraft-server.sql
Normal file
3
db/migrations/2_minecraft-server.sql
Normal file
|
@ -0,0 +1,3 @@
|
|||
-- add teh option to associate each discord server with a minecraft one managed by skynet
|
||||
ALTER TABLE servers
|
||||
ADD server_minecraft text;
|
18
db/migrations/3_minecraft-server-migrate.sql
Normal file
18
db/migrations/3_minecraft-server-migrate.sql
Normal file
|
@ -0,0 +1,18 @@
|
|||
-- Create the new table
|
||||
CREATE TABLE IF NOT EXISTS minecraft
|
||||
(
|
||||
server_discord integer not null,
|
||||
server_minecraft text not null,
|
||||
PRIMARY KEY (server_discord, server_minecraft),
|
||||
FOREIGN KEY (server_discord) REFERENCES servers (server)
|
||||
);
|
||||
|
||||
-- Import in the data
|
||||
INSERT INTO minecraft (server_discord, server_minecraft)
|
||||
SELECT server, server_minecraft
|
||||
FROM servers
|
||||
where server_minecraft is not null;
|
||||
|
||||
-- delete the col in teh server table
|
||||
ALTER TABLE servers
|
||||
DROP COLUMN server_minecraft;
|
7
db/migrations/4_committee-mk-i.sql
Normal file
7
db/migrations/4_committee-mk-i.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
-- temp table to allow folks to verify by committee email.
|
||||
CREATE TABLE IF NOT EXISTS committee (
|
||||
discord integer PRIMARY KEY,
|
||||
email text not null,
|
||||
auth_code text not null,
|
||||
committee integer DEFAULT 0
|
||||
);
|
8
example.env
Normal file
8
example.env
Normal file
|
@ -0,0 +1,8 @@
|
|||
HOME="."
|
||||
DATABASE="database.db"
|
||||
DISCORD_TOKEN=""
|
||||
DISCORD_TOKEN_MINECRAFT=""
|
||||
EMAIL_SMTP=""
|
||||
EMAIL_USER=""
|
||||
EMAIL_PASS=""
|
||||
WOLVES_URL=""
|
26
flake.lock
26
flake.lock
|
@ -5,11 +5,11 @@
|
|||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692351612,
|
||||
"narHash": "sha256-KTGonidcdaLadRnv9KFgwSMh1ZbXoR/OBmPjeNMhFwU=",
|
||||
"lastModified": 1721727458,
|
||||
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "78789c30d64dea2396c9da516bbcc8db3a475207",
|
||||
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1693060755,
|
||||
"narHash": "sha256-KNsbfqewEziFJEpPR0qvVz4rx0x6QXxw1CcunRhlFdk=",
|
||||
"lastModified": 1723151389,
|
||||
"narHash": "sha256-9AVY0ReCmSGXHrlx78+1RrqcDgVSRhHUKDVV1LLBy28=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c66ccfa00c643751da2fd9290e096ceaa30493fc",
|
||||
"rev": "13fe00cb6c75461901f072ae62b5805baef9f8b2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -34,16 +34,16 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1693087214,
|
||||
"narHash": "sha256-Kn1SSqRfPpqcI1MDy82JXrPT1WI8c03TA2F0xu6kS+4=",
|
||||
"lastModified": 1722995383,
|
||||
"narHash": "sha256-UzuXo7ZM8ZK0SkWFhHocKkLSGQPHS4JxaE1jvVR4fUo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f155f0cf4ea43c4e3c8918d2d327d44777b6cad4",
|
||||
"rev": "957d95fc8b9bf1eb60d43f8d2eba352b71bbf2be",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-23.05",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
|
@ -74,11 +74,11 @@
|
|||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692799911,
|
||||
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
376
flake.nix
376
flake.nix
|
@ -2,203 +2,213 @@
|
|||
description = "Skynet Discord Bot";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-23.05";
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, utils, naersk }: utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages."${system}";
|
||||
naersk-lib = naersk.lib."${system}";
|
||||
package_name = "skynet_discord_bot";
|
||||
desc = "Skynet Discord Bot";
|
||||
in rec {
|
||||
|
||||
# `nix build`
|
||||
packages."${package_name}" = naersk-lib.buildPackage {
|
||||
pname = "${package_name}";
|
||||
root = ./.;
|
||||
|
||||
buildInputs = [
|
||||
pkgs.openssl
|
||||
pkgs.pkg-config
|
||||
];
|
||||
};
|
||||
|
||||
defaultPackage = packages."${package_name}";
|
||||
|
||||
# `nix run`
|
||||
apps."${package_name}" = utils.lib.mkApp {
|
||||
drv = packages."${package_name}";
|
||||
};
|
||||
|
||||
defaultApp = apps."${package_name}";
|
||||
|
||||
# `nix develop`
|
||||
devShell = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [ rustc cargo pkg-config openssl];
|
||||
};
|
||||
|
||||
nixosModule = { lib, pkgs, config, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services."${package_name}";
|
||||
# secret options are in the env file(s) loaded separately
|
||||
environment_config = {
|
||||
LDAP_API = cfg.ldap;
|
||||
SKYNET_SERVER = cfg.discord.server;
|
||||
HOME = cfg.home;
|
||||
DATABASE = "database.db";
|
||||
};
|
||||
|
||||
service_name = script: lib.strings.sanitizeDerivationName("${cfg.user}@${script}");
|
||||
nixConfig = {
|
||||
extra-substituters = "https://nix-cache.skynet.ie/skynet-cache";
|
||||
extra-trusted-public-keys = "skynet-cache:zMFLzcRZPhUpjXUy8SF8Cf7KGAZwo98SKrzeXvdWABo=";
|
||||
};
|
||||
|
||||
# oneshot scripts to run
|
||||
serviceGenerator = mapAttrs' (script: time: nameValuePair (service_name script) {
|
||||
description = "Service for ${desc} ${script}";
|
||||
wantedBy = [ ];
|
||||
after = [ "network-online.target" ];
|
||||
environment = environment_config;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "${cfg.user}";
|
||||
Group = "${cfg.user}";
|
||||
ExecStart = "${self.defaultPackage."${system}"}/bin/${script}";
|
||||
EnvironmentFile = [
|
||||
"${cfg.env.ldap}"
|
||||
"${cfg.env.discord}"
|
||||
"${cfg.env.mail}"
|
||||
"${cfg.env.wolves}"
|
||||
];
|
||||
};
|
||||
});
|
||||
|
||||
# each timer will run the above service
|
||||
timerGenerator = mapAttrs' (script: time: nameValuePair (service_name script) {
|
||||
description = "Timer for ${desc} ${script}";
|
||||
|
||||
wantedBy = [ "timers.target" ];
|
||||
partOf = [ "${service_name script}.service" ];
|
||||
timerConfig = {
|
||||
OnCalendar = time;
|
||||
Unit = "${service_name script}.service";
|
||||
Persistent = true;
|
||||
};
|
||||
});
|
||||
|
||||
# modify these
|
||||
scripts = {
|
||||
# every 20 min
|
||||
"update_data" = "*:0,20,40";
|
||||
# groups are updated every hour, offset from teh ldap
|
||||
"update_users" = "00:05:00";
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
utils,
|
||||
naersk,
|
||||
}:
|
||||
utils.lib.eachDefaultSystem (
|
||||
system: let
|
||||
pkgs = (import nixpkgs) {inherit system;};
|
||||
naersk' = pkgs.callPackage naersk {};
|
||||
package_name = "skynet_discord_bot";
|
||||
desc = "Skynet Discord Bot";
|
||||
buildInputs = with pkgs; [
|
||||
openssl
|
||||
pkg-config
|
||||
rustfmt
|
||||
];
|
||||
in rec {
|
||||
packages = {
|
||||
# For `nix build` & `nix run`:
|
||||
default = naersk'.buildPackage {
|
||||
pname = "${package_name}";
|
||||
src = ./.;
|
||||
buildInputs = buildInputs;
|
||||
};
|
||||
|
||||
in {
|
||||
options.services."${package_name}" = {
|
||||
enable = mkEnableOption "enable ${package_name}";
|
||||
|
||||
env = {
|
||||
ldap = mkOption rec {
|
||||
type = types.str;
|
||||
description = "ENV file with LDAP_DISCORD_AUTH";
|
||||
};
|
||||
discord = mkOption rec {
|
||||
type = types.str;
|
||||
description = "ENV file with DISCORD_TOKEN";
|
||||
};
|
||||
mail = mkOption rec {
|
||||
type = types.str;
|
||||
description = "ENV file with EMAIL_SMTP, EMAIL_USER, EMAIL_PASS";
|
||||
};
|
||||
wolves = mkOption rec {
|
||||
type = types.str;
|
||||
description = "Mail details, has WOLVES_URL, WOLVES_KEY";
|
||||
};
|
||||
};
|
||||
|
||||
discord = {
|
||||
server = mkOption rec {
|
||||
type = types.str;
|
||||
description = "ID of the server the bot runs on";
|
||||
};
|
||||
};
|
||||
|
||||
ldap = mkOption rec {
|
||||
type = types.str;
|
||||
default = "https://api.account.skynet.ie";
|
||||
description = "Location of the ldap api";
|
||||
};
|
||||
|
||||
user = mkOption rec {
|
||||
type = types.str;
|
||||
default = "${package_name}";
|
||||
description = "The user to run the service";
|
||||
};
|
||||
|
||||
home = mkOption rec {
|
||||
type = types.str;
|
||||
default = "/etc/${cfg.prefix}${package_name}";
|
||||
description = "The home for the user";
|
||||
};
|
||||
|
||||
prefix = mkOption rec {
|
||||
type = types.str;
|
||||
default = "skynet_";
|
||||
example = default;
|
||||
description = "The prefix used to name service/folders";
|
||||
};
|
||||
# Run `nix build .#fmt` to run tests
|
||||
fmt = naersk'.buildPackage {
|
||||
src = ./.;
|
||||
mode = "fmt";
|
||||
buildInputs = buildInputs;
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.groups."${cfg.user}" = { };
|
||||
|
||||
users.users."${cfg.user}" = {
|
||||
createHome = true;
|
||||
isSystemUser = true;
|
||||
home = "${cfg.home}";
|
||||
group = "${cfg.user}";
|
||||
# Run `nix build .#clippy` to lint code
|
||||
clippy = naersk'.buildPackage {
|
||||
src = ./.;
|
||||
mode = "clippy";
|
||||
buildInputs = buildInputs;
|
||||
};
|
||||
};
|
||||
|
||||
defaultPackage = packages.default;
|
||||
|
||||
# `nix run`
|
||||
apps."${package_name}" = utils.lib.mkApp {
|
||||
drv = packages."${package_name}";
|
||||
};
|
||||
|
||||
defaultApp = apps."${package_name}";
|
||||
|
||||
# `nix develop`
|
||||
devShell = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [rustc cargo pkg-config openssl rustfmt];
|
||||
};
|
||||
|
||||
nixosModule = {
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.services."${package_name}";
|
||||
# secret options are in the env file(s) loaded separately
|
||||
environment_config = {
|
||||
HOME = cfg.home;
|
||||
DATABASE = "database.db";
|
||||
};
|
||||
|
||||
systemd.services = {
|
||||
# main service
|
||||
"${package_name}" = {
|
||||
description = desc;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ ];
|
||||
|
||||
service_name = script: lib.strings.sanitizeDerivationName "${cfg.user}@${script}";
|
||||
|
||||
# oneshot scripts to run
|
||||
serviceGenerator = mapAttrs' (script: time:
|
||||
nameValuePair (service_name script) {
|
||||
description = "Service for ${desc} ${script}";
|
||||
wantedBy = [];
|
||||
after = ["network-online.target"];
|
||||
environment = environment_config;
|
||||
|
||||
|
||||
serviceConfig = {
|
||||
User = "${cfg.user}";
|
||||
Group = "${cfg.user}";
|
||||
Restart = "always";
|
||||
ExecStart = "${self.defaultPackage."${system}"}/bin/${package_name}";
|
||||
# can have multiple env files
|
||||
Type = "oneshot";
|
||||
User = "${cfg.user}";
|
||||
Group = "${cfg.user}";
|
||||
ExecStart = "${self.defaultPackage."${system}"}/bin/${script}";
|
||||
EnvironmentFile = [
|
||||
"${cfg.env.ldap}"
|
||||
"${cfg.env.discord}"
|
||||
"${cfg.env.mail}"
|
||||
"${cfg.env.wolves}"
|
||||
];
|
||||
};
|
||||
restartTriggers = [
|
||||
"${cfg.env.ldap}"
|
||||
"${cfg.env.discord}"
|
||||
"${cfg.env.mail}"
|
||||
"${cfg.env.wolves}"
|
||||
];
|
||||
});
|
||||
|
||||
# each timer will run the above service
|
||||
timerGenerator = mapAttrs' (script: time:
|
||||
nameValuePair (service_name script) {
|
||||
description = "Timer for ${desc} ${script}";
|
||||
|
||||
wantedBy = ["timers.target"];
|
||||
partOf = ["${service_name script}.service"];
|
||||
timerConfig = {
|
||||
OnCalendar = time;
|
||||
Unit = "${service_name script}.service";
|
||||
Persistent = true;
|
||||
};
|
||||
});
|
||||
|
||||
# modify these
|
||||
scripts = {
|
||||
# every 20 min
|
||||
"update_data" = "*:0,20,40";
|
||||
# groups are updated every hour, offset from teh ldap
|
||||
"update_users" = "*:05:00";
|
||||
# minecraft stuff is updated at 5am
|
||||
"update_minecraft" = "5:10:00";
|
||||
};
|
||||
in {
|
||||
options.services."${package_name}" = {
|
||||
enable = mkEnableOption "enable ${package_name}";
|
||||
|
||||
env = {
|
||||
discord = mkOption rec {
|
||||
type = types.str;
|
||||
description = "ENV file with DISCORD_TOKEN, DISCORD_TOKEN_MINECRAFT";
|
||||
};
|
||||
mail = mkOption rec {
|
||||
type = types.str;
|
||||
description = "ENV file with EMAIL_SMTP, EMAIL_USER, EMAIL_PASS";
|
||||
};
|
||||
wolves = mkOption rec {
|
||||
type = types.str;
|
||||
description = "Mail details, has WOLVES_URL";
|
||||
};
|
||||
};
|
||||
} // serviceGenerator scripts;
|
||||
|
||||
# timers to run the above services
|
||||
systemd.timers = timerGenerator scripts;
|
||||
|
||||
|
||||
user = mkOption rec {
|
||||
type = types.str;
|
||||
default = "${package_name}";
|
||||
description = "The user to run the service";
|
||||
};
|
||||
|
||||
home = mkOption rec {
|
||||
type = types.str;
|
||||
default = "/etc/${cfg.prefix}${package_name}";
|
||||
description = "The home for the user";
|
||||
};
|
||||
|
||||
prefix = mkOption rec {
|
||||
type = types.str;
|
||||
default = "skynet_";
|
||||
example = default;
|
||||
description = "The prefix used to name service/folders";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.groups."${cfg.user}" = {};
|
||||
|
||||
users.users."${cfg.user}" = {
|
||||
createHome = true;
|
||||
isSystemUser = true;
|
||||
home = "${cfg.home}";
|
||||
group = "${cfg.user}";
|
||||
};
|
||||
|
||||
systemd.services =
|
||||
{
|
||||
# main service
|
||||
"${package_name}" = {
|
||||
description = desc;
|
||||
wantedBy = ["multi-user.target"];
|
||||
after = ["network-online.target"];
|
||||
wants = [];
|
||||
environment = environment_config;
|
||||
|
||||
serviceConfig = {
|
||||
User = "${cfg.user}";
|
||||
Group = "${cfg.user}";
|
||||
Restart = "always";
|
||||
ExecStart = "${self.defaultPackage."${system}"}/bin/${package_name}";
|
||||
# can have multiple env files
|
||||
EnvironmentFile = [
|
||||
"${cfg.env.discord}"
|
||||
"${cfg.env.mail}"
|
||||
"${cfg.env.wolves}"
|
||||
];
|
||||
};
|
||||
restartTriggers = [
|
||||
"${cfg.env.discord}"
|
||||
"${cfg.env.mail}"
|
||||
"${cfg.env.wolves}"
|
||||
];
|
||||
};
|
||||
}
|
||||
// serviceGenerator scripts;
|
||||
|
||||
# timers to run the above services
|
||||
systemd.timers = timerGenerator scripts;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "1.80"
|
25
src/bin/update_minecraft.rs
Normal file
25
src/bin/update_minecraft.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use skynet_discord_bot::{db_init, get_config, get_minecraft_config, update_server, whitelist_wipe};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let config = get_config();
|
||||
let db = match db_init(&config).await {
|
||||
Ok(x) => x,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let servers = get_minecraft_config(&db).await;
|
||||
let mut wiped = HashSet::new();
|
||||
|
||||
for server in &servers {
|
||||
// wipe whitelist first
|
||||
if !wiped.contains(&server.minecraft) {
|
||||
whitelist_wipe(&server.minecraft, &config.discord_token_minecraft).await;
|
||||
// add it to teh done list so its not done again
|
||||
wiped.insert(&server.minecraft);
|
||||
}
|
||||
|
||||
update_server(&server.minecraft, &db, &server.discord, &config).await;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ use serenity::{
|
|||
model::gateway::{GatewayIntents, Ready},
|
||||
Client,
|
||||
};
|
||||
use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles::update_server, Config, DataBase};
|
||||
use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles, Config, DataBase};
|
||||
use std::{process, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
|
@ -59,6 +59,6 @@ async fn bulk_check(ctx: Arc<Context>) {
|
|||
let db = db_lock.read().await;
|
||||
|
||||
for server_config in get_server_config_bulk(&db).await {
|
||||
update_server(&ctx, &server_config, &[], &vec![]).await;
|
||||
set_roles::update_server(&ctx, &server_config, &[], &[]).await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,46 +7,19 @@ use serenity::{
|
|||
},
|
||||
};
|
||||
use skynet_discord_bot::get_data::get_wolves;
|
||||
use skynet_discord_bot::{get_server_config, set_roles::update_server, DataBase, Servers};
|
||||
use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers};
|
||||
use sqlx::{Error, Pool, Sqlite};
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
// check if user has high enough permisssions
|
||||
let mut admin = false;
|
||||
|
||||
let g_id = match command.guild_id {
|
||||
None => return "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 {
|
||||
return "Administrator permission required".to_string();
|
||||
if let Some(msg) = is_admin(command, ctx).await {
|
||||
return msg;
|
||||
}
|
||||
|
||||
let api_key = if let CommandDataOptionValue::String(key) = command
|
||||
.data
|
||||
.options
|
||||
.get(0)
|
||||
.first()
|
||||
.expect("Expected user option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
|
@ -183,7 +156,7 @@ async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Resul
|
|||
if past_remove {
|
||||
roles_remove.push(past_role)
|
||||
}
|
||||
update_server(ctx, server, &roles_remove, &vec![]).await;
|
||||
update_server(ctx, server, &roles_remove, &[]).await;
|
||||
}
|
||||
|
||||
insert
|
||||
|
|
311
src/commands/committee.rs
Normal file
311
src/commands/committee.rs
Normal file
|
@ -0,0 +1,311 @@
|
|||
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 mod link {
|
||||
use super::*;
|
||||
use serenity::model::id::GuildId;
|
||||
use skynet_discord_bot::Committee;
|
||||
|
||||
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 option = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected email option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected email object");
|
||||
|
||||
let email = if let CommandDataOptionValue::String(email) = option {
|
||||
email.trim()
|
||||
} else {
|
||||
return "Please provide a valid committee email.".to_string();
|
||||
};
|
||||
|
||||
// fail early
|
||||
if !email.ends_with("@ulwolves.ie") {
|
||||
return "Please use a @ulwolves.ie address you have access to.".to_string();
|
||||
}
|
||||
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||
};
|
||||
let db = db_lock.read().await;
|
||||
|
||||
let config_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||
};
|
||||
let config = config_lock.read().await;
|
||||
|
||||
if get_server_member_discord(&db, &command.user.id).await.is_some() {
|
||||
return "Already linked".to_string();
|
||||
}
|
||||
|
||||
if get_verify_from_db(&db, &command.user.id).await.is_some() {
|
||||
return "Linking already in process, please check email.".to_string();
|
||||
}
|
||||
|
||||
// generate a auth key
|
||||
let auth = random_string(20);
|
||||
match send_mail(&config, email, &auth, &command.user.name) {
|
||||
Ok(_) => match save_to_db(&db, email, &auth, &command.user.id).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
return format!("Unable to save to db {} {e:?}", email);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return format!("Unable to send mail to {} {e:?}", email);
|
||||
}
|
||||
}
|
||||
|
||||
format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email)
|
||||
}
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command
|
||||
.name("link_committee")
|
||||
.description("Verify you are a committee member")
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("email")
|
||||
.description("UL Wolves Committee Email")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Committee> {
|
||||
sqlx::query_as::<_, Committee>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM committee
|
||||
WHERE discord = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.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).unwrap().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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ use serenity::{
|
|||
};
|
||||
use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, WolvesVerify};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
pub(crate) mod link {
|
||||
pub mod link {
|
||||
use super::*;
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
|
@ -44,7 +44,7 @@ pub(crate) mod link {
|
|||
let option = command
|
||||
.data
|
||||
.options
|
||||
.get(0)
|
||||
.first()
|
||||
.expect("Expected email option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
|
@ -87,12 +87,12 @@ pub(crate) mod link {
|
|||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command
|
||||
.name("link")
|
||||
.name("link_wolves")
|
||||
.description("Set Wolves Email")
|
||||
.create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true))
|
||||
}
|
||||
|
||||
async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Wolves> {
|
||||
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Wolves> {
|
||||
sqlx::query_as::<_, Wolves>(
|
||||
r#"
|
||||
SELECT *
|
||||
|
@ -234,7 +234,7 @@ pub(crate) mod link {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) mod verify {
|
||||
pub mod verify {
|
||||
use super::*;
|
||||
use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db};
|
||||
use serenity::model::user::User;
|
||||
|
@ -248,17 +248,17 @@ pub(crate) mod verify {
|
|||
};
|
||||
let db = db_lock.read().await;
|
||||
|
||||
// check if user has used /link
|
||||
// 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 first".to_string();
|
||||
return "Please use /link_wolves first".to_string();
|
||||
};
|
||||
|
||||
let option = command
|
||||
.data
|
||||
.options
|
||||
.get(0)
|
||||
.first()
|
||||
.expect("Expected code option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
|
@ -286,7 +286,7 @@ pub(crate) mod verify {
|
|||
}
|
||||
Err(e) => {
|
||||
println!("{:?}", e);
|
||||
"Failed to save, please try /link again".to_string()
|
||||
"Failed to save, please try /link_wolves again".to_string()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
334
src/commands/minecraft.rs
Normal file
334
src/commands/minecraft.rs
Normal file
|
@ -0,0 +1,334 @@
|
|||
use serenity::{
|
||||
builder::CreateApplicationCommand,
|
||||
client::Context,
|
||||
model::{
|
||||
application::interaction::application_command::ApplicationCommandInteraction,
|
||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||
},
|
||||
};
|
||||
|
||||
use skynet_discord_bot::DataBase;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
pub(crate) mod user {
|
||||
use super::*;
|
||||
pub(crate) mod add {
|
||||
use super::*;
|
||||
use crate::commands::link_email::link::get_server_member_discord;
|
||||
use serenity::model::id::UserId;
|
||||
use skynet_discord_bot::{whitelist_update, Config, Minecraft, Wolves};
|
||||
use sqlx::Error;
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command.name("link_minecraft").description("Link your minecraft account").create_option(|option| {
|
||||
option
|
||||
.name("minecraft-username")
|
||||
.description("Your Minecraft username")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// user has to have previously linked with wolves
|
||||
if get_server_member_discord(&db, &command.user.id).await.is_none() {
|
||||
return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string();
|
||||
}
|
||||
|
||||
let username = if let CommandDataOptionValue::String(username) = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected username option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected username object")
|
||||
{
|
||||
username.trim()
|
||||
} else {
|
||||
return "Please provide a valid username".to_string();
|
||||
};
|
||||
|
||||
// insert the username into the database
|
||||
match add_minecraft(&db, &command.user.id, username).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
dbg!("{:?}", e);
|
||||
return format!("Failure to minecraft username {:?}", username);
|
||||
}
|
||||
}
|
||||
|
||||
// get a list of servers that the user is a member of
|
||||
if let Ok(servers) = get_servers(&db, &command.user.id).await {
|
||||
for server in servers {
|
||||
whitelist_update(&vec![username.to_string()], &server.minecraft, &config.discord_token_minecraft).await;
|
||||
}
|
||||
}
|
||||
|
||||
"Added/Updated minecraft_user info".to_string()
|
||||
}
|
||||
|
||||
async fn add_minecraft(db: &Pool<Sqlite>, user: &UserId, minecraft: &str) -> Result<Option<Wolves>, Error> {
|
||||
sqlx::query_as::<_, Wolves>(
|
||||
"
|
||||
UPDATE wolves
|
||||
SET minecraft = ?2
|
||||
WHERE discord = ?1;
|
||||
",
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.bind(minecraft)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_servers(db: &Pool<Sqlite>, discord: &UserId) -> Result<Vec<Minecraft>, Error> {
|
||||
sqlx::query_as::<_, Minecraft>(
|
||||
"
|
||||
SELECT minecraft.*
|
||||
FROM minecraft
|
||||
JOIN (
|
||||
SELECT server
|
||||
FROM server_members
|
||||
JOIN wolves USING (id_wolves)
|
||||
WHERE discord = ?1
|
||||
) sub on minecraft.server_discord = sub.server
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod server {
|
||||
use super::*;
|
||||
|
||||
pub(crate) mod add {
|
||||
use serenity::model::id::GuildId;
|
||||
use sqlx::Error;
|
||||
// this is to managfe the server side of commands related to minecraft
|
||||
use super::*;
|
||||
use skynet_discord_bot::{is_admin, update_server, Config, Minecraft};
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command.name("minecraft_add").description("Add a minecraft server").create_option(|option| {
|
||||
option
|
||||
.name("server_id")
|
||||
.description("ID of the Minecraft server hosted by the Computer Society")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
None => return "Not in a server".to_string(),
|
||||
Some(x) => x,
|
||||
};
|
||||
|
||||
let server_minecraft = if let CommandDataOptionValue::String(id) = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected server_id option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected server_id object")
|
||||
{
|
||||
id.to_owned()
|
||||
} else {
|
||||
return String::from("Expected Server ID");
|
||||
};
|
||||
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||
};
|
||||
let db = db_lock.read().await;
|
||||
|
||||
match add_server(&db, &g_id, &server_minecraft).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("{:?}", e);
|
||||
return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
update_server(&server_minecraft, &db, &g_id, &config).await;
|
||||
|
||||
"Added/Updated minecraft_server info".to_string()
|
||||
}
|
||||
|
||||
async fn add_server(db: &Pool<Sqlite>, discord: &GuildId, minecraft: &str) -> Result<Option<Minecraft>, Error> {
|
||||
sqlx::query_as::<_, Minecraft>(
|
||||
"
|
||||
INSERT OR REPLACE INTO minecraft (server_discord, server_minecraft)
|
||||
VALUES (?1, ?2)
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.bind(minecraft)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod list {
|
||||
use serenity::builder::CreateApplicationCommand;
|
||||
use serenity::client::Context;
|
||||
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
||||
use skynet_discord_bot::{get_minecraft_config_server, is_admin, server_information, Config, DataBase};
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command.name("minecraft_list").description("List your minecraft servers")
|
||||
}
|
||||
|
||||
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 {
|
||||
None => return "Not in a server".to_string(),
|
||||
Some(x) => x,
|
||||
};
|
||||
|
||||
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 servers = get_minecraft_config_server(&db, g_id).await;
|
||||
|
||||
if servers.is_empty() {
|
||||
return "No minecraft servers, use /minecraft_add to add one".to_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 mut result = "Server Information:\n".to_string();
|
||||
for server in get_minecraft_config_server(&db, g_id).await {
|
||||
if let Some(x) = server_information(&server.minecraft, &config.discord_token_minecraft).await {
|
||||
result.push_str(&format!(
|
||||
r#"
|
||||
Name: {name}
|
||||
ID: {id}
|
||||
Online: {online}
|
||||
Info: {description}
|
||||
Link: <http://panel.games.skynet.ie/server/{id}>
|
||||
"#,
|
||||
name = &x.attributes.name,
|
||||
online = !x.attributes.is_suspended,
|
||||
description = &x.attributes.description,
|
||||
id = &x.attributes.identifier
|
||||
));
|
||||
}
|
||||
}
|
||||
result.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod delete {
|
||||
use serenity::builder::CreateApplicationCommand;
|
||||
use serenity::client::Context;
|
||||
use serenity::model::application::command::CommandOptionType;
|
||||
use serenity::model::id::GuildId;
|
||||
use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue};
|
||||
use skynet_discord_bot::{is_admin, DataBase, Minecraft};
|
||||
use sqlx::{Error, Pool, Sqlite};
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| {
|
||||
option
|
||||
.name("server_id")
|
||||
.description("ID of the Minecraft server hosted by the Computer Society")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
None => return "Not in a server".to_string(),
|
||||
Some(x) => x,
|
||||
};
|
||||
|
||||
let server_minecraft = if let CommandDataOptionValue::String(id) = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected server_id option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected server_id object")
|
||||
{
|
||||
id.to_owned()
|
||||
} else {
|
||||
return String::from("Expected Server ID");
|
||||
};
|
||||
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||
};
|
||||
let db = db_lock.read().await;
|
||||
|
||||
match server_remove(&db, &g_id, &server_minecraft).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("{:?}", e);
|
||||
return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft);
|
||||
}
|
||||
}
|
||||
|
||||
// no need to clear teh whitelist as it will be reset within 24hr anyways
|
||||
|
||||
"Removed minecraft_server info".to_string()
|
||||
}
|
||||
|
||||
async fn server_remove(db: &Pool<Sqlite>, discord: &GuildId, minecraft: &str) -> Result<Option<Minecraft>, Error> {
|
||||
sqlx::query_as::<_, Minecraft>(
|
||||
"
|
||||
DELETE FROM minecraft
|
||||
WHERE server_discord = ?1 AND server_minecraft = ?2
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.bind(minecraft)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +1,4 @@
|
|||
pub mod add_server;
|
||||
pub mod committee;
|
||||
pub mod link_email;
|
||||
pub mod minecraft;
|
||||
|
|
313
src/lib.rs
313
src/lib.rs
|
@ -8,10 +8,13 @@ use serenity::{
|
|||
prelude::TypeMapKey,
|
||||
};
|
||||
|
||||
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,
|
||||
|
@ -20,18 +23,20 @@ use std::{env, str::FromStr, sync::Arc};
|
|||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct Config {
|
||||
pub skynet_server: GuildId,
|
||||
pub ldap_api: String,
|
||||
// manages where teh database is stored
|
||||
pub home: String,
|
||||
pub database: String,
|
||||
|
||||
pub auth: String,
|
||||
// tokens for discord and other API's
|
||||
pub discord_token: String,
|
||||
pub discord_token_minecraft: String,
|
||||
|
||||
// email settings
|
||||
pub mail_smtp: String,
|
||||
pub mail_user: String,
|
||||
pub mail_pass: String,
|
||||
|
||||
// wolves API base for clubs/socs
|
||||
pub wolves_url: String,
|
||||
}
|
||||
impl TypeMapKey for Config {
|
||||
|
@ -48,10 +53,8 @@ pub fn get_config() -> Config {
|
|||
|
||||
// reasonable defaults
|
||||
let mut config = Config {
|
||||
skynet_server: Default::default(),
|
||||
ldap_api: "https://api.account.skynet.ie".to_string(),
|
||||
auth: "".to_string(),
|
||||
discord_token: "".to_string(),
|
||||
discord_token_minecraft: "".to_string(),
|
||||
|
||||
home: ".".to_string(),
|
||||
database: "database.db".to_string(),
|
||||
|
@ -62,12 +65,6 @@ pub fn get_config() -> Config {
|
|||
wolves_url: "".to_string(),
|
||||
};
|
||||
|
||||
if let Ok(x) = env::var("LDAP_API") {
|
||||
config.ldap_api = x.trim().to_string();
|
||||
}
|
||||
if let Ok(x) = env::var("SKYNET_SERVER") {
|
||||
config.skynet_server = GuildId::from(str_to_num::<u64>(&x));
|
||||
}
|
||||
if let Ok(x) = env::var("HOME") {
|
||||
config.home = x.trim().to_string();
|
||||
}
|
||||
|
@ -75,13 +72,12 @@ pub fn get_config() -> Config {
|
|||
config.database = x.trim().to_string();
|
||||
}
|
||||
|
||||
if let Ok(x) = env::var("LDAP_DISCORD_AUTH") {
|
||||
config.auth = x.trim().to_string();
|
||||
}
|
||||
|
||||
if let Ok(x) = env::var("DISCORD_TOKEN") {
|
||||
config.discord_token = x.trim().to_string();
|
||||
}
|
||||
if let Ok(x) = env::var("DISCORD_TOKEN_MINECRAFT") {
|
||||
config.discord_token_minecraft = x.trim().to_string();
|
||||
}
|
||||
|
||||
if let Ok(x) = env::var("EMAIL_SMTP") {
|
||||
config.mail_smtp = x.trim().to_string();
|
||||
|
@ -100,10 +96,6 @@ pub fn get_config() -> Config {
|
|||
config
|
||||
}
|
||||
|
||||
fn str_to_num<T: FromStr + Default>(x: &str) -> T {
|
||||
x.trim().parse::<T>().unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ServerMembers {
|
||||
pub server: GuildId,
|
||||
|
@ -210,6 +202,27 @@ impl<'r> FromRow<'r, SqliteRow> for WolvesVerify {
|
|||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
|
@ -257,6 +270,23 @@ impl<'r> FromRow<'r, SqliteRow> for Servers {
|
|||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
||||
|
@ -269,58 +299,8 @@ pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
|
|||
)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS wolves (
|
||||
id_wolves integer PRIMARY KEY,
|
||||
email text not null,
|
||||
discord integer,
|
||||
minecraft text
|
||||
)",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS index_discord ON wolves (discord)").execute(&pool).await?;
|
||||
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS wolves_verify (
|
||||
discord integer PRIMARY KEY,
|
||||
email text not null,
|
||||
auth_code text not null,
|
||||
date_expiry text not null
|
||||
)",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS index_date_expiry ON wolves_verify (date_expiry)")
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS server_members (
|
||||
server integer not null,
|
||||
id_wolves integer not null,
|
||||
expiry text not null,
|
||||
PRIMARY KEY(server,id_wolves),
|
||||
FOREIGN KEY (id_wolves) REFERENCES wolves (id_wolves)
|
||||
)",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS servers (
|
||||
server integer PRIMARY KEY,
|
||||
wolves_api text not null,
|
||||
role_past integer,
|
||||
role_current integer,
|
||||
member_past integer DEFAULT 0,
|
||||
member_current integer DEFAULT 0
|
||||
)",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
// migrations are amazing!
|
||||
sqlx::migrate!("./db/migrations").run(&pool).await.unwrap();
|
||||
|
||||
Ok(pool)
|
||||
}
|
||||
|
@ -381,7 +361,7 @@ pub fn random_string(len: usize) -> String {
|
|||
|
||||
pub mod set_roles {
|
||||
use super::*;
|
||||
pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &Vec<UserId>) {
|
||||
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()
|
||||
|
@ -466,7 +446,7 @@ pub mod set_roles {
|
|||
println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]);
|
||||
}
|
||||
|
||||
async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
||||
pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
||||
sqlx::query_as::<_, ServerMembersWolves>(
|
||||
r#"
|
||||
SELECT *
|
||||
|
@ -517,10 +497,11 @@ pub mod get_data {
|
|||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct WolvesResultUser {
|
||||
committee: String,
|
||||
wolves_id: 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,
|
||||
|
@ -568,7 +549,7 @@ pub mod get_data {
|
|||
// 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.wolves_id.parse::<u64>().unwrap_or_default();
|
||||
let id = user.member_id.parse::<u64>().unwrap_or_default();
|
||||
match existing.get(&(id as i64)) {
|
||||
None => {
|
||||
// user does not exist already, add everything
|
||||
|
@ -645,7 +626,7 @@ pub mod get_data {
|
|||
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
|
||||
",
|
||||
)
|
||||
.bind(&user.wolves_id)
|
||||
.bind(&user.member_id)
|
||||
.bind(&user.contact_email)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
|
@ -665,7 +646,7 @@ pub mod get_data {
|
|||
",
|
||||
)
|
||||
.bind(*server.as_u64() as i64)
|
||||
.bind(&user.wolves_id)
|
||||
.bind(&user.member_id)
|
||||
.bind(&user.expiry)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
|
@ -678,3 +659,179 @@ pub mod get_data {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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()
|
||||
}
|
||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -1,4 +1,4 @@
|
|||
mod commands;
|
||||
pub mod commands;
|
||||
|
||||
use serenity::{
|
||||
async_trait,
|
||||
|
@ -60,6 +60,13 @@ impl EventHandler for Handler {
|
|||
.create_application_command(|command| commands::add_server::register(command))
|
||||
.create_application_command(|command| commands::link_email::link::register(command))
|
||||
.create_application_command(|command| commands::link_email::verify::register(command))
|
||||
.create_application_command(|command| commands::minecraft::server::add::register(command))
|
||||
.create_application_command(|command| commands::minecraft::server::list::register(command))
|
||||
.create_application_command(|command| commands::minecraft::server::delete::register(command))
|
||||
.create_application_command(|command| commands::minecraft::user::add::register(command))
|
||||
// for committee server, temp
|
||||
.create_application_command(|command| commands::committee::link::register(command))
|
||||
.create_application_command(|command| commands::committee::verify::register(command))
|
||||
})
|
||||
.await
|
||||
{
|
||||
|
@ -76,9 +83,18 @@ impl EventHandler for Handler {
|
|||
//println!("Received command interaction: {:#?}", command);
|
||||
|
||||
let content = match command.data.name.as_str() {
|
||||
"add" => commands::add_server::run(&command, &ctx).await,
|
||||
"link" => commands::link_email::link::run(&command, &ctx).await,
|
||||
// user commands
|
||||
"link_wolves" => commands::link_email::link::run(&command, &ctx).await,
|
||||
"verify" => commands::link_email::verify::run(&command, &ctx).await,
|
||||
"link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await,
|
||||
// admin commands
|
||||
"add" => commands::add_server::run(&command, &ctx).await,
|
||||
"minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await,
|
||||
"minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await,
|
||||
"minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await,
|
||||
// for teh committee server, temporary
|
||||
"link_committee" => commands::committee::link::run(&command, &ctx).await,
|
||||
"verify_committee" => commands::committee::verify::run(&command, &ctx).await,
|
||||
_ => "not implemented :(".to_string(),
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue