Compare commits
215 Commits
@ -0,0 +1,56 @@
|
|||||||
|
name: Build Container
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
schedule:
|
||||||
|
# daily builds to always include patches in the docker image
|
||||||
|
- cron: '0 4 * * *'
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /var/lib/containers/
|
||||||
|
key: ${{ runner.os }}-podman-${{ hashFiles('Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-podman-
|
||||||
|
- name: Build
|
||||||
|
id: build-image
|
||||||
|
uses: redhat-actions/buildah-build@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
layers: true
|
||||||
|
containerfiles: ./Containerfile
|
||||||
|
platforms: ${{github.event.inputs.platforms}}
|
||||||
|
image: trivernis/tobi
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: redhat-actions/podman-login@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
registry: docker.io
|
||||||
|
- name: Push
|
||||||
|
uses: redhat-actions/push-to-registry@v2
|
||||||
|
with:
|
||||||
|
image: ${{ steps.build-image.outputs.image }}
|
||||||
|
tags: ${{ steps.build-image.outputs.tags }}
|
||||||
|
registry: docker.io
|
@ -0,0 +1,39 @@
|
|||||||
|
version: 1
|
||||||
|
when:
|
||||||
|
- event: [pull_request]
|
||||||
|
- event: push
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
- release/*
|
||||||
|
- fix/*
|
||||||
|
steps:
|
||||||
|
test:
|
||||||
|
image: rust:alpine
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache --force-overwrite \
|
||||||
|
build-base \
|
||||||
|
openssl-dev \
|
||||||
|
libopusenc-dev \
|
||||||
|
libpq-dev \
|
||||||
|
curl \
|
||||||
|
bash
|
||||||
|
- rustup default stable
|
||||||
|
- rustup component add clippy --toolchain stable-x86_64-unknown-linux-musl
|
||||||
|
- cargo clippy
|
||||||
|
- cargo test --verbose --package bot-coreutils
|
||||||
|
- cargo test --verbose --package bot-database
|
||||||
|
- cargo test --verbose
|
||||||
|
|
||||||
|
build:
|
||||||
|
image: rust:alpine
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache --force-overwrite \
|
||||||
|
build-base \
|
||||||
|
openssl-dev \
|
||||||
|
libopusenc-dev \
|
||||||
|
libpq-dev \
|
||||||
|
curl \
|
||||||
|
bash
|
||||||
|
- cargo build
|
||||||
|
when:
|
||||||
|
- event: [pull_request]
|
@ -0,0 +1,20 @@
|
|||||||
|
version: 1
|
||||||
|
when:
|
||||||
|
- event: [tag]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
steps:
|
||||||
|
build:
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
secrets: [forgejo_token]
|
||||||
|
settings:
|
||||||
|
dockerfile: Containerfile
|
||||||
|
tag: ${CI_COMMIT_TAG##v}
|
||||||
|
repo: git.trivernis.net/trivernis/2b-rs
|
||||||
|
registry: git.trivernis.net
|
||||||
|
platforms: linux/amd64
|
||||||
|
username:
|
||||||
|
from_secret: forgejo_id
|
||||||
|
password:
|
||||||
|
from_secret: forgejo_token
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,31 +1,55 @@
|
|||||||
|
[workspace]
|
||||||
|
members=["bot-coreutils", "bot-database", "bot-database/migration", "."]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "tobi-rs"
|
name = "tobi-rs"
|
||||||
version = "0.1.3"
|
version = "0.11.4"
|
||||||
authors = ["trivernis <trivernis@protonmail.com>"]
|
authors = ["trivernis <trivernis@protonmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = 'abort'
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serenity = "0.10.5"
|
bot-database = {path="./bot-database"}
|
||||||
|
bot-coreutils = {path="./bot-coreutils"}
|
||||||
|
serenity = "0.11.5"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
tokio = { version = "1.4.0", features = ["macros", "rt-multi-thread"] }
|
serde_derive = "1.0.145"
|
||||||
serde_derive = "1.0.125"
|
serde = "1.0.145"
|
||||||
serde = "1.0.125"
|
thiserror = "1.0.37"
|
||||||
thiserror = "1.0.24"
|
minecraft-data-rs = "0.5.0"
|
||||||
minecraft-data-rs = "0.2.0"
|
serde_json = "1.0.86"
|
||||||
songbird = "0.1.5"
|
rand = "0.8.5"
|
||||||
serde_json = "1.0.64"
|
regex = "1.6.0"
|
||||||
rand = "0.8.3"
|
aspotify = "0.7.1"
|
||||||
regex = "1.4.5"
|
|
||||||
aspotify = "0.7.0"
|
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
futures = "0.3.13"
|
futures = "0.3.24"
|
||||||
log = "0.4.14"
|
chrono = "0.4.22"
|
||||||
fern = "0.6.0"
|
sysinfo = "0.26.4"
|
||||||
chrono = "0.4.19"
|
reqwest = "0.11.12"
|
||||||
colored = "2.0.0"
|
chrono-tz = "0.6.3"
|
||||||
sysinfo = "0.16.5"
|
sauce-api = "1.0.0"
|
||||||
database = {path="./database"}
|
rustc_version_runtime = "0.2.1"
|
||||||
reqwest = "0.11.2"
|
trigram = "0.4.4"
|
||||||
chrono-tz = "0.5.3"
|
typemap_rev = "0.2.0"
|
||||||
|
youtube-metadata = "0.2.0"
|
||||||
|
xkcd-search = "0.1.2"
|
||||||
|
animethemes-rs = "0.4.5"
|
||||||
|
build-time = "0.1.2"
|
||||||
|
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||||
|
tracing = "0.1.37"
|
||||||
|
serenity-additions = "0.3.4"
|
||||||
|
|
||||||
|
[dependencies.songbird]
|
||||||
|
version = "0.3.0"
|
||||||
|
features = ["yt-dlp"]
|
||||||
|
|
||||||
|
[dependencies.tokio]
|
||||||
|
version = "1.21.2"
|
||||||
|
features = ["macros", "rt-multi-thread"]
|
||||||
|
|
||||||
|
# [patch.crates-io]
|
||||||
|
# serenity-additions = { path = "../serenity-additions" }
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
ARG BASE_IMAGE=docker.io/alpine:edge
|
||||||
|
|
||||||
|
FROM ${BASE_IMAGE} AS build_base
|
||||||
|
RUN apk update
|
||||||
|
RUN apk add --no-cache --force-overwrite \
|
||||||
|
build-base \
|
||||||
|
openssl-dev \
|
||||||
|
libopusenc-dev \
|
||||||
|
libpq-dev \
|
||||||
|
curl \
|
||||||
|
bash
|
||||||
|
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
|
||||||
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
RUN rm -rf /var/lib/{cache,log}/ /var/cache
|
||||||
|
|
||||||
|
FROM build_base AS builder
|
||||||
|
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
||||||
|
WORKDIR /usr/src
|
||||||
|
RUN cargo new tobi
|
||||||
|
WORKDIR /usr/src/tobi
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY src ./src
|
||||||
|
COPY bot-coreutils ./bot-coreutils
|
||||||
|
COPY bot-database ./bot-database
|
||||||
|
RUN cargo build --release
|
||||||
|
RUN mkdir /tmp/tobi
|
||||||
|
RUN cp target/release/tobi-rs /tmp/tobi/
|
||||||
|
|
||||||
|
FROM ${BASE_IMAGE} AS runtime-base
|
||||||
|
RUN apk update
|
||||||
|
RUN apk add --no-cache --force-overwrite \
|
||||||
|
openssl \
|
||||||
|
libopusenc \
|
||||||
|
libpq \
|
||||||
|
python3 \
|
||||||
|
py3-pip \
|
||||||
|
qalc \
|
||||||
|
ffmpeg \
|
||||||
|
bash
|
||||||
|
RUN pip3 install yt-dlp --break-system-packages
|
||||||
|
RUN rm -rf /var/lib/{cache,log}/ /var/cache
|
||||||
|
|
||||||
|
FROM runtime-base
|
||||||
|
COPY --from=builder /tmp/tobi/tobi-rs .
|
||||||
|
ENTRYPOINT ["/tobi-rs"]
|
@ -0,0 +1 @@
|
|||||||
|
target
|
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "bot-coreutils"
|
||||||
|
version = "0.1.1"
|
||||||
|
authors = ["trivernis <trivernis@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.21.2", features = ["process", "io-util"] }
|
||||||
|
log = "0.4.17"
|
||||||
|
url = "2.3.1"
|
||||||
|
mime_guess = "2.0.4"
|
||||||
|
rand = "0.8.5"
|
@ -0,0 +1,10 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
pub mod process;
|
||||||
|
pub mod shuffle;
|
||||||
|
pub mod string;
|
||||||
|
/// Utilities to quickly check strings that represent urls
|
||||||
|
pub mod url;
|
||||||
|
|
||||||
|
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
@ -0,0 +1,19 @@
|
|||||||
|
use std::io;
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
/// Asynchronously runs a given command and returns the output
|
||||||
|
pub async fn run_command_async(command: &str, args: &[&str]) -> io::Result<String> {
|
||||||
|
log::trace!("Running command '{}' with args {:?}", command, args);
|
||||||
|
let process_output: std::process::Output = Command::new(command).args(args).output().await?;
|
||||||
|
|
||||||
|
log::trace!("Reading from stderr...");
|
||||||
|
let stderr = String::from_utf8_lossy(&process_output.stderr[..]);
|
||||||
|
let stdout = String::from_utf8_lossy(&process_output.stdout[..]);
|
||||||
|
|
||||||
|
if stderr.len() != 0 {
|
||||||
|
log::trace!("STDERR of command {}: {}", command, stderr);
|
||||||
|
}
|
||||||
|
log::trace!("Command output is {}", stdout);
|
||||||
|
|
||||||
|
Ok(stdout.to_string())
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
use rand::seq::SliceRandom;
|
||||||
|
|
||||||
|
/// Chooses a random value from the given iterator
|
||||||
|
/// panics when the iterator is empty
|
||||||
|
pub fn choose_unchecked<'a, I: SliceRandom<Item = &'a T>, T>(i: I) -> &'a T {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
i.choose(&mut rng).unwrap()
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
use rand::Rng;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub trait Shuffle {
|
||||||
|
fn shuffle(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Shuffle for VecDeque<T> {
|
||||||
|
/// Fisher-Yates shuffle implementation
|
||||||
|
/// for VecDeque.
|
||||||
|
fn shuffle(&mut self) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let mut i = self.len();
|
||||||
|
|
||||||
|
while i >= 2 {
|
||||||
|
i -= 1;
|
||||||
|
self.swap(i, rng.gen_range(0..i + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
/// Enquotes a string in a safe way
|
||||||
|
pub fn enquote<S: ToString>(value: S) -> String {
|
||||||
|
let value = value.to_string();
|
||||||
|
format!("\"{}\"", value.replace("\"", "\\\""))
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod url_tests;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod string_tests;
|
@ -0,0 +1,8 @@
|
|||||||
|
use crate::string::enquote;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enquote() {
|
||||||
|
assert_eq!(enquote("hello"), r#""hello""#);
|
||||||
|
assert_eq!(enquote(r#"hello "there""#), r#""hello \"there\"""#);
|
||||||
|
assert_eq!(enquote(""), r#""""#);
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
use crate::url::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_returns_the_domain_name() {
|
||||||
|
assert_eq!(
|
||||||
|
get_domain_for_url("https://domain.com/sub/sub"),
|
||||||
|
Some("domain.com".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_domain_for_url("other-domain.com"),
|
||||||
|
Some("other-domain.com".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(get_domain_for_url("Invalid URL"), None);
|
||||||
|
assert_eq!(get_domain_for_url("file:////what/a/file.txt"), None);
|
||||||
|
assert_eq!(
|
||||||
|
get_domain_for_url("https://www.domain.com/sub",),
|
||||||
|
Some("domain.com".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_checks_for_image() {
|
||||||
|
assert!(is_image("domain.com/image.png"));
|
||||||
|
assert!(is_image("https://domain.com/image.jpeg?yo=someparam"));
|
||||||
|
assert!(!is_image("https://domain.com"));
|
||||||
|
assert!(!is_image("https://domain.com/file.pdf"));
|
||||||
|
assert!(!is_image("not an url"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_checks_for_video() {
|
||||||
|
assert!(is_video("domain.com/video.mp4"));
|
||||||
|
assert!(is_video("https://domain.com/video.webm?yo=someparam"));
|
||||||
|
assert!(!is_video("https://domain.com"));
|
||||||
|
assert!(!is_video("https://domain.com/file.pdf"));
|
||||||
|
assert!(!is_video("not an url"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_checks_if_its_valid() {
|
||||||
|
assert!(is_valid("https://domain.com"));
|
||||||
|
assert!(!is_valid("domain.com"));
|
||||||
|
assert!(is_valid("https://url.com/sub/sub/sub.txt"))
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
use mime_guess::{mime, Mime};
|
||||||
|
|
||||||
|
pub use url::*;
|
||||||
|
|
||||||
|
static PROTOCOL_HTTP: &str = "http://";
|
||||||
|
static PROTOCOL_HTTPS: &str = "https://";
|
||||||
|
static PROTOCOL_FILE: &str = "file:////";
|
||||||
|
static PROTOCOL_DATA: &str = "data:";
|
||||||
|
static PROTOCOLS: &[&str] = &[PROTOCOL_HTTP, PROTOCOL_HTTPS, PROTOCOL_FILE, PROTOCOL_DATA];
|
||||||
|
|
||||||
|
/// Adds the protocol in front of the url if it is missing from the input
|
||||||
|
fn add_missing_protocol(url_str: &str) -> String {
|
||||||
|
for protocol in PROTOCOLS {
|
||||||
|
if url_str.starts_with(protocol) {
|
||||||
|
return url_str.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("{}{}", PROTOCOL_HTTPS, url_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the given url into the url representation
|
||||||
|
/// Allows for fuzzier input than the original method. If no protocol is provided,
|
||||||
|
/// it assumes https.
|
||||||
|
#[inline]
|
||||||
|
pub fn parse_url(url_str: &str) -> Result<Url, url::ParseError> {
|
||||||
|
let url_str = add_missing_protocol(url_str);
|
||||||
|
Url::parse(&url_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the domain for a given url string
|
||||||
|
/// Example
|
||||||
|
/// ```
|
||||||
|
/// use bot_coreutils::url::get_domain_for_url;
|
||||||
|
///
|
||||||
|
/// assert_eq!(get_domain_for_url("https://reddit.com/r/anime"), Some("reddit.com".to_string()));
|
||||||
|
/// assert_eq!(get_domain_for_url("reddit.com"), Some("reddit.com".to_string()));
|
||||||
|
/// assert_eq!(get_domain_for_url("invalid url"), None);
|
||||||
|
/// ```
|
||||||
|
pub fn get_domain_for_url(url_str: &str) -> Option<String> {
|
||||||
|
let url = parse_url(url_str).ok()?;
|
||||||
|
let domain = url.domain()?;
|
||||||
|
|
||||||
|
Some(domain.trim_start_matches("www.").to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guesses the mime for a given url string
|
||||||
|
#[inline]
|
||||||
|
fn guess_mime_for_url(url_str: &str) -> Option<Mime> {
|
||||||
|
parse_url(url_str)
|
||||||
|
.ok()
|
||||||
|
.and_then(|u| mime_guess::from_path(u.path()).first())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if a given url could be an image
|
||||||
|
pub fn is_image(url_str: &str) -> bool {
|
||||||
|
if let Some(guess) = guess_mime_for_url(url_str) {
|
||||||
|
guess.type_() == mime::IMAGE
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if a given url could be a video
|
||||||
|
pub fn is_video(url_str: &str) -> bool {
|
||||||
|
if let Some(guess) = guess_mime_for_url(url_str) {
|
||||||
|
guess.type_() == mime::VIDEO
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if the given url is valid
|
||||||
|
pub fn is_valid(url_str: &str) -> bool {
|
||||||
|
Url::parse(url_str).is_ok()
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
.env
|
@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "bot-database"
|
||||||
|
version = "0.6.0"
|
||||||
|
authors = ["trivernis <trivernis@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
chrono = "0.4.22"
|
||||||
|
thiserror = "1.0.37"
|
||||||
|
tracing = "0.1.37"
|
||||||
|
|
||||||
|
[dependencies.sea-orm]
|
||||||
|
version = "0.9.3"
|
||||||
|
features = ["runtime-tokio-native-tls", "sqlx-postgres"]
|
||||||
|
|
||||||
|
[dependencies.migration]
|
||||||
|
path = "./migration"
|
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "migration"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "migration"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sea-orm-migration = "0.9.3"
|
||||||
|
tokio = { version = "1.21.2", features = ["rt", "net", "tracing"] }
|
@ -0,0 +1,37 @@
|
|||||||
|
# Running Migrator CLI
|
||||||
|
|
||||||
|
- Apply all pending migrations
|
||||||
|
```sh
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
```sh
|
||||||
|
cargo run -- up
|
||||||
|
```
|
||||||
|
- Apply first 10 pending migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- up -n 10
|
||||||
|
```
|
||||||
|
- Rollback last applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- down
|
||||||
|
```
|
||||||
|
- Rollback last 10 applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- down -n 10
|
||||||
|
```
|
||||||
|
- Drop all tables from the database, then reapply all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- fresh
|
||||||
|
```
|
||||||
|
- Rollback all applied migrations, then reapply all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- refresh
|
||||||
|
```
|
||||||
|
- Rollback all applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- reset
|
||||||
|
```
|
||||||
|
- Check the status of all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- status
|
||||||
|
```
|
@ -0,0 +1,16 @@
|
|||||||
|
pub use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
mod m20220029_164527_change_timestamp_format;
|
||||||
|
mod m20220101_000001_create_table;
|
||||||
|
|
||||||
|
pub struct Migrator;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigratorTrait for Migrator {
|
||||||
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
|
vec![
|
||||||
|
Box::new(m20220101_000001_create_table::Migration),
|
||||||
|
Box::new(m20220029_164527_change_timestamp_format::Migration),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
use crate::{DbErr, Table};
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum Statistics {
|
||||||
|
Table,
|
||||||
|
ExecutedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum EphemeralMessages {
|
||||||
|
Table,
|
||||||
|
Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20220029_164527_change_timestamp_format"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Statistics::Table)
|
||||||
|
.modify_column(
|
||||||
|
ColumnDef::new(Statistics::ExecutedAt)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(EphemeralMessages::Table)
|
||||||
|
.modify_column(
|
||||||
|
ColumnDef::new(EphemeralMessages::Timeout)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Statistics::Table)
|
||||||
|
.modify_column(
|
||||||
|
ColumnDef::new(Statistics::ExecutedAt)
|
||||||
|
.timestamp()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(EphemeralMessages::Table)
|
||||||
|
.modify_column(
|
||||||
|
ColumnDef::new(EphemeralMessages::Timeout)
|
||||||
|
.timestamp()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,287 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum EphemeralMessages {
|
||||||
|
Table,
|
||||||
|
ChannelId,
|
||||||
|
MessageId,
|
||||||
|
Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum GuildPlaylists {
|
||||||
|
Table,
|
||||||
|
GuildId,
|
||||||
|
Name,
|
||||||
|
Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum GuildSettings {
|
||||||
|
Table,
|
||||||
|
GuildId,
|
||||||
|
Key,
|
||||||
|
Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum Media {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
Category,
|
||||||
|
Name,
|
||||||
|
Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum Statistics {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
Version,
|
||||||
|
Command,
|
||||||
|
ExecutedAt,
|
||||||
|
Success,
|
||||||
|
ErrorMsg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum YoutubeSongs {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
SpotifyId,
|
||||||
|
Artist,
|
||||||
|
Title,
|
||||||
|
Album,
|
||||||
|
Url,
|
||||||
|
Score,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20220101_000001_create_table"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager.create_table(ephemeral_messages()).await?;
|
||||||
|
manager.create_table(guild_playlists()).await?;
|
||||||
|
manager.create_table(guild_settings()).await?;
|
||||||
|
manager.create_table(media()).await?;
|
||||||
|
manager.create_table(statistics()).await?;
|
||||||
|
manager.create_table(youtube_songs()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(EphemeralMessages::Table).to_owned())
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(GuildPlaylists::Table).to_owned())
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(GuildSettings::Table).to_owned())
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(Media::Table).to_owned())
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(Statistics::Table).to_owned())
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(YoutubeSongs::Table).to_owned())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ephemeral_messages() -> TableCreateStatement {
|
||||||
|
Table::create()
|
||||||
|
.table(EphemeralMessages::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(EphemeralMessages::ChannelId)
|
||||||
|
.big_integer()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(EphemeralMessages::MessageId)
|
||||||
|
.big_integer()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(EphemeralMessages::Timeout)
|
||||||
|
.timestamp()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.primary_key(
|
||||||
|
Index::create()
|
||||||
|
.col(EphemeralMessages::ChannelId)
|
||||||
|
.col(EphemeralMessages::MessageId),
|
||||||
|
)
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guild_playlists() -> TableCreateStatement {
|
||||||
|
Table::create()
|
||||||
|
.table(GuildPlaylists::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(GuildPlaylists::GuildId)
|
||||||
|
.big_integer()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(GuildPlaylists::Name)
|
||||||
|
.string_len(255)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(GuildPlaylists::Url)
|
||||||
|
.string_len(1204)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.primary_key(
|
||||||
|
Index::create()
|
||||||
|
.col(GuildPlaylists::GuildId)
|
||||||
|
.col(GuildPlaylists::Name),
|
||||||
|
)
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guild_settings() -> TableCreateStatement {
|
||||||
|
Table::create()
|
||||||
|
.table(GuildSettings::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(GuildSettings::GuildId)
|
||||||
|
.big_integer()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(GuildSettings::Key)
|
||||||
|
.string_len(255)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(GuildSettings::Value)
|
||||||
|
.string_len(1024)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.primary_key(
|
||||||
|
Index::create()
|
||||||
|
.col(GuildSettings::GuildId)
|
||||||
|
.col(GuildSettings::Key),
|
||||||
|
)
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn media() -> TableCreateStatement {
|
||||||
|
Table::create()
|
||||||
|
.table(Media::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(Media::Id)
|
||||||
|
.big_integer()
|
||||||
|
.auto_increment()
|
||||||
|
.not_null()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(Media::Category).string_len(128))
|
||||||
|
.col(ColumnDef::new(Media::Name).string_len(128))
|
||||||
|
.col(ColumnDef::new(Media::Url).string_len(128))
|
||||||
|
.index(
|
||||||
|
Index::create()
|
||||||
|
.unique()
|
||||||
|
.col(Media::Category)
|
||||||
|
.col(Media::Name),
|
||||||
|
)
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn statistics() -> TableCreateStatement {
|
||||||
|
Table::create()
|
||||||
|
.table(Statistics::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(Statistics::Id)
|
||||||
|
.big_integer()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(Statistics::Version)
|
||||||
|
.string_len(32)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(Statistics::Command)
|
||||||
|
.string_len(255)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(Statistics::ExecutedAt)
|
||||||
|
.timestamp()
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(Statistics::Success)
|
||||||
|
.boolean()
|
||||||
|
.not_null()
|
||||||
|
.default(true),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(Statistics::ErrorMsg).string())
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn youtube_songs() -> TableCreateStatement {
|
||||||
|
Table::create()
|
||||||
|
.table(YoutubeSongs::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(YoutubeSongs::Id)
|
||||||
|
.big_integer()
|
||||||
|
.primary_key()
|
||||||
|
.auto_increment(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(YoutubeSongs::SpotifyId)
|
||||||
|
.string_len(255)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(YoutubeSongs::Artist)
|
||||||
|
.string_len(128)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(YoutubeSongs::Title)
|
||||||
|
.string_len(255)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(YoutubeSongs::Album)
|
||||||
|
.string_len(255)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(YoutubeSongs::Url).string_len(128).not_null())
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(YoutubeSongs::Score)
|
||||||
|
.integer()
|
||||||
|
.default(0)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
|
.index(
|
||||||
|
Index::create()
|
||||||
|
.unique()
|
||||||
|
.col(YoutubeSongs::SpotifyId)
|
||||||
|
.col(YoutubeSongs::Url),
|
||||||
|
)
|
||||||
|
.to_owned()
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
use migration::Migrator;
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
cli::run_cli(Migrator).await;
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use crate::entity::ephemeral_messages;
|
||||||
|
use crate::error::DatabaseResult;
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
|
||||||
|
impl super::BotDatabase {
|
||||||
|
/// Adds a command statistic to the database
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn add_ephemeral_message(
|
||||||
|
&self,
|
||||||
|
channel_id: u64,
|
||||||
|
message_id: u64,
|
||||||
|
timeout: SystemTime,
|
||||||
|
) -> DatabaseResult<()> {
|
||||||
|
let model = ephemeral_messages::ActiveModel {
|
||||||
|
channel_id: Set(channel_id as i64),
|
||||||
|
message_id: Set(message_id as i64),
|
||||||
|
timeout: Set(DateTimeLocal::from(timeout).into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
model.insert(&self.db).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a vec of all ephemeral messages
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn get_ephemeral_messages(&self) -> DatabaseResult<Vec<ephemeral_messages::Model>> {
|
||||||
|
let messages = ephemeral_messages::Entity::find().all(&self.db).await?;
|
||||||
|
|
||||||
|
Ok(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes a single ephemeral message
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn delete_ephemeral_message(
|
||||||
|
&self,
|
||||||
|
channel_id: i64,
|
||||||
|
message_id: i64,
|
||||||
|
) -> DatabaseResult<()> {
|
||||||
|
ephemeral_messages::Entity::delete_many()
|
||||||
|
.filter(ephemeral_messages::Column::ChannelId.eq(channel_id))
|
||||||
|
.filter(ephemeral_messages::Column::MessageId.eq(message_id))
|
||||||
|
.exec(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
use crate::entity::guild_playlists;
|
||||||
|
use crate::error::DatabaseResult;
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
|
||||||
|
impl super::BotDatabase {
|
||||||
|
/// Returns a list of all guild playlists
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn get_guild_playlists(
|
||||||
|
&self,
|
||||||
|
guild_id: u64,
|
||||||
|
) -> DatabaseResult<Vec<guild_playlists::Model>> {
|
||||||
|
let playlists = guild_playlists::Entity::find()
|
||||||
|
.filter(guild_playlists::Column::GuildId.eq(guild_id))
|
||||||
|
.all(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(playlists)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a guild playlist by name
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn get_guild_playlist(
|
||||||
|
&self,
|
||||||
|
guild_id: u64,
|
||||||
|
name: String,
|
||||||
|
) -> DatabaseResult<Option<guild_playlists::Model>> {
|
||||||
|
let playlist = guild_playlists::Entity::find()
|
||||||
|
.filter(guild_playlists::Column::GuildId.eq(guild_id))
|
||||||
|
.filter(guild_playlists::Column::Name.eq(name))
|
||||||
|
.one(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new playlist to the database overwriting the old one
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn add_guild_playlist(
|
||||||
|
&self,
|
||||||
|
guild_id: u64,
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
) -> DatabaseResult<()> {
|
||||||
|
let model = guild_playlists::ActiveModel {
|
||||||
|
guild_id: Set(guild_id as i64),
|
||||||
|
name: Set(name),
|
||||||
|
url: Set(url),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
model.insert(&self.db).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use std::any;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::entity::guild_settings;
|
||||||
|
use crate::error::DatabaseResult;
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
|
||||||
|
impl super::BotDatabase {
|
||||||
|
/// Returns a guild setting from the database
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn get_guild_setting<T: 'static, S: AsRef<str> + Debug>(
|
||||||
|
&self,
|
||||||
|
guild_id: u64,
|
||||||
|
key: S,
|
||||||
|
) -> DatabaseResult<Option<T>>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
{
|
||||||
|
let setting = guild_settings::Entity::find()
|
||||||
|
.filter(guild_settings::Column::GuildId.eq(guild_id as i64))
|
||||||
|
.filter(guild_settings::Column::Key.eq(key.as_ref()))
|
||||||
|
.one(&self.db)
|
||||||
|
.await?;
|
||||||
|
if let Some(setting) = setting {
|
||||||
|
if any::TypeId::of::<T>() == any::TypeId::of::<bool>() {
|
||||||
|
Ok(setting
|
||||||
|
.value
|
||||||
|
.clone()
|
||||||
|
.unwrap_or("false".to_string())
|
||||||
|
.parse::<T>()
|
||||||
|
.ok())
|
||||||
|
} else {
|
||||||
|
Ok(setting.value.clone().and_then(|v| v.parse::<T>().ok()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upserting a guild setting
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn set_guild_setting<T>(
|
||||||
|
&self,
|
||||||
|
guild_id: u64,
|
||||||
|
key: String,
|
||||||
|
value: T,
|
||||||
|
) -> DatabaseResult<()>
|
||||||
|
where
|
||||||
|
T: 'static + ToString + FromStr + Debug,
|
||||||
|
{
|
||||||
|
let model = guild_settings::ActiveModel {
|
||||||
|
guild_id: Set(guild_id as i64),
|
||||||
|
key: Set(key.clone()),
|
||||||
|
value: Set(Some(value.to_string())),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
if self
|
||||||
|
.get_guild_setting::<T, _>(guild_id, &key)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
model.update(&self.db).await?;
|
||||||
|
} else {
|
||||||
|
model.insert(&self.db).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes a guild setting
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn delete_guild_setting<S: AsRef<str> + Debug>(
|
||||||
|
&self,
|
||||||
|
guild_id: u64,
|
||||||
|
key: S,
|
||||||
|
) -> DatabaseResult<()> {
|
||||||
|
guild_settings::Entity::delete_many()
|
||||||
|
.filter(guild_settings::Column::GuildId.eq(guild_id))
|
||||||
|
.filter(guild_settings::Column::Key.eq(key.as_ref()))
|
||||||
|
.exec(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
use crate::entity::media;
|
||||||
|
use crate::error::DatabaseResult;
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
impl super::BotDatabase {
|
||||||
|
/// Returns a list of all gifs in the database
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn get_all_media(&self) -> DatabaseResult<Vec<media::Model>> {
|
||||||
|
let entries = media::Entity::find().all(&self.db).await?;
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of gifs by assigned category
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn get_media_by_category<S: AsRef<str> + 'static + Debug>(
|
||||||
|
&self,
|
||||||
|
category: S,
|
||||||
|
) -> DatabaseResult<Vec<media::Model>> {
|
||||||
|
let entries = media::Entity::find()
|
||||||
|
.filter(media::Column::Category.eq(category.as_ref()))
|
||||||
|
.all(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a gif to the database
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn add_media(
|
||||||
|
&self,
|
||||||
|
url: String,
|
||||||
|
category: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
) -> DatabaseResult<()> {
|
||||||
|
let model = media::ActiveModel {
|
||||||
|
url: Set(url),
|
||||||
|
category: Set(category),
|
||||||
|
name: Set(name),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
model.insert(&self.db).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
pub use ephemeral_messages::*;
|
||||||
|
pub use guild_playlists::*;
|
||||||
|
pub use guild_playlists::*;
|
||||||
|
pub use media::*;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
pub use statistics::*;
|
||||||
|
pub use youtube_songs::*;
|
||||||
|
|
||||||
|
mod ephemeral_messages;
|
||||||
|
mod guild_playlists;
|
||||||
|
mod guild_settings;
|
||||||
|
mod media;
|
||||||
|
mod statistics;
|
||||||
|
mod youtube_songs;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BotDatabase {
|
||||||
|
db: DatabaseConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BotDatabase {
|
||||||
|
pub fn new(db: DatabaseConnection) -> Self {
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
use crate::entity::statistics;
|
||||||
|
use crate::error::DatabaseResult;
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use sea_orm::{FromQueryResult, QuerySelect};
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
#[derive(FromQueryResult)]
|
||||||
|
struct CommandCount {
|
||||||
|
count: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::BotDatabase {
|
||||||
|
/// Adds a command statistic to the database
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn add_statistic(
|
||||||
|
&self,
|
||||||
|
version: String,
|
||||||
|
command: String,
|
||||||
|
executed_at: SystemTime,
|
||||||
|
success: bool,
|
||||||
|
error_msg: Option<String>,
|
||||||
|
) -> DatabaseResult<()> {
|
||||||
|
let model = statistics::ActiveModel {
|
||||||
|
version: Set(version),
|
||||||
|
command: Set(command),
|
||||||
|
executed_at: Set(DateTimeLocal::from(executed_at).into()),
|
||||||
|
success: Set(success),
|
||||||
|
error_msg: Set(error_msg),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
model.insert(&self.db).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total number of commands executed
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn get_total_commands_statistic(&self) -> DatabaseResult<u64> {
|
||||||
|
let total_count: Option<CommandCount> = statistics::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column_as(statistics::Column::Id.count(), "count")
|
||||||
|
.into_model::<CommandCount>()
|
||||||
|
.one(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(total_count.unwrap().count as u64)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
use crate::entity::youtube_songs;
|
||||||
|
use crate::error::DatabaseResult;
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
|
||||||
|
impl super::BotDatabase {
|
||||||
|
/// Adds a song to the database or increments the score when it
|
||||||
|
/// already exists
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn add_song(
|
||||||
|
&self,
|
||||||
|
spotify_id: String,
|
||||||
|
artist: String,
|
||||||
|
title: String,
|
||||||
|
album: String,
|
||||||
|
url: String,
|
||||||
|
) -> DatabaseResult<()> {
|
||||||
|
if let Some(model) = self.get_song(&spotify_id).await? {
|
||||||
|
let mut active_model: youtube_songs::ActiveModel = model.into();
|
||||||
|
active_model.score = Set(active_model.score.unwrap() + 1);
|
||||||
|
active_model.update(&self.db).await?;
|
||||||
|
} else {
|
||||||
|
let model = youtube_songs::ActiveModel {
|
||||||
|
spotify_id: Set(spotify_id),
|
||||||
|
artist: Set(artist),
|
||||||
|
title: Set(title),
|
||||||
|
album: Set(album),
|
||||||
|
url: Set(url),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
model.insert(&self.db).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the song with the best score for the given query
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn get_song(&self, spotify_id: &str) -> DatabaseResult<Option<youtube_songs::Model>> {
|
||||||
|
let song = youtube_songs::Entity::find()
|
||||||
|
.filter(youtube_songs::Column::SpotifyId.eq(spotify_id))
|
||||||
|
.one(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(song)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes a song from the database
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn delete_song(&self, id: i64) -> DatabaseResult<()> {
|
||||||
|
youtube_songs::Entity::delete_many()
|
||||||
|
.filter(youtube_songs::Column::Id.eq(id))
|
||||||
|
.exec(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "ephemeral_messages")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub channel_id: i64,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub message_id: i64,
|
||||||
|
pub timeout: DateTimeWithTimeZone,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
panic!("No RelationDef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -0,0 +1,24 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "guild_playlists")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub guild_id: i64,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub name: String,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
panic!("No RelationDef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -0,0 +1,24 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "guild_settings")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub guild_id: i64,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub key: String,
|
||||||
|
pub value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
panic!("No RelationDef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -0,0 +1,24 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "media")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
pub category: Option<String>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
panic!("No RelationDef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -0,0 +1,10 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
|
||||||
|
|
||||||
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod ephemeral_messages;
|
||||||
|
pub mod guild_playlists;
|
||||||
|
pub mod guild_settings;
|
||||||
|
pub mod media;
|
||||||
|
pub mod statistics;
|
||||||
|
pub mod youtube_songs;
|
@ -0,0 +1,8 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
|
||||||
|
|
||||||
|
pub use super::ephemeral_messages::Entity as EphemeralMessages;
|
||||||
|
pub use super::guild_playlists::Entity as GuildPlaylists;
|
||||||
|
pub use super::guild_settings::Entity as GuildSettings;
|
||||||
|
pub use super::media::Entity as Media;
|
||||||
|
pub use super::statistics::Entity as Statistics;
|
||||||
|
pub use super::youtube_songs::Entity as YoutubeSongs;
|
@ -0,0 +1,27 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "statistics")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
pub version: String,
|
||||||
|
pub command: String,
|
||||||
|
pub executed_at: DateTimeWithTimeZone,
|
||||||
|
pub success: bool,
|
||||||
|
#[sea_orm(column_type = "Text", nullable)]
|
||||||
|
pub error_msg: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
panic!("No RelationDef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -0,0 +1,27 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "youtube_songs")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
pub spotify_id: String,
|
||||||
|
pub artist: String,
|
||||||
|
pub title: String,
|
||||||
|
pub album: String,
|
||||||
|
pub url: String,
|
||||||
|
pub score: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
panic!("No RelationDef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
@ -0,0 +1,21 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type DatabaseResult<T> = Result<T, DatabaseError>;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum DatabaseError {
|
||||||
|
#[error("DotEnv Error: {0}")]
|
||||||
|
DotEnv(#[from] dotenv::Error),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
SeaOrm(#[from] sea_orm::error::DbErr),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
Msg(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for DatabaseError {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Self::Msg(s.to_string())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
use crate::error::DatabaseResult;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
pub mod database;
|
||||||
|
pub mod entity;
|
||||||
|
pub mod error;
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
|
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
pub use database::BotDatabase as Database;
|
||||||
|
use migration::MigratorTrait;
|
||||||
|
use sea_orm::{ConnectOptions, Database as SeaDatabase, DatabaseConnection};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn get_connection() -> DatabaseResult<DatabaseConnection> {
|
||||||
|
let database_url = env::var("DATABASE_URL").expect("No DATABASE_URL in path");
|
||||||
|
tracing::debug!("Establishing database connection...");
|
||||||
|
let opt = ConnectOptions::new(database_url);
|
||||||
|
let db = SeaDatabase::connect(opt).await?;
|
||||||
|
tracing::debug!("Running migrations...");
|
||||||
|
migration::Migrator::up(&db, None).await?;
|
||||||
|
tracing::debug!("Migrations finished");
|
||||||
|
tracing::info!("Database connection initialized");
|
||||||
|
|
||||||
|
Ok(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_database() -> DatabaseResult<Database> {
|
||||||
|
let conn = get_connection().await?;
|
||||||
|
Ok(Database::new(conn))
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
use super::entity;
|
||||||
|
|
||||||
|
pub use entity::ephemeral_messages::Model as EphemeralMessage;
|
||||||
|
pub use entity::guild_playlists::Model as GuildPlaylist;
|
||||||
|
pub use entity::guild_settings::Model as GuildSetting;
|
||||||
|
pub use entity::media::Model as Media;
|
||||||
|
pub use entity::statistics::Model as Statistic;
|
||||||
|
pub use entity::youtube_songs::Model as YoutubeSong;
|
@ -1,467 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
[[package]]
|
|
||||||
name = "async-trait"
|
|
||||||
version = "0.1.48"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "1.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono"
|
|
||||||
version = "0.4.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
"time",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "database"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"diesel",
|
|
||||||
"diesel_migrations",
|
|
||||||
"dotenv",
|
|
||||||
"log",
|
|
||||||
"r2d2",
|
|
||||||
"thiserror",
|
|
||||||
"tokio-diesel",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diesel"
|
|
||||||
version = "1.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "047bfc4d5c3bd2ef6ca6f981941046113524b9a9f9a7cbdfdd7ff40f58e6f542"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"byteorder",
|
|
||||||
"chrono",
|
|
||||||
"diesel_derives",
|
|
||||||
"pq-sys",
|
|
||||||
"r2d2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diesel_derives"
|
|
||||||
version = "1.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diesel_migrations"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c"
|
|
||||||
dependencies = [
|
|
||||||
"migrations_internals",
|
|
||||||
"migrations_macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dotenv"
|
|
||||||
version = "0.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-io",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-channel"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-core"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-io"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-sink"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-task"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-util"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"pin-project-lite",
|
|
||||||
"pin-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hermit-abi"
|
|
||||||
version = "0.1.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "instant"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.93"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
|
|
||||||
dependencies = [
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "migrations_internals"
|
|
||||||
version = "1.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860"
|
|
||||||
dependencies = [
|
|
||||||
"diesel",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "migrations_macros"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
|
|
||||||
dependencies = [
|
|
||||||
"migrations_internals",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-integer"
|
|
||||||
version = "0.1.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num_cpus"
|
|
||||||
version = "1.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
|
||||||
dependencies = [
|
|
||||||
"hermit-abi",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
|
||||||
dependencies = [
|
|
||||||
"instant",
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"instant",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-lite"
|
|
||||||
version = "0.2.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-utils"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pq-sys"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
|
|
||||||
dependencies = [
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.26"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-xid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "r2d2"
|
|
||||||
version = "0.8.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"parking_lot",
|
|
||||||
"scheduled-thread-pool",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scheduled-thread-pool"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7"
|
|
||||||
dependencies = [
|
|
||||||
"parking_lot",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smallvec"
|
|
||||||
version = "1.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-xid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time"
|
|
||||||
version = "0.1.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"wasi",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num_cpus",
|
|
||||||
"pin-project-lite",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-diesel"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "git+https://github.com/Trivernis/tokio-diesel#f4af42558246ab323600622ba8d08803d3c18842"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"diesel",
|
|
||||||
"futures",
|
|
||||||
"r2d2",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.10.0+wasi-snapshot-preview1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu",
|
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "database"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["trivernis <trivernis@protonmail.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
dotenv = "0.15.0"
|
|
||||||
chrono = "0.4.19"
|
|
||||||
thiserror = "1.0.24"
|
|
||||||
diesel = {version="1.4.6", features=["postgres", "r2d2", "chrono"]}
|
|
||||||
log = "0.4.14"
|
|
||||||
diesel_migrations = "1.4.0"
|
|
||||||
r2d2 = "0.8.9"
|
|
||||||
tokio-diesel = {git = "https://github.com/Trivernis/tokio-diesel"}
|
|
@ -1,5 +0,0 @@
|
|||||||
# For documentation on how to configure this file,
|
|
||||||
# see diesel.rs/guides/configuring-diesel-cli
|
|
||||||
|
|
||||||
[print_schema]
|
|
||||||
file = "src/schema.rs"
|
|
@ -1,6 +0,0 @@
|
|||||||
-- This file was automatically created by Diesel to setup helper functions
|
|
||||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
|
||||||
-- changes will be added to existing projects as new migrations.
|
|
||||||
|
|
||||||
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
|
||||||
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
|
@ -1,36 +0,0 @@
|
|||||||
-- This file was automatically created by Diesel to setup helper functions
|
|
||||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
|
||||||
-- changes will be added to existing projects as new migrations.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Sets up a trigger for the given table to automatically set a column called
|
|
||||||
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
|
||||||
-- in the modified columns)
|
|
||||||
--
|
|
||||||
-- # Example
|
|
||||||
--
|
|
||||||
-- ```sql
|
|
||||||
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
|
||||||
--
|
|
||||||
-- SELECT diesel_manage_updated_at('users');
|
|
||||||
-- ```
|
|
||||||
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
|
||||||
BEGIN
|
|
||||||
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
|
||||||
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
|
||||||
BEGIN
|
|
||||||
IF (
|
|
||||||
NEW IS DISTINCT FROM OLD AND
|
|
||||||
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
|
||||||
) THEN
|
|
||||||
NEW.updated_at := current_timestamp;
|
|
||||||
END IF;
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
@ -1,2 +0,0 @@
|
|||||||
-- This file should undo anything in `up.sql`
|
|
||||||
DROP TABLE guild_settings;
|
|
@ -1,6 +0,0 @@
|
|||||||
CREATE TABLE guild_settings (
|
|
||||||
guild_id BIGINT NOT NULL,
|
|
||||||
key VARCHAR(255) NOT NULL,
|
|
||||||
value VARCHAR(1024),
|
|
||||||
PRIMARY KEY (guild_id, key)
|
|
||||||
);
|
|
@ -1,2 +0,0 @@
|
|||||||
-- This file should undo anything in `up.sql`
|
|
||||||
DROP TABLE guild_playlists;
|
|
@ -1,7 +0,0 @@
|
|||||||
-- Your SQL goes here
|
|
||||||
CREATE TABLE guild_playlists (
|
|
||||||
guild_id BIGINT NOT NULL,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
url VARCHAR(1024) NOT NULL,
|
|
||||||
PRIMARY KEY (guild_id, name)
|
|
||||||
)
|
|
@ -1,155 +0,0 @@
|
|||||||
use crate::error::DatabaseResult;
|
|
||||||
use crate::models::*;
|
|
||||||
use crate::schema::*;
|
|
||||||
use crate::PoolConnection;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use diesel::{delete, insert_into};
|
|
||||||
use std::any;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use tokio_diesel::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Database {
|
|
||||||
pool: PoolConnection,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Database {}
|
|
||||||
unsafe impl Sync for Database {}
|
|
||||||
|
|
||||||
impl Database {
|
|
||||||
pub fn new(pool: PoolConnection) -> Self {
|
|
||||||
Self { pool }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a guild setting from the database
|
|
||||||
pub async fn get_guild_setting<T: 'static>(
|
|
||||||
&self,
|
|
||||||
guild_id: u64,
|
|
||||||
key: String,
|
|
||||||
) -> DatabaseResult<Option<T>>
|
|
||||||
where
|
|
||||||
T: FromStr,
|
|
||||||
{
|
|
||||||
use guild_settings::dsl;
|
|
||||||
log::debug!("Retrieving setting '{}' for guild {}", key, guild_id);
|
|
||||||
|
|
||||||
let entries: Vec<GuildSetting> = dsl::guild_settings
|
|
||||||
.filter(dsl::guild_id.eq(guild_id as i64))
|
|
||||||
.filter(dsl::key.eq(key))
|
|
||||||
.load_async::<GuildSetting>(&self.pool)
|
|
||||||
.await?;
|
|
||||||
log::trace!("Result is {:?}", entries);
|
|
||||||
|
|
||||||
if let Some(first) = entries.first() {
|
|
||||||
if any::TypeId::of::<T>() == any::TypeId::of::<bool>() {
|
|
||||||
Ok(first
|
|
||||||
.value
|
|
||||||
.clone()
|
|
||||||
.unwrap_or("false".to_string())
|
|
||||||
.parse::<T>()
|
|
||||||
.ok())
|
|
||||||
} else {
|
|
||||||
Ok(first.value.clone().and_then(|v| v.parse::<T>().ok()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Upserting a guild setting
|
|
||||||
pub async fn set_guild_setting<T>(
|
|
||||||
&self,
|
|
||||||
guild_id: u64,
|
|
||||||
key: String,
|
|
||||||
value: T,
|
|
||||||
) -> DatabaseResult<()>
|
|
||||||
where
|
|
||||||
T: ToString + Debug,
|
|
||||||
{
|
|
||||||
use guild_settings::dsl;
|
|
||||||
log::debug!("Setting '{}' to '{:?}' for guild {}", key, value, guild_id);
|
|
||||||
|
|
||||||
insert_into(dsl::guild_settings)
|
|
||||||
.values(GuildSettingInsert {
|
|
||||||
guild_id: guild_id as i64,
|
|
||||||
key: key.to_string(),
|
|
||||||
value: value.to_string(),
|
|
||||||
})
|
|
||||||
.on_conflict((dsl::guild_id, dsl::key))
|
|
||||||
.do_update()
|
|
||||||
.set(dsl::value.eq(value.to_string()))
|
|
||||||
.execute_async(&self.pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes a guild setting
|
|
||||||
pub async fn delete_guild_setting(&self, guild_id: u64, key: String) -> DatabaseResult<()> {
|
|
||||||
use guild_settings::dsl;
|
|
||||||
delete(dsl::guild_settings)
|
|
||||||
.filter(dsl::guild_id.eq(guild_id as i64))
|
|
||||||
.filter(dsl::key.eq(key))
|
|
||||||
.execute_async(&self.pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a list of all guild playlists
|
|
||||||
pub async fn get_guild_playlists(&self, guild_id: u64) -> DatabaseResult<Vec<GuildPlaylist>> {
|
|
||||||
use guild_playlists::dsl;
|
|
||||||
log::debug!("Retrieving guild playlists for guild {}", guild_id);
|
|
||||||
|
|
||||||
let playlists: Vec<GuildPlaylist> = dsl::guild_playlists
|
|
||||||
.filter(dsl::guild_id.eq(guild_id as i64))
|
|
||||||
.load_async::<GuildPlaylist>(&self.pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(playlists)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a guild playlist by name
|
|
||||||
pub async fn get_guild_playlist(
|
|
||||||
&self,
|
|
||||||
guild_id: u64,
|
|
||||||
name: String,
|
|
||||||
) -> DatabaseResult<Option<GuildPlaylist>> {
|
|
||||||
use guild_playlists::dsl;
|
|
||||||
log::debug!("Retriving guild playlist '{}' for guild {}", name, guild_id);
|
|
||||||
|
|
||||||
let playlists: Vec<GuildPlaylist> = dsl::guild_playlists
|
|
||||||
.filter(dsl::guild_id.eq(guild_id as i64))
|
|
||||||
.filter(dsl::name.eq(name))
|
|
||||||
.load_async::<GuildPlaylist>(&self.pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(playlists.into_iter().next())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new playlist to the database overwriting the old one
|
|
||||||
pub async fn add_guild_playlist(
|
|
||||||
&self,
|
|
||||||
guild_id: u64,
|
|
||||||
name: String,
|
|
||||||
url: String,
|
|
||||||
) -> DatabaseResult<()> {
|
|
||||||
use guild_playlists::dsl;
|
|
||||||
log::debug!("Inserting guild playlist '{}' for guild {}", name, guild_id);
|
|
||||||
|
|
||||||
insert_into(dsl::guild_playlists)
|
|
||||||
.values(GuildPlaylistInsert {
|
|
||||||
guild_id: guild_id as i64,
|
|
||||||
name: name.clone(),
|
|
||||||
url: url.clone(),
|
|
||||||
})
|
|
||||||
.on_conflict((dsl::guild_id, dsl::name))
|
|
||||||
.do_update()
|
|
||||||
.set(dsl::url.eq(url))
|
|
||||||
.execute_async(&self.pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub type DatabaseResult<T> = Result<T, DatabaseError>;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum DatabaseError {
|
|
||||||
#[error("DotEnv Error: {0}")]
|
|
||||||
DotEnv(#[from] dotenv::Error),
|
|
||||||
|
|
||||||
#[error("Connection Error: {0}")]
|
|
||||||
ConnectionError(#[from] diesel::prelude::ConnectionError),
|
|
||||||
|
|
||||||
#[error("Pool Connection Error: {0}")]
|
|
||||||
PoolConnectionError(#[from] r2d2::Error),
|
|
||||||
|
|
||||||
#[error("Migration Error: {0}")]
|
|
||||||
MigrationError(#[from] diesel_migrations::RunMigrationsError),
|
|
||||||
|
|
||||||
#[error("Result Error: {0}")]
|
|
||||||
ResultError(#[from] diesel::result::Error),
|
|
||||||
|
|
||||||
#[error("AsyncError: {0}")]
|
|
||||||
AsyncError(#[from] tokio_diesel::AsyncError),
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate diesel;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate diesel_migrations;
|
|
||||||
|
|
||||||
use crate::error::DatabaseResult;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use diesel::r2d2::{ConnectionManager, Pool};
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
pub mod database;
|
|
||||||
pub mod error;
|
|
||||||
pub mod models;
|
|
||||||
pub mod schema;
|
|
||||||
|
|
||||||
pub use database::Database;
|
|
||||||
|
|
||||||
type PoolConnection = Pool<ConnectionManager<PgConnection>>;
|
|
||||||
|
|
||||||
embed_migrations!("../database/migrations");
|
|
||||||
|
|
||||||
fn get_connection() -> DatabaseResult<PoolConnection> {
|
|
||||||
dotenv::dotenv()?;
|
|
||||||
let database_url = env::var("DATABASE_URL").expect("No DATABASE_URL in path");
|
|
||||||
log::debug!("Establishing database connection...");
|
|
||||||
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
|
||||||
let pool = Pool::builder().max_size(16).build(manager)?;
|
|
||||||
let connection = pool.get()?;
|
|
||||||
log::debug!("Running migrations...");
|
|
||||||
embedded_migrations::run(&connection)?;
|
|
||||||
log::debug!("Migrations finished");
|
|
||||||
log::info!("Database connection initialized");
|
|
||||||
|
|
||||||
Ok(pool)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_database() -> DatabaseResult<Database> {
|
|
||||||
let conn = get_connection()?;
|
|
||||||
Ok(Database::new(conn))
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
use crate::schema::*;
|
|
||||||
|
|
||||||
#[derive(Queryable, Debug)]
|
|
||||||
pub struct GuildSetting {
|
|
||||||
pub guild_id: i64,
|
|
||||||
pub key: String,
|
|
||||||
pub value: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable, Debug)]
|
|
||||||
#[table_name = "guild_settings"]
|
|
||||||
pub struct GuildSettingInsert {
|
|
||||||
pub guild_id: i64,
|
|
||||||
pub key: String,
|
|
||||||
pub value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Queryable, Debug)]
|
|
||||||
pub struct GuildPlaylist {
|
|
||||||
pub guild_id: i64,
|
|
||||||
pub name: String,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable, Debug)]
|
|
||||||
#[table_name = "guild_playlists"]
|
|
||||||
pub struct GuildPlaylistInsert {
|
|
||||||
pub guild_id: i64,
|
|
||||||
pub name: String,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
table! {
|
|
||||||
guild_playlists (guild_id, name) {
|
|
||||||
guild_id -> Int8,
|
|
||||||
name -> Varchar,
|
|
||||||
url -> Varchar,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table! {
|
|
||||||
guild_settings (guild_id, key) {
|
|
||||||
guild_id -> Int8,
|
|
||||||
key -> Varchar,
|
|
||||||
value -> Nullable<Varchar>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
|
||||||
guild_playlists,
|
|
||||||
guild_settings,
|
|
||||||
);
|
|
@ -1,5 +1,5 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly"
|
channel = "stable"
|
||||||
targets = [
|
targets = [
|
||||||
"x86_64-unknown-linux-gnu",
|
"x86_64-unknown-linux-gnu",
|
||||||
]
|
]
|
@ -0,0 +1,25 @@
|
|||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::CommandResult;
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Displays information about the bot")]
|
||||||
|
#[bucket("general")]
|
||||||
|
async fn about(ctx: &Context, msg: &Message) -> CommandResult {
|
||||||
|
msg.channel_id
|
||||||
|
.send_message(ctx, |m| {
|
||||||
|
m.embed(|e| {
|
||||||
|
e.title("About").description(format!(
|
||||||
|
"\
|
||||||
|
I'm a general purpose discord bot written in rusty Rust. \
|
||||||
|
My main focus is providing information about all kinds of stuff and playing music.\
|
||||||
|
Use `{}help` to get an overview of the commands I provide.",
|
||||||
|
std::env::var("BOT_PREFIX").unwrap()
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
use crate::utils::context_data::get_database_from_context;
|
||||||
|
use bot_coreutils::url;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::{Args, CommandResult};
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
use serenity_additions::core::SHORT_TIMEOUT;
|
||||||
|
use serenity_additions::ephemeral_message::EphemeralMessage;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Adds media to the database")]
|
||||||
|
#[usage("<url> [<category>] [<name>]")]
|
||||||
|
#[bucket("general")]
|
||||||
|
#[aliases("add_gif", "add-gif", "addgif", "add-media", "addmedia")]
|
||||||
|
#[min_args(1)]
|
||||||
|
#[max_args(3)]
|
||||||
|
#[owners_only]
|
||||||
|
async fn add_media(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let url = args.single::<String>()?;
|
||||||
|
|
||||||
|
if !url::is_valid(&url) {
|
||||||
|
msg.reply(ctx, "Invalid url").await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let category = args.single_quoted::<String>().ok();
|
||||||
|
let name = args.single_quoted::<String>().ok();
|
||||||
|
let database = get_database_from_context(&ctx).await;
|
||||||
|
|
||||||
|
database.add_media(url, category, name).await?;
|
||||||
|
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |c| {
|
||||||
|
c.reference_message(msg)
|
||||||
|
.content("Media entry added to the database.")
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
use futures::future::BoxFuture;
|
||||||
|
use futures::FutureExt;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::{Args, CommandResult};
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
use serenity::Result as SerenityResult;
|
||||||
|
use serenity_additions::core::SHORT_TIMEOUT;
|
||||||
|
use serenity_additions::ephemeral_message::EphemeralMessage;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Clears the chat (maximum 100 messages)")]
|
||||||
|
#[usage("[<number>]")]
|
||||||
|
#[example("20")]
|
||||||
|
#[min_args(0)]
|
||||||
|
#[max_args(1)]
|
||||||
|
#[bucket("general")]
|
||||||
|
#[required_permissions("MANAGE_MESSAGES")]
|
||||||
|
async fn clear(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let limit = args.single::<u64>().unwrap_or(20);
|
||||||
|
tracing::debug!("Deleting messages for channel {}", msg.channel_id);
|
||||||
|
let messages = msg.channel_id.messages(ctx, |b| b.limit(limit)).await?;
|
||||||
|
tracing::debug!("Deleting {} messages", messages.len());
|
||||||
|
let futures: Vec<BoxFuture<SerenityResult<()>>> = messages
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| async move { ctx.http.delete_message(m.channel_id.0, m.id.0).await }.boxed())
|
||||||
|
.collect();
|
||||||
|
tracing::debug!("Waiting for all messages to be deleted");
|
||||||
|
let deleted = futures::future::join_all(futures).await;
|
||||||
|
let deleted_count = deleted.into_iter().filter(|d| d.is_ok()).count();
|
||||||
|
tracing::debug!("{} Messages deleted", deleted_count);
|
||||||
|
|
||||||
|
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |f| {
|
||||||
|
f.content(format!("Deleted {} messages", deleted_count))
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::{Args, CommandResult};
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Fuck this person in particular")]
|
||||||
|
#[usage("<person> [<amount>] [<verbosity>]")]
|
||||||
|
#[min_args(1)]
|
||||||
|
#[max_args(3)]
|
||||||
|
#[bucket("general")]
|
||||||
|
#[aliases("frick", "fock")]
|
||||||
|
async fn fuck(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let mut amount = args.single::<usize>().unwrap_or(3);
|
||||||
|
if amount > 3 {
|
||||||
|
msg.reply(&ctx.http, "Don't you think that's a bit much?")
|
||||||
|
.await?;
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
amount = 3;
|
||||||
|
} else {
|
||||||
|
msg.reply(&ctx.http, "no").await?;
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
let mut verbosity = args.single::<usize>().unwrap_or(1);
|
||||||
|
if verbosity == 0 {
|
||||||
|
verbosity = 1
|
||||||
|
}
|
||||||
|
let fuck_word = match verbosity {
|
||||||
|
1 => "frick",
|
||||||
|
2 => "flock",
|
||||||
|
3 => "fock",
|
||||||
|
4 => "fck",
|
||||||
|
_ => "fuck",
|
||||||
|
};
|
||||||
|
|
||||||
|
for _ in 0..amount {
|
||||||
|
msg.channel_id
|
||||||
|
.say(&ctx, format!("{} <@{}>", fuck_word, msg.author.id))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
use crate::messages::inspirobot::create_inspirobot_menu;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::CommandResult;
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Get an inspiring quote")]
|
||||||
|
#[usage("")]
|
||||||
|
#[aliases("inspireme", "inspire-me", "inspiro")]
|
||||||
|
#[bucket("general")]
|
||||||
|
async fn inspirobot(ctx: &Context, msg: &Message) -> CommandResult {
|
||||||
|
create_inspirobot_menu(ctx, msg.channel_id, msg.author.id).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
use crate::messages::gifs::create_media_menu;
|
||||||
|
use crate::utils::context_data::get_database_from_context;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::CommandResult;
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Displays a list of all gifs used by the bot")]
|
||||||
|
#[bucket("general")]
|
||||||
|
#[only_in(guilds)]
|
||||||
|
async fn media(ctx: &Context, msg: &Message) -> CommandResult {
|
||||||
|
let database = get_database_from_context(ctx).await;
|
||||||
|
let gifs = database.get_all_media().await?;
|
||||||
|
create_media_menu(ctx, msg.channel_id, gifs, msg.author.id).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,22 +1,41 @@
|
|||||||
use serenity::framework::standard::macros::group;
|
use serenity::framework::standard::macros::group;
|
||||||
|
|
||||||
use pekofy::PEKOFY_COMMAND;
|
use about::ABOUT_COMMAND;
|
||||||
|
use add_media::ADD_MEDIA_COMMAND;
|
||||||
|
use clear::CLEAR_COMMAND;
|
||||||
|
use fuck::FUCK_COMMAND;
|
||||||
|
use inspirobot::INSPIROBOT_COMMAND;
|
||||||
|
use media::MEDIA_COMMAND;
|
||||||
|
use pain::PAIN_COMMAND;
|
||||||
|
use party::PARTY_COMMAND;
|
||||||
use ping::PING_COMMAND;
|
use ping::PING_COMMAND;
|
||||||
use qalc::QALC_COMMAND;
|
use qalc::QALC_COMMAND;
|
||||||
use shutdown::SHUTDOWN_COMMAND;
|
use shutdown::SHUTDOWN_COMMAND;
|
||||||
use stats::STATS_COMMAND;
|
use stats::STATS_COMMAND;
|
||||||
use time::TIME_COMMAND;
|
use time::TIME_COMMAND;
|
||||||
use timezones::TIMEZONES_COMMAND;
|
use timezones::TIMEZONES_COMMAND;
|
||||||
|
use xkcd::XKCD_COMMAND;
|
||||||
|
|
||||||
|
mod about;
|
||||||
|
mod add_media;
|
||||||
|
mod clear;
|
||||||
|
mod fuck;
|
||||||
pub(crate) mod help;
|
pub(crate) mod help;
|
||||||
mod pekofy;
|
mod inspirobot;
|
||||||
|
mod media;
|
||||||
|
mod pain;
|
||||||
|
mod party;
|
||||||
mod ping;
|
mod ping;
|
||||||
mod qalc;
|
mod qalc;
|
||||||
mod shutdown;
|
mod shutdown;
|
||||||
mod stats;
|
mod stats;
|
||||||
mod time;
|
mod time;
|
||||||
mod timezones;
|
mod timezones;
|
||||||
|
mod xkcd;
|
||||||
|
|
||||||
#[group]
|
#[group]
|
||||||
#[commands(ping, stats, shutdown, pekofy, time, timezones, qalc)]
|
#[commands(
|
||||||
|
ping, stats, shutdown, time, timezones, qalc, about, add_media, media, pain, clear, xkcd, fuck,
|
||||||
|
party, inspirobot
|
||||||
|
)]
|
||||||
pub struct Misc;
|
pub struct Misc;
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
use crate::utils::context_data::get_database_from_context;
|
||||||
|
use crate::utils::error::BotError;
|
||||||
|
use rand::prelude::IteratorRandom;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::{Args, CommandResult};
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
|
||||||
|
static CATEGORY_PREFIX: &str = "pain-";
|
||||||
|
static NOT_FOUND_PAIN: &str = "404";
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Various types of pain (pain-peko)")]
|
||||||
|
#[usage("<pain-type>")]
|
||||||
|
#[example("peko")]
|
||||||
|
#[min_args(1)]
|
||||||
|
#[max_args(1)]
|
||||||
|
#[bucket("general")]
|
||||||
|
async fn pain(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
|
tracing::debug!("Got pain command");
|
||||||
|
let pain_type = args.message().to_lowercase();
|
||||||
|
let database = get_database_from_context(ctx).await;
|
||||||
|
let mut media = database
|
||||||
|
.get_media_by_category(format!("{}{}", CATEGORY_PREFIX, pain_type))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if media.is_empty() {
|
||||||
|
tracing::debug!("No media found for pain {}. Using 404", pain_type);
|
||||||
|
media = database
|
||||||
|
.get_media_by_category(format!("{}{}", CATEGORY_PREFIX, NOT_FOUND_PAIN))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = media
|
||||||
|
.into_iter()
|
||||||
|
.choose(&mut rand::thread_rng())
|
||||||
|
.ok_or(BotError::from("No gifs found."))?;
|
||||||
|
tracing::trace!("Gif for pain is {:?}", entry);
|
||||||
|
msg.reply(ctx, entry.url).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
use crate::utils::context_data::get_database_from_context;
|
||||||
|
use crate::utils::error::BotError;
|
||||||
|
use bot_database::models::Media;
|
||||||
|
use rand::prelude::SliceRandom;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::{Args, CommandResult};
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Party command")]
|
||||||
|
#[max_args(1)]
|
||||||
|
#[usage("(<amount>)")]
|
||||||
|
#[bucket("general")]
|
||||||
|
async fn party(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let mut amount = args.single::<u32>().unwrap_or(1);
|
||||||
|
if amount > 5 {
|
||||||
|
amount = 5;
|
||||||
|
}
|
||||||
|
if amount == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let database = get_database_from_context(ctx).await;
|
||||||
|
let mut media = database.get_media_by_category("party").await?;
|
||||||
|
media.shuffle(&mut rand::thread_rng());
|
||||||
|
let mut chosen_gifs = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..amount {
|
||||||
|
chosen_gifs.push(media.pop());
|
||||||
|
}
|
||||||
|
let chosen_gifs: Vec<Media> = chosen_gifs.into_iter().filter_map(|g| g).collect();
|
||||||
|
if chosen_gifs.is_empty() {
|
||||||
|
return Err(BotError::from("No media found.").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
for gif in chosen_gifs {
|
||||||
|
msg.channel_id
|
||||||
|
.send_message(&ctx.http, |m| m.content(gif.url))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
use crate::messages::xkcd::create_xkcd_menu;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::{Args, CommandResult};
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
use xkcd_search::get_comic;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Retrieves xkcd comics")]
|
||||||
|
#[usage("[(<id>|<query..>)]")]
|
||||||
|
#[bucket("general")]
|
||||||
|
async fn xkcd(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let comics = if let Ok(id) = args.single::<u32>() {
|
||||||
|
if let Ok(comic) = xkcd_search::get_comic(id).await {
|
||||||
|
vec![comic]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
} else if !args.is_empty() {
|
||||||
|
let query = args.message();
|
||||||
|
let results = xkcd_search::search(query).await?;
|
||||||
|
let comics =
|
||||||
|
futures::future::join_all(results.into_iter().map(|(_, id)| get_comic(id))).await;
|
||||||
|
comics
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|result| result.ok())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![xkcd_search::get_latest_comic().await?]
|
||||||
|
};
|
||||||
|
|
||||||
|
create_xkcd_menu(ctx, msg.channel_id, comics, msg.author.id).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
use crate::commands::common::handle_autodelete;
|
||||||
|
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
|
||||||
|
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::macros::command;
|
||||||
|
use serenity::framework::standard::{Args, CommandError, CommandResult};
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
use serenity_additions::core::SHORT_TIMEOUT;
|
||||||
|
use serenity_additions::ephemeral_message::EphemeralMessage;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Removes a song from the queue")]
|
||||||
|
#[usage("<pos>")]
|
||||||
|
#[example("102")]
|
||||||
|
#[num_args(1)]
|
||||||
|
#[bucket("general")]
|
||||||
|
#[only_in(guilds)]
|
||||||
|
#[aliases("rms", "removesong", "remove-song")]
|
||||||
|
#[checks(DJ)]
|
||||||
|
async fn remove_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let guild = msg.guild(&ctx.cache).unwrap();
|
||||||
|
tracing::debug!("Moving song for guild {}", guild.id);
|
||||||
|
|
||||||
|
let pos = args.single::<usize>()?;
|
||||||
|
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
|
||||||
|
player
|
||||||
|
} else {
|
||||||
|
return create_no_voicechannel_message(&ctx.http, msg.channel_id)
|
||||||
|
.await
|
||||||
|
.map_err(CommandError::from);
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut player = player.lock().await;
|
||||||
|
player.queue().remove(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
|
||||||
|
m.content(format!("🗑️ Removed Song at `{}`", pos))
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
handle_autodelete(ctx, msg).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue