Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
trivernis | 2c02320d83 | 4 years ago |
trivernis | 314e305bba | 4 years ago |
trivernis | 6a56c02712 | 4 years ago |
trivernis | 1550cebdf7 | 4 years ago |
@ -1,56 +0,0 @@
|
|||||||
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
|
|
@ -1,39 +0,0 @@
|
|||||||
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]
|
|
@ -1,20 +0,0 @@
|
|||||||
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,45 +0,0 @@
|
|||||||
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,24 @@
|
|||||||
|
# syntax=docker/dockerfile:1.0-experimental
|
||||||
|
FROM rust:latest AS builder
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y build-essential libssl-dev libopus-dev libpq-dev
|
||||||
|
WORKDIR /usr/src
|
||||||
|
RUN USER=root 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
|
||||||
|
COPY bot-serenityutils ./bot-serenityutils
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=target \
|
||||||
|
cargo build --release
|
||||||
|
RUN mkdir /tmp/tobi
|
||||||
|
RUN --mount=type=cache,target=target cp target/release/tobi-rs /tmp/tobi/
|
||||||
|
|
||||||
|
FROM bitnami/minideb:latest
|
||||||
|
RUN install_packages openssl libopus0 ffmpeg python3 python3-pip libpq5
|
||||||
|
RUN pip3 install youtube-dl
|
||||||
|
RUN rm -rf /var/lib/{apt,dpkg,cache,log}/
|
||||||
|
COPY --from=builder /tmp/tobi/tobi-rs .
|
||||||
|
ENTRYPOINT ["/tobi-rs"]
|
@ -0,0 +1,314 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bot-coreutils"
|
||||||
|
version = "0.1.1"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"mime_guess",
|
||||||
|
"rand",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matches"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.7.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow",
|
||||||
|
"ntapi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miow"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
"rand_hc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_hc"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"memchr",
|
||||||
|
"mio",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"matches",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
|
[[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"
|
@ -0,0 +1,467 @@
|
|||||||
|
# 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 = "bot-database"
|
||||||
|
version = "0.4.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"diesel",
|
||||||
|
"diesel_migrations",
|
||||||
|
"dotenv",
|
||||||
|
"log",
|
||||||
|
"r2d2",
|
||||||
|
"thiserror",
|
||||||
|
"tokio-diesel",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "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"
|
@ -0,0 +1,5 @@
|
|||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
@ -1,13 +0,0 @@
|
|||||||
[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"] }
|
|
@ -1,37 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
@ -1,16 +0,0 @@
|
|||||||
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),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,287 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
use migration::Migrator;
|
|
||||||
use sea_orm_migration::prelude::*;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
cli::run_cli(Migrator).await;
|
|
||||||
}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
-- 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();
|
@ -0,0 +1,36 @@
|
|||||||
|
-- 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;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE guild_settings;
|
@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE guild_settings (
|
||||||
|
guild_id BIGINT NOT NULL,
|
||||||
|
key VARCHAR(255) NOT NULL,
|
||||||
|
value VARCHAR(1024),
|
||||||
|
PRIMARY KEY (guild_id, key)
|
||||||
|
);
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE guild_playlists;
|
@ -0,0 +1,7 @@
|
|||||||
|
-- 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)
|
||||||
|
)
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE gifs;
|
@ -0,0 +1,8 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE gifs (
|
||||||
|
id BIGSERIAL PRIMARY KEY ,
|
||||||
|
category VARCHAR(128),
|
||||||
|
name VARCHAR(128),
|
||||||
|
url VARCHAR(128) NOT NULL,
|
||||||
|
UNIQUE (category, name)
|
||||||
|
)
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE statistics;
|
@ -0,0 +1,9 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE statistics (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
version VARCHAR(32) NOT NULL,
|
||||||
|
command VARCHAR(255) NOT NULL,
|
||||||
|
executed_at TIMESTAMP NOT NULL,
|
||||||
|
success BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
error_msg TEXT
|
||||||
|
)
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE youtube_songs;
|
@ -0,0 +1,11 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE youtube_songs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
spotify_id VARCHAR(255) NOT NULL,
|
||||||
|
artist VARCHAR(128) NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
album VARCHAR(255) NOT NULL,
|
||||||
|
url VARCHAR(128) NOT NULL,
|
||||||
|
score INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
UNIQUE (spotify_id, url)
|
||||||
|
)
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE ephemeral_messages;
|
@ -0,0 +1,7 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE ephemeral_messages (
|
||||||
|
channel_id BIGINT NOT NULL,
|
||||||
|
message_id BIGINT NOT NULL,
|
||||||
|
timeout TIMESTAMP NOT NULL,
|
||||||
|
PRIMARY KEY (channel_id, message_id)
|
||||||
|
)
|
@ -0,0 +1,57 @@
|
|||||||
|
use diesel::insert_into;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use tokio_diesel::*;
|
||||||
|
|
||||||
|
use crate::error::DatabaseResult;
|
||||||
|
use crate::models::*;
|
||||||
|
use crate::schema::*;
|
||||||
|
use crate::Database;
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
/// Returns a list of all gifs in the database
|
||||||
|
pub async fn get_all_gifs(&self) -> DatabaseResult<Vec<Gif>> {
|
||||||
|
use gifs::dsl;
|
||||||
|
log::debug!("Loading all gifs from the database");
|
||||||
|
|
||||||
|
let gifs: Vec<Gif> = dsl::gifs.load_async::<Gif>(&self.pool).await?;
|
||||||
|
Ok(gifs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of gifs by assigned category
|
||||||
|
pub async fn get_gifs_by_category(&self, category: &str) -> DatabaseResult<Vec<Gif>> {
|
||||||
|
use gifs::dsl;
|
||||||
|
log::debug!("Searching for gifs in category '{}'", category);
|
||||||
|
|
||||||
|
let gifs: Vec<Gif> = dsl::gifs
|
||||||
|
.filter(dsl::category.eq(category))
|
||||||
|
.load_async::<Gif>(&self.pool)
|
||||||
|
.await?;
|
||||||
|
Ok(gifs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a gif to the database
|
||||||
|
pub async fn add_gif(
|
||||||
|
&self,
|
||||||
|
url: &str,
|
||||||
|
category: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
) -> DatabaseResult<()> {
|
||||||
|
use gifs::dsl;
|
||||||
|
log::debug!(
|
||||||
|
"Inserting gif with url '{}' and name {:?} and category {:?}",
|
||||||
|
url,
|
||||||
|
name,
|
||||||
|
category
|
||||||
|
);
|
||||||
|
insert_into(dsl::gifs)
|
||||||
|
.values(GifInsert {
|
||||||
|
url: url.to_string(),
|
||||||
|
name,
|
||||||
|
category,
|
||||||
|
})
|
||||||
|
.execute_async(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +0,0 @@
|
|||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +1,30 @@
|
|||||||
pub use ephemeral_messages::*;
|
pub use ephemeral_messages::*;
|
||||||
|
pub use gifs::*;
|
||||||
pub use guild_playlists::*;
|
pub use guild_playlists::*;
|
||||||
pub use guild_playlists::*;
|
pub use guild_playlists::*;
|
||||||
pub use media::*;
|
|
||||||
use sea_orm::DatabaseConnection;
|
|
||||||
pub use statistics::*;
|
pub use statistics::*;
|
||||||
pub use youtube_songs::*;
|
pub use youtube_songs::*;
|
||||||
|
|
||||||
|
use crate::PoolConnection;
|
||||||
|
|
||||||
mod ephemeral_messages;
|
mod ephemeral_messages;
|
||||||
|
mod gifs;
|
||||||
mod guild_playlists;
|
mod guild_playlists;
|
||||||
mod guild_settings;
|
mod guild_settings;
|
||||||
mod media;
|
|
||||||
mod statistics;
|
mod statistics;
|
||||||
mod youtube_songs;
|
mod youtube_songs;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BotDatabase {
|
pub struct Database {
|
||||||
db: DatabaseConnection,
|
pool: PoolConnection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BotDatabase {
|
unsafe impl Send for Database {}
|
||||||
pub fn new(db: DatabaseConnection) -> Self {
|
|
||||||
Self { db }
|
unsafe impl Sync for Database {}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
pub fn new(pool: PoolConnection) -> Self {
|
||||||
|
Self { pool }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,50 @@
|
|||||||
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;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
#[derive(FromQueryResult)]
|
use diesel::dsl::count;
|
||||||
struct CommandCount {
|
use diesel::insert_into;
|
||||||
count: i64,
|
use diesel::prelude::*;
|
||||||
}
|
use tokio_diesel::*;
|
||||||
|
|
||||||
impl super::BotDatabase {
|
use crate::error::DatabaseResult;
|
||||||
|
use crate::models::*;
|
||||||
|
use crate::schema::*;
|
||||||
|
use crate::Database;
|
||||||
|
|
||||||
|
impl Database {
|
||||||
/// Adds a command statistic to the database
|
/// Adds a command statistic to the database
|
||||||
#[tracing::instrument(level = "debug", skip(self))]
|
|
||||||
pub async fn add_statistic(
|
pub async fn add_statistic(
|
||||||
&self,
|
&self,
|
||||||
version: String,
|
version: &str,
|
||||||
command: String,
|
command: &str,
|
||||||
executed_at: SystemTime,
|
executed_at: SystemTime,
|
||||||
success: bool,
|
success: bool,
|
||||||
error_msg: Option<String>,
|
error_msg: Option<String>,
|
||||||
) -> DatabaseResult<()> {
|
) -> DatabaseResult<()> {
|
||||||
let model = statistics::ActiveModel {
|
use statistics::dsl;
|
||||||
version: Set(version),
|
log::trace!("Adding statistic to database");
|
||||||
command: Set(command),
|
insert_into(dsl::statistics)
|
||||||
executed_at: Set(DateTimeLocal::from(executed_at).into()),
|
.values(StatisticInsert {
|
||||||
success: Set(success),
|
version: version.to_string(),
|
||||||
error_msg: Set(error_msg),
|
command: command.to_string(),
|
||||||
..Default::default()
|
executed_at,
|
||||||
};
|
success,
|
||||||
model.insert(&self.db).await?;
|
error_msg,
|
||||||
|
})
|
||||||
|
.execute_async(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the total number of commands executed
|
/// Returns the total number of commands executed
|
||||||
#[tracing::instrument(level = "debug", skip(self))]
|
|
||||||
pub async fn get_total_commands_statistic(&self) -> DatabaseResult<u64> {
|
pub async fn get_total_commands_statistic(&self) -> DatabaseResult<u64> {
|
||||||
let total_count: Option<CommandCount> = statistics::Entity::find()
|
use statistics::dsl;
|
||||||
.select_only()
|
log::trace!("Querying total number of commands");
|
||||||
.column_as(statistics::Column::Id.count(), "count")
|
let total_count: i64 = dsl::statistics
|
||||||
.into_model::<CommandCount>()
|
.select(count(dsl::id))
|
||||||
.one(&self.db)
|
.first_async::<i64>(&self.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(total_count.unwrap().count as u64)
|
Ok(total_count as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
//! 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 {}
|
|
@ -1,24 +0,0 @@
|
|||||||
//! 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 {}
|
|
@ -1,24 +0,0 @@
|
|||||||
//! 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 {}
|
|
@ -1,24 +0,0 @@
|
|||||||
//! 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 {}
|
|
@ -1,10 +0,0 @@
|
|||||||
//! 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;
|
|
@ -1,8 +0,0 @@
|
|||||||
//! 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;
|
|
@ -1,27 +0,0 @@
|
|||||||
//! 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 {}
|
|
@ -1,27 +0,0 @@
|
|||||||
//! 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 {}
|
|
@ -1,31 +1,47 @@
|
|||||||
use crate::error::DatabaseResult;
|
#[macro_use]
|
||||||
|
extern crate diesel;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate diesel_migrations;
|
||||||
|
|
||||||
|
use crate::error::{DatabaseError, DatabaseResult};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::r2d2::{ConnectionManager, ManageConnection, Pool};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod entity;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
pub use database::BotDatabase as Database;
|
pub use database::Database;
|
||||||
use migration::MigratorTrait;
|
|
||||||
use sea_orm::{ConnectOptions, Database as SeaDatabase, DatabaseConnection};
|
type PoolConnection = Pool<ConnectionManager<PgConnection>>;
|
||||||
|
|
||||||
#[tracing::instrument]
|
embed_migrations!("../bot-database/migrations");
|
||||||
async fn get_connection() -> DatabaseResult<DatabaseConnection> {
|
|
||||||
|
fn get_connection() -> DatabaseResult<PoolConnection> {
|
||||||
let database_url = env::var("DATABASE_URL").expect("No DATABASE_URL in path");
|
let database_url = env::var("DATABASE_URL").expect("No DATABASE_URL in path");
|
||||||
tracing::debug!("Establishing database connection...");
|
log::debug!("Establishing database connection...");
|
||||||
let opt = ConnectOptions::new(database_url);
|
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
||||||
let db = SeaDatabase::connect(opt).await?;
|
log::trace!("Connecting...");
|
||||||
tracing::debug!("Running migrations...");
|
manager
|
||||||
migration::Migrator::up(&db, None).await?;
|
.connect()
|
||||||
tracing::debug!("Migrations finished");
|
.map_err(|e| DatabaseError::Msg(format!("{:?}", e)))?;
|
||||||
tracing::info!("Database connection initialized");
|
log::trace!("Creating pool...");
|
||||||
|
let pool = Pool::builder().max_size(16).build(manager)?;
|
||||||
Ok(db)
|
log::trace!("Getting one connection to run migrations...");
|
||||||
|
let connection = pool.get()?;
|
||||||
|
log::debug!("Running migrations...");
|
||||||
|
embedded_migrations::run(&connection)?;
|
||||||
|
log::debug!("Migrations finished");
|
||||||
|
log::info!("Database connection initialized");
|
||||||
|
|
||||||
|
Ok(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_database() -> DatabaseResult<Database> {
|
pub fn get_database() -> DatabaseResult<Database> {
|
||||||
let conn = get_connection().await?;
|
let conn = get_connection()?;
|
||||||
Ok(Database::new(conn))
|
Ok(Database::new(conn))
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,94 @@
|
|||||||
use super::entity;
|
use crate::schema::*;
|
||||||
|
use std::time::SystemTime;
|
||||||
pub use entity::ephemeral_messages::Model as EphemeralMessage;
|
|
||||||
pub use entity::guild_playlists::Model as GuildPlaylist;
|
#[derive(Queryable, Debug)]
|
||||||
pub use entity::guild_settings::Model as GuildSetting;
|
pub struct GuildSetting {
|
||||||
pub use entity::media::Model as Media;
|
pub guild_id: i64,
|
||||||
pub use entity::statistics::Model as Statistic;
|
pub key: String,
|
||||||
pub use entity::youtube_songs::Model as YoutubeSong;
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug, Clone)]
|
||||||
|
pub struct Gif {
|
||||||
|
pub id: i64,
|
||||||
|
pub category: Option<String>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, Debug)]
|
||||||
|
#[table_name = "gifs"]
|
||||||
|
pub struct GifInsert {
|
||||||
|
pub category: Option<String>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, Debug)]
|
||||||
|
#[table_name = "statistics"]
|
||||||
|
pub struct StatisticInsert {
|
||||||
|
pub version: String,
|
||||||
|
pub command: String,
|
||||||
|
pub executed_at: SystemTime,
|
||||||
|
pub success: bool,
|
||||||
|
pub error_msg: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug, Clone)]
|
||||||
|
pub struct YoutubeSong {
|
||||||
|
pub id: i64,
|
||||||
|
pub spotify_id: String,
|
||||||
|
pub artist: String,
|
||||||
|
pub title: String,
|
||||||
|
pub album: String,
|
||||||
|
pub url: String,
|
||||||
|
pub score: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, Debug)]
|
||||||
|
#[table_name = "youtube_songs"]
|
||||||
|
pub struct YoutubeSongInsert {
|
||||||
|
pub spotify_id: String,
|
||||||
|
pub artist: String,
|
||||||
|
pub title: String,
|
||||||
|
pub album: String,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug, Clone)]
|
||||||
|
pub struct EphemeralMessage {
|
||||||
|
pub channel_id: i64,
|
||||||
|
pub message_id: i64,
|
||||||
|
pub timeout: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, Debug)]
|
||||||
|
#[table_name = "ephemeral_messages"]
|
||||||
|
pub struct EphemeralMessageInsert {
|
||||||
|
pub channel_id: i64,
|
||||||
|
pub message_id: i64,
|
||||||
|
pub timeout: SystemTime,
|
||||||
|
}
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
table! {
|
||||||
|
ephemeral_messages (channel_id, message_id) {
|
||||||
|
channel_id -> Int8,
|
||||||
|
message_id -> Int8,
|
||||||
|
timeout -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
gifs (id) {
|
||||||
|
id -> Int8,
|
||||||
|
category -> Nullable<Varchar>,
|
||||||
|
name -> Nullable<Varchar>,
|
||||||
|
url -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
statistics (id) {
|
||||||
|
id -> Int8,
|
||||||
|
version -> Varchar,
|
||||||
|
command -> Varchar,
|
||||||
|
executed_at -> Timestamp,
|
||||||
|
success -> Bool,
|
||||||
|
error_msg -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
youtube_songs (id) {
|
||||||
|
id -> Int8,
|
||||||
|
spotify_id -> Varchar,
|
||||||
|
artist -> Varchar,
|
||||||
|
title -> Varchar,
|
||||||
|
album -> Varchar,
|
||||||
|
url -> Varchar,
|
||||||
|
score -> Int4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allow_tables_to_appear_in_same_query!(
|
||||||
|
ephemeral_messages,
|
||||||
|
gifs,
|
||||||
|
guild_playlists,
|
||||||
|
guild_settings,
|
||||||
|
statistics,
|
||||||
|
youtube_songs,
|
||||||
|
);
|
@ -0,0 +1 @@
|
|||||||
|
target
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "bot-serenityutils"
|
||||||
|
version = "0.2.3"
|
||||||
|
authors = ["trivernis <trivernis@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serenity = "0.10.5"
|
||||||
|
tokio = "1.4.0"
|
||||||
|
thiserror = "1.0.24"
|
||||||
|
log = "0.4.14"
|
||||||
|
futures = "0.3.14"
|
||||||
|
serde_json = "1.0.64"
|
@ -0,0 +1,44 @@
|
|||||||
|
use crate::error::SerenityUtilsResult;
|
||||||
|
use crate::menu::traits::EventDrivenMessage;
|
||||||
|
use serenity::http::Http;
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
use serenity::model::id::{ChannelId, MessageId};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub static SHORT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
pub static MEDIUM_TIMEOUT: Duration = Duration::from_secs(20);
|
||||||
|
pub static LONG_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
|
pub static EXTRA_LONG_TIMEOUT: Duration = Duration::from_secs(600);
|
||||||
|
|
||||||
|
pub type BoxedEventDrivenMessage = Box<dyn EventDrivenMessage>;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq, Hash)]
|
||||||
|
pub struct MessageHandle {
|
||||||
|
pub channel_id: u64,
|
||||||
|
pub message_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageHandle {
|
||||||
|
/// Creates a new message handle
|
||||||
|
pub fn new(channel_id: ChannelId, message_id: MessageId) -> Self {
|
||||||
|
Self {
|
||||||
|
message_id: message_id.0,
|
||||||
|
channel_id: channel_id.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new message handle from raw ids
|
||||||
|
pub fn from_raw_ids(channel_id: u64, message_id: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
message_id,
|
||||||
|
channel_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the message object of the handle
|
||||||
|
pub async fn get_message(&self, http: &Arc<Http>) -> SerenityUtilsResult<Message> {
|
||||||
|
let msg = http.get_message(self.channel_id, self.message_id).await?;
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
use crate::core::MessageHandle;
|
||||||
|
use crate::error::SerenityUtilsResult;
|
||||||
|
use serenity::builder::CreateMessage;
|
||||||
|
use serenity::http::Http;
|
||||||
|
use serenity::model::channel::Message;
|
||||||
|
use serenity::model::id::ChannelId;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub struct EphemeralMessage;
|
||||||
|
|
||||||
|
impl EphemeralMessage {
|
||||||
|
/// Ensures that an already existing message is
|
||||||
|
/// deleted after a certain amount of time
|
||||||
|
pub async fn create_from_message(
|
||||||
|
http: &Arc<Http>,
|
||||||
|
message: &Message,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Creating ephemeral message from existing message");
|
||||||
|
let handle = MessageHandle::new(message.channel_id, message.id);
|
||||||
|
let http = Arc::clone(&http);
|
||||||
|
|
||||||
|
log::debug!("Starting delete task");
|
||||||
|
tokio::spawn(async move {
|
||||||
|
log::debug!("Waiting for timeout to pass");
|
||||||
|
tokio::time::sleep(timeout).await;
|
||||||
|
log::debug!("Deleting ephemeral message");
|
||||||
|
if let Err(e) = http
|
||||||
|
.delete_message(handle.channel_id, handle.message_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
log::error!("Failed to delete ephemeral message {:?}: {}", handle, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new message that is deleted after a certain amount of time
|
||||||
|
pub async fn create<'a, F>(
|
||||||
|
http: &Arc<Http>,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
timeout: Duration,
|
||||||
|
f: F,
|
||||||
|
) -> SerenityUtilsResult<Message>
|
||||||
|
where
|
||||||
|
F: for<'b> FnOnce(&'b mut CreateMessage<'a>) -> &'b mut CreateMessage<'a>,
|
||||||
|
{
|
||||||
|
log::debug!("Creating new ephemeral message");
|
||||||
|
let msg = channel_id.send_message(http, f).await?;
|
||||||
|
Self::create_from_message(http, &msg, timeout).await?;
|
||||||
|
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type SerenityUtilsResult<T> = Result<T, SerenityUtilsError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum SerenityUtilsError {
|
||||||
|
#[error("Serenity Error: {0}")]
|
||||||
|
SerenityError(#[from] serenity::Error),
|
||||||
|
|
||||||
|
#[error("Page {0} not found")]
|
||||||
|
PageNotFound(usize),
|
||||||
|
|
||||||
|
#[error("Serenity Utils not fully initialized")]
|
||||||
|
Uninitialized,
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
Msg(String),
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
pub mod core;
|
||||||
|
pub mod ephemeral_message;
|
||||||
|
pub mod error;
|
||||||
|
pub mod macros;
|
||||||
|
pub mod menu;
|
||||||
|
|
||||||
|
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
@ -0,0 +1,16 @@
|
|||||||
|
/// Forwards the error directly to the user
|
||||||
|
/// without having to accept it in any handler.
|
||||||
|
/// Can only be used in async functions that return a Result.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! forward_error {
|
||||||
|
($ctx:expr,$channel_id:expr,$result:expr) => {
|
||||||
|
match $result {
|
||||||
|
Err(e) => {
|
||||||
|
use bot_serenityutils::{core::SHORT_TIMEOUT, ephemeral_message::EphemeralMessage};
|
||||||
|
$channel_id.say($ctx, format!("‼️ {}", e)).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Ok(v) => v,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
use crate::core::{BoxedEventDrivenMessage, MessageHandle};
|
||||||
|
use crate::error::{SerenityUtilsError, SerenityUtilsResult};
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::model::prelude::*;
|
||||||
|
use serenity::prelude::TypeMapKey;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
/// Container to store event driven messages in the serenity context data
|
||||||
|
pub struct EventDrivenMessageContainer;
|
||||||
|
pub type MessageRef = Arc<Mutex<BoxedEventDrivenMessage>>;
|
||||||
|
pub type EventDrivenMessagesRef = Arc<Mutex<HashMap<MessageHandle, MessageRef>>>;
|
||||||
|
|
||||||
|
impl TypeMapKey for EventDrivenMessageContainer {
|
||||||
|
type Value = EventDrivenMessagesRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
static UPDATE_INTERVAL_SECS: u64 = 5;
|
||||||
|
|
||||||
|
/// Starts the loop to handle message updates
|
||||||
|
pub async fn start_update_loop(ctx: &Context) {
|
||||||
|
let event_messages = get_listeners_from_context(ctx)
|
||||||
|
.await
|
||||||
|
.expect("Failed to get event message container");
|
||||||
|
let http = Arc::clone(&ctx.http);
|
||||||
|
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
log::trace!("Locking listener from update loop.");
|
||||||
|
let messages = {
|
||||||
|
let msgs_lock = event_messages.lock().await;
|
||||||
|
|
||||||
|
msgs_lock
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (*k, v.clone()))
|
||||||
|
.collect::<Vec<(MessageHandle, MessageRef)>>()
|
||||||
|
};
|
||||||
|
log::trace!("Listener locked.");
|
||||||
|
let mut frozen_messages = Vec::new();
|
||||||
|
|
||||||
|
for (key, msg) in messages {
|
||||||
|
let mut msg = msg.lock().await;
|
||||||
|
if let Err(e) = msg.update(&http).await {
|
||||||
|
log::error!("Failed to update message: {:?}", e);
|
||||||
|
}
|
||||||
|
if msg.is_frozen() {
|
||||||
|
frozen_messages.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut msgs_lock = event_messages.lock().await;
|
||||||
|
for key in frozen_messages {
|
||||||
|
msgs_lock.remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::trace!("Listener unlocked");
|
||||||
|
tokio::time::sleep(Duration::from_secs(UPDATE_INTERVAL_SECS)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// To be fired from the serenity handler when a message was deleted
|
||||||
|
pub async fn handle_message_delete(
|
||||||
|
ctx: &Context,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
message_id: MessageId,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
let mut affected_messages = Vec::new();
|
||||||
|
{
|
||||||
|
let listeners = get_listeners_from_context(ctx).await?;
|
||||||
|
log::trace!("Locking listener from handle_message_delete.");
|
||||||
|
let mut listeners_lock = listeners.lock().await;
|
||||||
|
log::trace!("Listener locked.");
|
||||||
|
|
||||||
|
let handle = MessageHandle::new(channel_id, message_id);
|
||||||
|
if let Some(msg) = listeners_lock.get(&handle) {
|
||||||
|
affected_messages.push(Arc::clone(msg));
|
||||||
|
listeners_lock.remove(&handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::trace!("Listener unlocked");
|
||||||
|
for msg in affected_messages {
|
||||||
|
let mut msg = msg.lock().await;
|
||||||
|
msg.on_deleted(ctx).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// To be fired from the serenity handler when multiple messages were deleted
|
||||||
|
pub async fn handle_message_delete_bulk(
|
||||||
|
ctx: &Context,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
message_ids: &Vec<MessageId>,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
let mut affected_messages = Vec::new();
|
||||||
|
{
|
||||||
|
let listeners = get_listeners_from_context(ctx).await?;
|
||||||
|
log::trace!("Locking listener from handle_message_delete_bulk.");
|
||||||
|
let mut listeners_lock = listeners.lock().await;
|
||||||
|
log::trace!("Listener locked.");
|
||||||
|
|
||||||
|
for message_id in message_ids {
|
||||||
|
let handle = MessageHandle::new(channel_id, *message_id);
|
||||||
|
if let Some(msg) = listeners_lock.get_mut(&handle) {
|
||||||
|
affected_messages.push(Arc::clone(msg));
|
||||||
|
listeners_lock.remove(&handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::trace!("Listener unlocked");
|
||||||
|
for msg in affected_messages {
|
||||||
|
let mut msg = msg.lock().await;
|
||||||
|
msg.on_deleted(ctx).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when a reaction was added to a message
|
||||||
|
pub async fn handle_reaction_add(ctx: &Context, reaction: &Reaction) -> SerenityUtilsResult<()> {
|
||||||
|
let mut affected_messages = Vec::new();
|
||||||
|
{
|
||||||
|
let listeners = get_listeners_from_context(ctx).await?;
|
||||||
|
log::trace!("Locking listener from handle_reaction_add.");
|
||||||
|
let mut listeners_lock = listeners.lock().await;
|
||||||
|
log::trace!("Listener locked.");
|
||||||
|
|
||||||
|
let handle = MessageHandle::new(reaction.channel_id, reaction.message_id);
|
||||||
|
|
||||||
|
if let Some(msg) = listeners_lock.get_mut(&handle) {
|
||||||
|
affected_messages.push(Arc::clone(&msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::trace!("Listener unlocked");
|
||||||
|
for msg in affected_messages {
|
||||||
|
let mut msg = msg.lock().await;
|
||||||
|
msg.on_reaction_add(ctx, reaction.clone()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when a reaction was added to a message
|
||||||
|
pub async fn handle_reaction_remove(ctx: &Context, reaction: &Reaction) -> SerenityUtilsResult<()> {
|
||||||
|
let mut affected_messages = Vec::new();
|
||||||
|
{
|
||||||
|
let listeners = get_listeners_from_context(ctx).await?;
|
||||||
|
log::trace!("Locking listener from handle_reaction_remove.");
|
||||||
|
let mut listeners_lock = listeners.lock().await;
|
||||||
|
log::trace!("Listener locked.");
|
||||||
|
|
||||||
|
let handle = MessageHandle::new(reaction.channel_id, reaction.message_id);
|
||||||
|
|
||||||
|
if let Some(msg) = listeners_lock.get_mut(&handle) {
|
||||||
|
affected_messages.push(Arc::clone(&msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::trace!("Listener unlocked");
|
||||||
|
for msg in affected_messages {
|
||||||
|
let mut msg = msg.lock().await;
|
||||||
|
msg.on_reaction_remove(ctx, reaction.clone()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_listeners_from_context(
|
||||||
|
ctx: &Context,
|
||||||
|
) -> SerenityUtilsResult<EventDrivenMessagesRef> {
|
||||||
|
let data = ctx.data.read().await;
|
||||||
|
let listeners = data
|
||||||
|
.get::<EventDrivenMessageContainer>()
|
||||||
|
.ok_or(SerenityUtilsError::Uninitialized)?;
|
||||||
|
log::trace!("Returning listener");
|
||||||
|
Ok(listeners.clone())
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
use crate::error::{SerenityUtilsError, SerenityUtilsResult};
|
||||||
|
use crate::menu::container::get_listeners_from_context;
|
||||||
|
use crate::menu::menu::Menu;
|
||||||
|
use crate::menu::typedata::HelpActiveContainer;
|
||||||
|
use crate::menu::ActionContainer;
|
||||||
|
use serde_json::json;
|
||||||
|
use serde_json::Value;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::http::CacheHttp;
|
||||||
|
use serenity::model::channel::Reaction;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
/// Shows the next page in the menu
|
||||||
|
pub async fn next_page(ctx: &Context, menu: &mut Menu<'_>, _: Reaction) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Showing next page");
|
||||||
|
menu.current_page = (menu.current_page + 1) % menu.pages.len();
|
||||||
|
display_page(ctx, menu).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows the previous page in the menu
|
||||||
|
pub async fn previous_page(
|
||||||
|
ctx: &Context,
|
||||||
|
menu: &mut Menu<'_>,
|
||||||
|
_: Reaction,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Showing previous page");
|
||||||
|
if menu.current_page == 0 {
|
||||||
|
menu.current_page = menu.pages.len() - 1;
|
||||||
|
} else {
|
||||||
|
menu.current_page = menu.current_page - 1;
|
||||||
|
}
|
||||||
|
display_page(ctx, menu).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows the previous page in the menu
|
||||||
|
pub async fn close_menu(
|
||||||
|
ctx: &Context,
|
||||||
|
menu: &mut Menu<'_>,
|
||||||
|
_: Reaction,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Closing menu");
|
||||||
|
menu.close(ctx.http()).await?;
|
||||||
|
let listeners = get_listeners_from_context(&ctx).await?;
|
||||||
|
let mut listeners_lock = listeners.lock().await;
|
||||||
|
let message = menu.message.read().await;
|
||||||
|
listeners_lock.remove(&*message);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn toggle_help(
|
||||||
|
ctx: &Context,
|
||||||
|
menu: &mut Menu<'_>,
|
||||||
|
_: Reaction,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Displaying help");
|
||||||
|
let show_help = menu
|
||||||
|
.data
|
||||||
|
.get::<HelpActiveContainer>()
|
||||||
|
.expect("Missing HelpActiveContainer in menu data")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
if show_help.load(Ordering::Relaxed) {
|
||||||
|
display_page(ctx, menu).await?;
|
||||||
|
show_help.store(false, Ordering::Relaxed);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let page = menu
|
||||||
|
.pages
|
||||||
|
.get(menu.current_page)
|
||||||
|
.ok_or(SerenityUtilsError::PageNotFound(menu.current_page))?
|
||||||
|
.get()
|
||||||
|
.await?;
|
||||||
|
let mut message = menu.get_message(ctx.http()).await?;
|
||||||
|
log::debug!("Building help entries");
|
||||||
|
let mut help_entries = menu
|
||||||
|
.help_entries
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(e, h)| Some((menu.controls.get(e)?, e, h)))
|
||||||
|
.collect::<Vec<(&ActionContainer, &String, &String)>>();
|
||||||
|
help_entries.sort_by_key(|(c, _, _)| c.position());
|
||||||
|
let help_message = help_entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, e, h)| format!(" - {} {}", e, h))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
log::trace!("Help message is {}", help_message);
|
||||||
|
|
||||||
|
message
|
||||||
|
.edit(ctx, |m| {
|
||||||
|
m.0.clone_from(&mut page.0.clone());
|
||||||
|
|
||||||
|
if let Some(embed) = m.0.get_mut("embed") {
|
||||||
|
let embed = embed.as_object_mut().unwrap();
|
||||||
|
let fields = embed
|
||||||
|
.entry("fields")
|
||||||
|
.or_insert_with(|| Value::Array(vec![]));
|
||||||
|
if let Value::Array(ref mut inner) = *fields {
|
||||||
|
inner.push(json!({
|
||||||
|
"inline": false,
|
||||||
|
"name": "Help".to_string(),
|
||||||
|
"value": help_message,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.embed(|e| {
|
||||||
|
e.field("Help", help_message, false);
|
||||||
|
|
||||||
|
e
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
log::debug!("Help message displayed");
|
||||||
|
show_help.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays the menu page
|
||||||
|
async fn display_page(ctx: &Context, menu: &mut Menu<'_>) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Displaying page {}", menu.current_page);
|
||||||
|
let page = menu
|
||||||
|
.pages
|
||||||
|
.get(menu.current_page)
|
||||||
|
.ok_or(SerenityUtilsError::PageNotFound(menu.current_page))?
|
||||||
|
.get()
|
||||||
|
.await?;
|
||||||
|
let mut msg = menu.get_message(ctx.http()).await?;
|
||||||
|
|
||||||
|
msg.edit(ctx, |e| {
|
||||||
|
e.0.clone_from(&mut page.0.clone());
|
||||||
|
e
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
log::debug!("Page displayed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,447 @@
|
|||||||
|
use crate::core::MessageHandle;
|
||||||
|
use crate::error::{SerenityUtilsError, SerenityUtilsResult};
|
||||||
|
use crate::menu::container::get_listeners_from_context;
|
||||||
|
use crate::menu::controls::{close_menu, next_page, previous_page, toggle_help};
|
||||||
|
use crate::menu::traits::EventDrivenMessage;
|
||||||
|
use crate::menu::typedata::HelpActiveContainer;
|
||||||
|
use crate::menu::{EventDrivenMessagesRef, Page};
|
||||||
|
use futures::FutureExt;
|
||||||
|
use serenity::async_trait;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::http::Http;
|
||||||
|
use serenity::model::channel::{Message, Reaction, ReactionType};
|
||||||
|
use serenity::model::id::ChannelId;
|
||||||
|
use serenity::prelude::{TypeMap, TypeMapKey};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
|
pub static NEXT_PAGE_EMOJI: &str = "➡️";
|
||||||
|
pub static PREVIOUS_PAGE_EMOJI: &str = "⬅️";
|
||||||
|
pub static CLOSE_MENU_EMOJI: &str = "❌";
|
||||||
|
pub static HELP_EMOJI: &str = "❔";
|
||||||
|
|
||||||
|
pub type ControlActionResult<'b> =
|
||||||
|
Pin<Box<dyn Future<Output = SerenityUtilsResult<()>> + Send + 'b>>;
|
||||||
|
|
||||||
|
pub type ControlActionArc = Arc<
|
||||||
|
dyn for<'b> Fn(&'b Context, &'b mut Menu<'_>, Reaction) -> ControlActionResult<'b>
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ActionContainer {
|
||||||
|
inner: ControlActionArc,
|
||||||
|
position: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionContainer {
|
||||||
|
/// Creates a new control action
|
||||||
|
pub fn new<F: 'static>(position: isize, callback: F) -> Self
|
||||||
|
where
|
||||||
|
F: for<'b> Fn(&'b Context, &'b mut Menu<'_>, Reaction) -> ControlActionResult<'b>
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(callback),
|
||||||
|
position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the action
|
||||||
|
pub async fn run(
|
||||||
|
&self,
|
||||||
|
ctx: &Context,
|
||||||
|
menu: &mut Menu<'_>,
|
||||||
|
reaction: Reaction,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
self.inner.clone()(ctx, menu, reaction).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the position of the action
|
||||||
|
pub fn position(&self) -> isize {
|
||||||
|
self.position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A menu message
|
||||||
|
pub struct Menu<'a> {
|
||||||
|
pub message: Arc<RwLock<MessageHandle>>,
|
||||||
|
pub pages: Vec<Page<'a>>,
|
||||||
|
pub current_page: usize,
|
||||||
|
pub controls: HashMap<String, ActionContainer>,
|
||||||
|
pub timeout: Instant,
|
||||||
|
pub sticky: bool,
|
||||||
|
pub data: TypeMap,
|
||||||
|
pub help_entries: HashMap<String, String>,
|
||||||
|
closed: bool,
|
||||||
|
listeners: EventDrivenMessagesRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Menu<'_> {
|
||||||
|
/// Removes all reactions from the menu
|
||||||
|
pub(crate) async fn close(&mut self, http: &Http) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Closing menu...");
|
||||||
|
let handle = self.message.read().await;
|
||||||
|
http.delete_message_reactions(handle.channel_id, handle.message_id)
|
||||||
|
.await?;
|
||||||
|
self.closed = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the message of the menu
|
||||||
|
pub async fn get_message(&self, http: &Http) -> SerenityUtilsResult<Message> {
|
||||||
|
let handle = self.message.read().await;
|
||||||
|
let msg = http
|
||||||
|
.get_message(handle.channel_id, handle.message_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recreates the message completely
|
||||||
|
pub async fn recreate(&self, http: &Http) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Recreating message");
|
||||||
|
|
||||||
|
let old_handle = {
|
||||||
|
let handle = self.message.read().await;
|
||||||
|
(*handle).clone()
|
||||||
|
};
|
||||||
|
log::debug!("Getting current page");
|
||||||
|
let current_page = self
|
||||||
|
.pages
|
||||||
|
.get(self.current_page)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(SerenityUtilsError::PageNotFound(self.current_page))?
|
||||||
|
.get()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
log::debug!("Creating new message");
|
||||||
|
let message = http
|
||||||
|
.send_message(
|
||||||
|
old_handle.channel_id,
|
||||||
|
&serde_json::to_value(current_page.0).unwrap(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let mut controls = self
|
||||||
|
.controls
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<(String, ActionContainer)>>();
|
||||||
|
controls.sort_by_key(|(_, a)| a.position);
|
||||||
|
|
||||||
|
for emoji in controls.into_iter().map(|(e, _)| e) {
|
||||||
|
http.create_reaction(
|
||||||
|
message.channel_id.0,
|
||||||
|
message.id.0,
|
||||||
|
&ReactionType::Unicode(emoji.clone()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
log::trace!("New message is {:?}", message);
|
||||||
|
|
||||||
|
let new_handle = {
|
||||||
|
let mut handle = self.message.write().await;
|
||||||
|
handle.message_id = message.id.0;
|
||||||
|
(*handle).clone()
|
||||||
|
};
|
||||||
|
{
|
||||||
|
log::debug!("Changing key of message");
|
||||||
|
let mut listeners_lock = self.listeners.lock().await;
|
||||||
|
let menu = listeners_lock.remove(&old_handle).unwrap();
|
||||||
|
listeners_lock.insert(new_handle, menu);
|
||||||
|
}
|
||||||
|
log::debug!("Deleting original message");
|
||||||
|
http.delete_message(old_handle.channel_id, old_handle.message_id)
|
||||||
|
.await?;
|
||||||
|
log::debug!("Message recreated");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'a> EventDrivenMessage for Menu<'a> {
|
||||||
|
fn is_frozen(&self) -> bool {
|
||||||
|
self.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(&mut self, http: &Http) -> SerenityUtilsResult<()> {
|
||||||
|
log::trace!("Checking for menu timeout");
|
||||||
|
if Instant::now() >= self.timeout {
|
||||||
|
log::debug!("Menu timout reached. Closing menu.");
|
||||||
|
self.close(http).await?;
|
||||||
|
} else if self.sticky {
|
||||||
|
log::debug!("Message is sticky. Checking for new messages in channel...");
|
||||||
|
let handle = {
|
||||||
|
let handle = self.message.read().await;
|
||||||
|
(*handle).clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let channel_id = ChannelId(handle.channel_id);
|
||||||
|
let messages = channel_id
|
||||||
|
.messages(http, |p| p.after(handle.message_id).limit(1))
|
||||||
|
.await?;
|
||||||
|
log::trace!("Messages are {:?}", messages);
|
||||||
|
if messages.len() > 0 {
|
||||||
|
log::debug!("New messages in channel. Recreating...");
|
||||||
|
self.recreate(http).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_reaction_add(
|
||||||
|
&mut self,
|
||||||
|
ctx: &Context,
|
||||||
|
reaction: Reaction,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
log::debug!("Reaction to menu added");
|
||||||
|
let current_user = ctx.http.get_current_user().await?;
|
||||||
|
|
||||||
|
if reaction.user_id.unwrap().0 == current_user.id.0 {
|
||||||
|
log::debug!("Reaction is from current user.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let emoji_string = reaction.emoji.as_data();
|
||||||
|
|
||||||
|
log::debug!("Deleting user reaction.");
|
||||||
|
reaction.delete(ctx).await?;
|
||||||
|
if let Some(control) = self.controls.get(&emoji_string).cloned() {
|
||||||
|
log::debug!("Running control");
|
||||||
|
control.run(ctx, self, reaction).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder for messages
|
||||||
|
pub struct MenuBuilder {
|
||||||
|
pages: Vec<Page<'static>>,
|
||||||
|
current_page: usize,
|
||||||
|
controls: HashMap<String, ActionContainer>,
|
||||||
|
timeout: Duration,
|
||||||
|
sticky: bool,
|
||||||
|
data: TypeMap,
|
||||||
|
help_entries: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MenuBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pages: vec![],
|
||||||
|
current_page: 0,
|
||||||
|
controls: HashMap::new(),
|
||||||
|
timeout: Duration::from_secs(60),
|
||||||
|
sticky: false,
|
||||||
|
data: TypeMap::new(),
|
||||||
|
help_entries: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuBuilder {
|
||||||
|
/// Creates a new paginaton menu
|
||||||
|
pub fn new_paginator() -> Self {
|
||||||
|
log::debug!("Creating new paginator");
|
||||||
|
let mut controls = HashMap::new();
|
||||||
|
let mut help_entries = HashMap::new();
|
||||||
|
controls.insert(
|
||||||
|
PREVIOUS_PAGE_EMOJI.to_string(),
|
||||||
|
ActionContainer::new(0, |c, m, r| previous_page(c, m, r).boxed()),
|
||||||
|
);
|
||||||
|
help_entries.insert(
|
||||||
|
PREVIOUS_PAGE_EMOJI.to_string(),
|
||||||
|
"Displays the previous page".to_string(),
|
||||||
|
);
|
||||||
|
controls.insert(
|
||||||
|
CLOSE_MENU_EMOJI.to_string(),
|
||||||
|
ActionContainer::new(1, |c, m, r| close_menu(c, m, r).boxed()),
|
||||||
|
);
|
||||||
|
help_entries.insert(
|
||||||
|
CLOSE_MENU_EMOJI.to_string(),
|
||||||
|
"Closes the menu buttons".to_string(),
|
||||||
|
);
|
||||||
|
controls.insert(
|
||||||
|
NEXT_PAGE_EMOJI.to_string(),
|
||||||
|
ActionContainer::new(2, |c, m, r| next_page(c, m, r).boxed()),
|
||||||
|
);
|
||||||
|
help_entries.insert(
|
||||||
|
NEXT_PAGE_EMOJI.to_string(),
|
||||||
|
"Displays the next page".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
controls,
|
||||||
|
help_entries,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a page to the message builder
|
||||||
|
pub fn add_page(mut self, page: Page<'static>) -> Self {
|
||||||
|
self.pages.push(page);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds multiple pages to the message
|
||||||
|
pub fn add_pages<I>(mut self, pages: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Page<'static>>,
|
||||||
|
{
|
||||||
|
let mut pages = pages.into_iter().collect();
|
||||||
|
self.pages.append(&mut pages);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a single control to the message
|
||||||
|
pub fn add_control<S, F: 'static>(mut self, position: isize, emoji: S, action: F) -> Self
|
||||||
|
where
|
||||||
|
S: ToString,
|
||||||
|
F: for<'b> Fn(&'b Context, &'b mut Menu<'_>, Reaction) -> ControlActionResult<'b>
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
{
|
||||||
|
self.controls
|
||||||
|
.insert(emoji.to_string(), ActionContainer::new(position, action));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a single control to the message
|
||||||
|
pub fn add_controls<S, I>(mut self, controls: I) -> Self
|
||||||
|
where
|
||||||
|
S: ToString,
|
||||||
|
I: IntoIterator<Item = (isize, S, ControlActionArc)>,
|
||||||
|
{
|
||||||
|
for (position, emoji, action) in controls {
|
||||||
|
self.controls.insert(
|
||||||
|
emoji.to_string(),
|
||||||
|
ActionContainer {
|
||||||
|
position,
|
||||||
|
inner: action,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the timeout for the message
|
||||||
|
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||||
|
self.timeout = timeout;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the start page of the message
|
||||||
|
pub fn start_page(mut self, page: usize) -> Self {
|
||||||
|
self.current_page = page;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message should be sticky and always be
|
||||||
|
/// the last one in the channel
|
||||||
|
pub fn sticky(mut self, value: bool) -> Self {
|
||||||
|
self.sticky = value;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds data to the menu typemap
|
||||||
|
pub fn add_data<T>(mut self, value: T::Value) -> Self
|
||||||
|
where
|
||||||
|
T: TypeMapKey,
|
||||||
|
{
|
||||||
|
self.data.insert::<T>(value);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a help entry
|
||||||
|
pub fn add_help<S: ToString>(mut self, button: S, help: S) -> Self {
|
||||||
|
self.help_entries
|
||||||
|
.insert(button.to_string(), help.to_string());
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns showing help for buttons on
|
||||||
|
pub fn show_help(self) -> Self {
|
||||||
|
self.add_control(100, HELP_EMOJI, |c, m, r| Box::pin(toggle_help(c, m, r)))
|
||||||
|
.add_data::<HelpActiveContainer>(Arc::new(AtomicBool::new(false)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// builds the menu
|
||||||
|
pub async fn build(
|
||||||
|
self,
|
||||||
|
ctx: &Context,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
) -> SerenityUtilsResult<Arc<RwLock<MessageHandle>>> {
|
||||||
|
log::debug!("Building menu...");
|
||||||
|
let mut current_page = self
|
||||||
|
.pages
|
||||||
|
.get(self.current_page)
|
||||||
|
.ok_or(SerenityUtilsError::PageNotFound(self.current_page))?
|
||||||
|
.clone()
|
||||||
|
.get()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let message = channel_id.send_message(ctx, |_| &mut current_page).await?;
|
||||||
|
log::trace!("Message is {:?}", message);
|
||||||
|
let listeners = get_listeners_from_context(ctx).await?;
|
||||||
|
log::debug!("Sorting controls...");
|
||||||
|
let mut controls = self
|
||||||
|
.controls
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<(String, ActionContainer)>>();
|
||||||
|
controls.sort_by_key(|(_, a)| a.position);
|
||||||
|
|
||||||
|
log::debug!("Creating menu...");
|
||||||
|
let message_handle = MessageHandle::new(message.channel_id, message.id);
|
||||||
|
let handle_lock = Arc::new(RwLock::new(message_handle));
|
||||||
|
|
||||||
|
let menu = Menu {
|
||||||
|
message: Arc::clone(&handle_lock),
|
||||||
|
pages: self.pages,
|
||||||
|
current_page: self.current_page,
|
||||||
|
controls: self.controls,
|
||||||
|
timeout: Instant::now() + self.timeout,
|
||||||
|
closed: false,
|
||||||
|
listeners: Arc::clone(&listeners),
|
||||||
|
sticky: self.sticky,
|
||||||
|
data: self.data,
|
||||||
|
help_entries: self.help_entries,
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!("Storing menu to listeners...");
|
||||||
|
{
|
||||||
|
let mut listeners_lock = listeners.lock().await;
|
||||||
|
log::trace!("Listeners locked.");
|
||||||
|
listeners_lock.insert(message_handle, Arc::new(Mutex::new(Box::new(menu))));
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("Adding controls...");
|
||||||
|
for (emoji, _) in controls {
|
||||||
|
message
|
||||||
|
.react(ctx, ReactionType::Unicode(emoji.clone()))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
log::debug!("Menu successfully created.");
|
||||||
|
|
||||||
|
Ok(handle_lock)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
pub(crate) mod container;
|
||||||
|
pub(crate) mod controls;
|
||||||
|
pub(crate) mod menu;
|
||||||
|
pub(crate) mod page;
|
||||||
|
pub(crate) mod traits;
|
||||||
|
pub(crate) mod typedata;
|
||||||
|
|
||||||
|
pub use container::*;
|
||||||
|
pub use controls::*;
|
||||||
|
pub use menu::{
|
||||||
|
ActionContainer, ControlActionArc, Menu, MenuBuilder, CLOSE_MENU_EMOJI, NEXT_PAGE_EMOJI,
|
||||||
|
PREVIOUS_PAGE_EMOJI,
|
||||||
|
};
|
||||||
|
pub use page::*;
|
||||||
|
|
||||||
|
pub use traits::EventDrivenMessage;
|
@ -0,0 +1,40 @@
|
|||||||
|
use crate::error::SerenityUtilsResult;
|
||||||
|
use serenity::builder::CreateMessage;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub type MessageBuildOutput<'b> =
|
||||||
|
Pin<Box<dyn Future<Output = SerenityUtilsResult<CreateMessage<'b>>> + Send + 'b>>;
|
||||||
|
pub type MessageBuilderFn<'b> = Arc<dyn Fn() -> MessageBuildOutput<'b> + Send + Sync>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
/// A page that stores a builder function for message pages
|
||||||
|
/// or static pages
|
||||||
|
pub enum Page<'b> {
|
||||||
|
Builder(MessageBuilderFn<'b>),
|
||||||
|
Static(CreateMessage<'b>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> Page<'b> {
|
||||||
|
/// Creates a new page with the given builder function
|
||||||
|
pub fn new_builder<F: 'static>(builder_fn: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn() -> MessageBuildOutput<'b> + Send + Sync,
|
||||||
|
{
|
||||||
|
Self::Builder(Arc::new(builder_fn))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new page with a static message
|
||||||
|
pub fn new_static(page: CreateMessage<'b>) -> Self {
|
||||||
|
Self::Static(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the CreateMessage of the page
|
||||||
|
pub async fn get(&self) -> SerenityUtilsResult<CreateMessage<'b>> {
|
||||||
|
match self {
|
||||||
|
Page::Builder(b) => b().await,
|
||||||
|
Page::Static(inner) => Ok(inner.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
use crate::error::SerenityUtilsResult;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::http::Http;
|
||||||
|
use serenity::{async_trait, model::prelude::*};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait EventDrivenMessage: Send + Sync {
|
||||||
|
/// Returns if a message has been frozen and won't handle any further events
|
||||||
|
fn is_frozen(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired periodically
|
||||||
|
async fn update(&mut self, _http: &Http) -> SerenityUtilsResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when the message was deleted
|
||||||
|
async fn on_deleted(&mut self, _ctx: &Context) -> SerenityUtilsResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when a reaction was added to the message
|
||||||
|
async fn on_reaction_add(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &Context,
|
||||||
|
_reaction: Reaction,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when a reaction was removed from the message
|
||||||
|
async fn on_reaction_remove(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &Context,
|
||||||
|
_reaction: Reaction,
|
||||||
|
) -> SerenityUtilsResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
use serenity::prelude::TypeMapKey;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct HelpActiveContainer;
|
||||||
|
|
||||||
|
impl TypeMapKey for HelpActiveContainer {
|
||||||
|
type Value = Arc<AtomicBool>;
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
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(())
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
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(())
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
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(())
|
|
||||||
}
|
|
@ -1,41 +1,30 @@
|
|||||||
use serenity::framework::standard::macros::group;
|
use serenity::framework::standard::macros::group;
|
||||||
|
|
||||||
use about::ABOUT_COMMAND;
|
use about::ABOUT_COMMAND;
|
||||||
use add_media::ADD_MEDIA_COMMAND;
|
use add_gif::ADD_GIF_COMMAND;
|
||||||
use clear::CLEAR_COMMAND;
|
use gifs::GIFS_COMMAND;
|
||||||
use fuck::FUCK_COMMAND;
|
|
||||||
use inspirobot::INSPIROBOT_COMMAND;
|
|
||||||
use media::MEDIA_COMMAND;
|
|
||||||
use pain::PAIN_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 about;
|
||||||
mod add_media;
|
mod add_gif;
|
||||||
mod clear;
|
mod gifs;
|
||||||
mod fuck;
|
|
||||||
pub(crate) mod help;
|
pub(crate) mod help;
|
||||||
mod inspirobot;
|
|
||||||
mod media;
|
|
||||||
mod pain;
|
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(
|
#[commands(
|
||||||
ping, stats, shutdown, time, timezones, qalc, about, add_media, media, pain, clear, xkcd, fuck,
|
ping, stats, shutdown, time, timezones, qalc, about, add_gif, gifs, pain
|
||||||
party, inspirobot
|
|
||||||
)]
|
)]
|
||||||
pub struct Misc;
|
pub struct Misc;
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
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(())
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
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(())
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue