Compare commits

...

30 Commits

Author SHA1 Message Date
60adc1e937 test
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2022-10-23 04:14:15 +05:30
af80a94cfb minor changes
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2022-05-11 02:06:48 +05:30
cdf5c2bce6 test cachix
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2022-05-07 03:53:38 +05:30
25735e3581 revert back to old ci
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2022-04-04 18:17:02 +05:30
a3919853b0 ci: move some variables [skip ci]
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2022-03-29 08:23:37 +05:30
a4e7c64193 commands/tags: add trandom
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2022-03-26 12:05:20 +05:30
0453caee43 check referenced message as well
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2022-03-26 11:30:55 +05:30
5779f54f2d see count for other users as well 2022-02-25 21:33:35 +05:30
1d63fc050c fix tags levenshtein dist 2022-02-25 20:07:44 +05:30
05dc40863e fixed select menu taking more than 25 items 2022-02-25 19:22:38 +05:30
c04ec75f3e i hate myself [skip ci] 2022-02-20 21:02:55 +05:30
5d24893af6 test release CI 2022-02-20 20:47:34 +05:30
cee2e47b6c cadd: use a better delimiter 2022-02-19 15:11:58 +05:30
cffeff4e27 fix regex 2022-02-19 14:28:07 +05:30
d20326a846 [ci skip] use functions instead of macros 2022-02-15 20:49:50 +05:30
a7a15dc3b1 just made a huge fucking mess
added SelectMenu for embeds
removed interactions (for now)
stupid macros
need to organize this shit asap
2022-02-14 23:13:14 +05:30
18b19f0695 sql sanitation 2022-02-14 20:26:34 +05:30
f59a2c1b7f move pipeline files to their own repo 2022-02-14 13:12:22 +05:30
9a01af14f7 [skip ci] remove github actions 2022-02-14 09:36:54 +05:30
8e732b34ca added tcopy
also renamed some commands + hclfmt the nomad file
2022-02-14 09:18:55 +05:30
5c911042be show actual serial numbers 2022-02-14 00:58:19 +05:30
8177787376 dont rearrange sequences 2022-02-14 00:17:14 +05:30
44aba55f5e added README.md 2022-02-13 23:55:24 +05:30
b43472227a fixed tedit and tadd 2022-02-13 20:45:15 +05:30
5f8ceb94b2 renamed count commands and added tags commands 2022-02-13 20:28:59 +05:30
6461fa4b94 added embed scroll in count: ls 2022-02-13 14:35:25 +05:30
9b8b2d28e5 add help messages and turn rust backtrace on 2022-02-13 00:02:10 +05:30
17e98f9160 added initial sql script and some minor change 2022-02-12 23:24:32 +05:30
88b6b4fb8d expose DB port and misc changes 2022-02-12 18:09:49 +05:30
a070bdd528 finally back, added a nomad job file 2022-02-06 23:05:08 +05:30
23 changed files with 3938 additions and 620 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
Cargo.nix linguist-generated

View File

@@ -1,21 +0,0 @@
name: main
on:
push:
pull_request:
workflow_dispatch:
jobs:
packages:
name: test packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: cachix/install-nix-action@v13
with:
install_url: https://github.com/numtide/nix-flakes-installer/releases/download/nix-2.4pre20210429_d15a196/install
extra_nix_config: |
experimental-features = nix-command flakes
- uses: cachix/cachix-action@v10
with:
name: natto1784
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix build

5
.gitignore vendored
View File

@@ -1,2 +1,7 @@
target/ target/
result result
\#*\#
.\#*
.*~*~
*~
result-bin

714
Cargo.lock generated

File diff suppressed because it is too large Load Diff

2321
Cargo.nix generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,22 @@
cargo-features = ["edition2021"]
[package] [package]
name = "singh3" name = "singh3"
version = "0.1.0" version = "0.1.0"
authors = [ "Amneesh Singh <natto@weirdnatto.in>" ] authors = [ "Amneesh Singh <natto@weirdnatto.in>" ]
edition = "2018" edition = "2021"
[dependencies] [dependencies]
tracing = "0.1.22" tracing = "*"
regex = "1.5.4" regex = "1"
tokio-postgres = "0.7.2" tokio-postgres = "*"
rand = "0.8.4" rand = "*"
[dependencies.serenity] [dependencies.serenity]
version = "0.10.8" version = "0.10.10"
features = ["cache", "framework", "standard_framework", "rustls_backend", "unstable_discord_api", "collector"] features = ["cache", "framework", "standard_framework", "rustls_backend", "unstable_discord_api", "collector"]
[dependencies.tokio] [dependencies.tokio]
version = "1.0" version = "1.0"
features = ["macros", "signal", "rt-multi-thread"] features = ["macros", "signal", "rt-multi-thread"]

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
[![Concourse CI](https://ci.weirdnatto.in/api/v1/teams/main/pipelines/singh3/badge)](https://ci.weirdnatto.in/teams/main/pipelines/singh3) [![Docker](https://img.shields.io/docker/image-size/natto17/singh3.svg)](https://hub.docker.com/repository/docker/natto17/singh3)
# Singh3
My stupid discord bot that I will probably stop maintaining again like I did for the past 8+ months
Real shit code
- Not really a useful bot, run at your own risk
- You need postgresql too
- Environment Variables
- DISCORD_TOKEN={{.your-discord-token}}
- DB_URL={{.your-postgresql-db-url}}
- Image size is kinda large cuz it uses ubuntu for libraries that are dynamically needed

85
ci/pipeline.yml Normal file
View File

@@ -0,0 +1,85 @@
resource_types:
- name: nomad
type: registry-image
source:
repository: natto17/concourse-nomad-resource
tag: latest
resources:
- name: image
type: registry-image
icon: docker
source:
repository: ((docker.user))/singh3
tag: latest
username: ((docker.user))
password: ((docker.pass))
- name: nomad-job
type: nomad
source:
url: https://nomad.weirdnatto.in
name: singh3
token: ((nomad.token))
consul_token: ((nomad.consul))
vault_token: ((nomad.vault))
- name: repo
type: git
icon: discord
source:
uri: https://git.weirdnatto.in/natto1784/singh3.git
branch: master
- name: nix
type: registry-image
icon: docker
source:
repository: nixos/nix
tag: latest
jobs:
- name: configure-self
public: true
plan:
- get: repo
trigger: true
- set_pipeline: self
file: repo/ci/pipeline.yml
- name: singh3
plan:
- get: repo
trigger: true
passed: [configure-self]
- get: nix
trigger: false
- task: build
image: nix
config:
params:
CACHIX_NAME: ((cachix.name))
CACHIX_AUTH_TOKEN: ((cachix.token))
inputs:
- name: repo
outputs:
- name: upload
platform: linux
run:
path: sh
args:
- -c
- |
nix-env -iA nixpkgs.cachix nixpkgs.gzip
cachix use $CACHIX_NAME
cachix watch-exec pain nix -- --extra-experimental-features "nix-command flakes" build ./repo
nix --extra-experimental-features "nix-command flakes" build ./repo#image -o result
gzip -cd < $(readlink result) > ./upload/image.tar
- put: image
inputs: [upload]
params:
image: upload/image.tar
- put: nomad-job
params:
job_path: repo/singh3.nomad
templating: false
restart: true

10
default.nix Normal file
View File

@@ -0,0 +1,10 @@
(import
(
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
)
{ src = ./.; }
).defaultNix

108
flake.lock generated
View File

@@ -1,12 +1,47 @@
{ {
"nodes": { "nodes": {
"cargo2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1648397583,
"narHash": "sha256-g1Ej9APkfHjGd9ExBDvpmyAvsKcfMOsNH6p95xc8E/Y=",
"owner": "cargo2nix",
"repo": "cargo2nix",
"rev": "4362ae00fe824d120e94dd5d6f6e63969dc3d264",
"type": "github"
},
"original": {
"owner": "cargo2nix",
"repo": "cargo2nix",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"locked": { "locked": {
"lastModified": 1614513358, "lastModified": 1638122382,
"narHash": "sha256-LakhOx3S1dRjnh0b5Dg3mbZyH0ToC9I8Y2wKSkBaTzU=", "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "5466c5bbece17adaab2d82fae80b46e807611bf3", "rev": "74f7e4319258e287b0f9cb95426c9853b282730b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1637014545,
"narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -16,6 +51,22 @@
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": {
"lastModified": 1638109994,
"narHash": "sha256-OpA37PTiPMIqoRJbufbl5rOLII7HeeGcA0yl7FoyCIE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a284564b7f75ac4db73607db02076e8da9d42c9d",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "release-21.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1622966049, "lastModified": 1622966049,
"narHash": "sha256-6g+28v94ISkVk9TBSsITVOnB2slK8plieWPIF2jo/l0=", "narHash": "sha256-6g+28v94ISkVk9TBSsITVOnB2slK8plieWPIF2jo/l0=",
@@ -31,39 +82,66 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1617325113, "lastModified": 1637453606,
"narHash": "sha256-GksR0nvGxfZ79T91UUtWjjccxazv6Yh/MvEJ82v1Xmw=", "narHash": "sha256-Gy6cwUswft9xqsjWxFYEnx/63/qzaFUwatcbV5GF/GQ=",
"owner": "nixos", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "54c1e44240d8a527a8f4892608c4bce5440c3ecb", "rev": "8afc4e543663ca0a6a4f496262cd05233737e732",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs", "cargo2nix": "cargo2nix",
"rust-overlay": "rust-overlay", "nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay_2",
"utils": "utils" "utils": "utils"
} }
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": [
"nixpkgs": "nixpkgs_2" "cargo2nix",
"flake-utils"
],
"nixpkgs": [
"cargo2nix",
"nixpkgs"
]
}, },
"locked": { "locked": {
"lastModified": 1623034161, "lastModified": 1638152159,
"narHash": "sha256-cbw9X+nVFcpIuBga0hkbtzXbW2fyDWBon6oUN/uQmu0=", "narHash": "sha256-Q0UHsm36cCxk16I/bF1rHJHxjIflESKk2ej76P39j90=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "0b952cdfa37f8b0fc70fc75fbd4605227cd0b272", "rev": "d9a664513558376595e838b21348cdac0ba3115e",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"rust-overlay_2": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1648953213,
"narHash": "sha256-kXWcXFwqcvooHjcDoEK4mtvKU/8LuetwhrofU+9KMS0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "caa1a7ea867138d4f4e05bd2f41d707df0a61cde",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -1,37 +1,54 @@
{ {
description = "A simple filehost written in rust"; description = "singh3 discord bot";
inputs = { inputs = {
nixpkgs.url = github:nixos/nixpkgs/nixos-unstable; nixpkgs.url = github:nixos/nixpkgs/nixos-unstable;
utils.url = github:numtide/flake-utils; utils.url = github:numtide/flake-utils;
rust-overlay.url = github:oxalica/rust-overlay; rust-overlay.url = github:oxalica/rust-overlay;
cargo2nix.url = github:cargo2nix/cargo2nix;
}; };
outputs = { self, nixpkgs, utils, rust-overlay }: outputs = { self, nixpkgs, utils, rust-overlay, cargo2nix }:
utils.lib.eachDefaultSystem utils.lib.eachDefaultSystem
(system: (system:
let let
overlays = [ (import rust-overlay) ]; overlays =
pkgs = import nixpkgs { [
inherit system overlays; (import "${cargo2nix}/overlay")
}; rust-overlay.overlay
in ];
{
devShell = with pkgs; mkShell { pkgs = import nixpkgs {
buildInputs = [ inherit system overlays;
rust-bin.nightly.latest.default };
rust-analyzer
]; rustPkgs = pkgs.rustBuilder.makePackageSet' {
}; rustChannel = "latest";
defaultPackage = pkgs.rustPlatform.buildRustPackage rec { packageFun = import ./Cargo.nix;
pname = "singh3"; };
version = "0.1.0";
src = ./. ; in
nativeBuildInputs = with pkgs; [ rec {
rust-bin.nightly.latest.default devShell = with pkgs; mkShell {
]; buildInputs = [
cargoSha256 = "sha256-K+WHOEo6reNfcs7pOZZmHZfZl4pUqlykfTdqgSyVURU="; rust-bin.nightly.latest.default
}; rust-analyzer
} postgresql
); ];
};
packages = {
default = (rustPkgs.workspace.singh3 { }).bin;
image = pkgs.dockerTools.buildImage {
name = "singh3";
tag = "latest";
created = "now";
contents = [ packages.default ];
config.Cmd = [ "/bin/singh3" ];
};
};
defaultPackage = packages.default;
}
);
} }

13
init.sql Normal file
View File

@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS words (
id serial primary key,
name varchar not null,
reg varchar not null,
owner varchar );
CREATE TABLE IF NOT EXISTS tags (
id serial primary key,
name varchar unique not null,
value varchar not null,
owner varchar );
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;

85
singh3.nomad Normal file
View File

@@ -0,0 +1,85 @@
job "singh3" {
region = "global"
datacenters = ["nazrin"]
type = "service"
group "svc" {
count = 1
network {
mode = "bridge"
port "db" {
static = 5454
to = 5432
}
}
vault {
policies = ["singh3-policy"]
}
service {
name = "singh3-db"
port = "db"
}
task "db" {
template {
data = <<EOF
{{with secret "kv/data/singh3/db"}}{{.Data.data.pass}}{{end}}
EOF
destination = "${NOMAD_SECRETS_DIR}/db.pass"
}
driver = "docker"
config {
image = "postgres:alpine"
ports = ["db"]
volumes = ["/var/lib/nomad-st/postgres-singh3:/var/lib/postgresql/data"]
}
env {
POSTGRES_USER = "singh3"
POSTGRES_PASSWORD_FILE = "${NOMAD_SECRETS_DIR}/db.pass"
POSTGRES_DB = "singh3"
}
resources {
cpu = 128
memory = 100
}
}
task "bot" {
driver = "docker"
config {
image = "natto17/singh3:latest"
force_pull = true
}
resources {
cpu = 128
memory = 100
}
template {
data = <<EOF
{{with secret "kv/data/singh3/db"}}
DB_URL="postgresql://singh3:{{.Data.data.pass}}@localhost:5432/singh3"
{{end}}
{{with secret "kv/data/singh3/discord"}}
DISCORD_TOKEN="{{.Data.data.token}}"
{{end}}
RUST_BACKTRACE=1
EOF
destination = "${NOMAD_SECRETS_DIR}/data.env"
env = true
}
}
}
}

View File

@@ -1,48 +1,65 @@
use crate::lib::components::make_terminal_components;
use core::time::Duration;
use regex::Regex;
use serenity::{ use serenity::{
builder::CreateEmbed,
collector::component_interaction_collector::ComponentInteractionCollectorBuilder,
framework::standard::{macros::command, Args, CommandResult}, framework::standard::{macros::command, Args, CommandResult},
model::prelude::*, futures::StreamExt,
model::{interactions::InteractionResponseType, prelude::*},
prelude::*, prelude::*,
utils::Colour,
}; };
use std::env; use tokio_postgres::Row;
use tokio_postgres::NoTls;
#[command] #[command]
pub async fn kitna(ctx: &Context, msg: &Message, args: Args) -> CommandResult { #[aliases("kitna", "c")]
let query: String = args.raw().collect::<Vec<&str>>().join(" "); pub async fn count(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
if query == "" { if args.len() > 2 || args.len() == 0 {
msg.reply(ctx, "bruh kitna kya?").await?; msg.reply(ctx, "Please use `,count <word> <user>`").await?;
return Ok(()) return Ok(());
} }
let db: String = env::var("DB_URL").expect("bhay DB_URL daal na");
let (client, conn) = tokio_postgres::connect(&db, NoTls) let query = args.single::<String>().unwrap();
.await let user = if args.len() == 2 {
.expect("cant connect bha"); let user = args.single::<UserId>();
tokio::spawn(async move { match user {
if let Err(e) = conn.await { Ok(id) => match id.to_user(&ctx.http).await {
eprintln!("connection error: {}", e); Ok(u) => u,
Err(_) => {
msg.reply(ctx, "No such user").await?;
return Ok(());
}
},
Err(_) => {
msg.reply(ctx, "No such user").await?;
return Ok(());
}
} }
}); } else {
let id = msg.author.id.as_u64().to_owned().to_string(); msg.author.clone()
let mut query_helper = client };
.query(
format!("select name from words where '{}' ~ reg", query).as_str(), let data_read = ctx.data.read().await;
&[], let db = data_read
) .get::<crate::Database>()
.await .expect("Expected Database in TypeMap.")
.expect("helper query to select count failed"); .clone();
let id = user.id.to_string();
let mut query_helper = db
.query("SELECT name FROM words WHERE $1 ~ reg", &[&query])
.await?;
if query_helper.is_empty() { if query_helper.is_empty() {
query_helper = client query_helper = db
.query( .query("SELECT name FROM words WHERE name=$1", &[&query])
format!("select name from words where name='{}'", query).as_str(), .await?;
&[],
)
.await
.expect("helper query to select count failed");
if query_helper.is_empty() { if query_helper.is_empty() {
msg.reply( msg.reply(
ctx, ctx,
format!( format!(
"No entry for '{}' found. If you want to add it, run 'xxadd {}&<regex>'", "No entry for '{}' found. If you want to add it, run `,cadd {} <regex>`",
query, query query, query
), ),
) )
@@ -57,47 +74,49 @@ pub async fn kitna(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
}; };
for row in query_helper { for row in query_helper {
let name: &str = row.get(0); let name: &str = row.get(0);
let query_result: i32 = client let count_query = db
.query_one( .query(
format!("select count from user{} where name='{}'", id, name).as_str(), format!("SELECT count FROM user{} WHERE name=$1", id).as_str(),
&[], &[&name],
) )
.await .await?;
.expect("cant select the count") let query_result: i32 = if count_query.is_empty() {
.get(0); 0
reply = reply + &format!("\n{} count for you: {}", name, query_result); } else {
count_query[0].get(0)
};
reply += &format!("\n{} count for {}: {}", name, user.name, query_result);
} }
msg.reply(ctx, reply).await?; msg.reply(ctx, reply).await?;
Ok(()) Ok(())
} }
#[command] #[command]
pub async fn add(ctx: &Context, msg: &Message, args: Args) -> CommandResult { pub async fn cadd(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query: String = args.raw().collect::<Vec<&str>>().join(" "); let query: String = args.raw().collect::<Vec<&str>>().join(" ");
let queries = query.split("&").collect::<Vec<&str>>(); let queries = query.splitn(2, " ").collect::<Vec<&str>>();
if queries.len() != 2 { if queries.len() != 2 {
msg.reply(ctx, "Please use the proper syntax: xxadd <name>&<regex>\nIf you don't know what regex is, just do: xxadd <name>&<name>") msg.reply(ctx, "Please use the proper syntax: `,cadd <name> <regex>`\nIf you don't know what regex is, just do: `,cadd <name> <name>`")
.await?; .await?;
return Ok(()); return Ok(());
} }
if queries[1].contains(" ") { let r = Regex::new(&format!("(?i){}", queries[1]));
msg.reply(ctx, "Not a valid regex").await?;
if r.is_err() {
msg.reply(ctx, "Please enter a valid regex").await?;
return Ok(()); return Ok(());
} }
let db: String = env::var("DB_URL").expect("bhay DB_URL daal na");
let (client, conn) = tokio_postgres::connect(&db, NoTls) let reg = r.unwrap();
.await
.expect("cant connect bha"); let data_read = ctx.data.read().await;
tokio::spawn(async move { let db = data_read
if let Err(e) = conn.await { .get::<crate::Database>()
eprintln!("connection error: {}", e); .expect("Expected Database in TypeMap.")
} .clone();
});
let check_existense = client let check_existense = db
.query( .query("SELECT name, reg FROM words WHERE name=$1", &[&queries[0]])
format!("select name, reg from words where name='{}'", queries[0]).as_str(),
&[],
)
.await?; .await?;
if check_existense.len() != 0 { if check_existense.len() != 0 {
let reg: String = check_existense[0].get(1); let reg: String = check_existense[0].get(1);
@@ -108,63 +127,50 @@ pub async fn add(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
.await?; .await?;
return Ok(()); return Ok(());
} }
client db.execute(
.execute( "INSERT INTO words(name, reg, owner) VALUES($1, $2, $3)",
format!( &[&queries[0], &reg.to_string(), &msg.author.id.to_string()],
"insert into words(name, reg, owner) values('{}','(?i){}', '{}')", )
queries[0], .await?;
queries[1],
msg.author.id.as_u64().to_owned().to_string()
)
.as_str(),
&[],
)
.await?;
msg.reply(ctx, "Added").await?; msg.reply(ctx, "Added").await?;
Ok(()) Ok(())
} }
#[command] #[command]
pub async fn rm(ctx: &Context, msg: &Message, args: Args) -> CommandResult { #[aliases("crm")]
pub async fn cremove(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query: String = args.raw().collect::<Vec<&str>>().join(" "); let query: String = args.raw().collect::<Vec<&str>>().join(" ");
let db: String = env::var("DB_URL").expect("bhay DB_URL daal na"); if query == "" {
let (client, conn) = tokio_postgres::connect(&db, NoTls) msg.reply(ctx, "remove what?").await?;
.await return Ok(());
.expect("cant connect bha"); }
tokio::spawn(async move { let data_read = ctx.data.read().await;
if let Err(e) = conn.await { let db = data_read
eprintln!("connection error: {}", e); .get::<crate::Database>()
} .expect("Expected Database in TypeMap.")
}); .clone();
let owner = client let owner = db
.query( .query("SELECT owner FROM words WHERE name=$1", &[&query])
format!("select owner from words where name = '{}'", query).as_str(),
&[],
)
.await?; .await?;
if owner.len() == 1 { if owner.len() == 1 {
let owner_id: String = owner[0].get(0); let owner_id: String = owner[0].get(0);
if owner_id != msg.author.id.as_u64().to_owned().to_string() { if owner_id != msg.author.id.to_string() {
msg.reply(ctx, "You don't even own this word").await?; msg.reply(ctx, "You don't even own this word").await?;
return Ok(()); return Ok(());
} }
} }
client db.execute("DELETE FROM words WHERE name=$1", &[&query])
.execute(
format!("delete from words where name='{}'", query,).as_str(),
&[],
)
.await?; .await?;
msg.reply(ctx, "Deleted if it existed").await?; msg.reply(ctx, "Deleted if it existed").await?;
Ok(()) Ok(())
} }
#[command] #[command]
pub async fn change(ctx: &Context, msg: &Message, args: Args) -> CommandResult { pub async fn cedit(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query: String = args.raw().collect::<Vec<&str>>().join(" "); let query: String = args.raw().collect::<Vec<&str>>().join(" ");
let queries = query.split("&").collect::<Vec<&str>>(); let queries = query.splitn(2, "&").collect::<Vec<&str>>();
if queries.len() != 2 { if queries.len() != 2 {
msg.reply(ctx, "Please use the proper syntax\nxxchange <name>&<regex>") msg.reply(ctx, "Please use the proper syntax\n,cedit <name>&<regex>")
.await?; .await?;
return Ok(()); return Ok(());
} }
@@ -172,38 +178,182 @@ pub async fn change(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
msg.reply(ctx, "Not a valid regex").await?; msg.reply(ctx, "Not a valid regex").await?;
return Ok(()); return Ok(());
} }
let db: String = env::var("DB_URL").expect("bhay DB_URL daal na"); let data_read = ctx.data.read().await;
let (client, conn) = tokio_postgres::connect(&db, NoTls) let db = data_read
.await .get::<crate::Database>()
.expect("cant connect bha"); .expect("Expected Database in TypeMap.")
tokio::spawn(async move { .clone();
if let Err(e) = conn.await { let owner = db
eprintln!("connection error: {}", e); .query("SELECT owner FROM words WHERE name=$1", &[&queries[0]])
}
});
let owner = client
.query(
format!("select owner from words where name = '{}'", queries[0]).as_str(),
&[],
)
.await?; .await?;
if owner.len() == 1 { if owner.len() == 1 {
let owner_id: String = owner[0].get(0); let owner_id: String = owner[0].get(0);
if owner_id != msg.author.id.as_u64().to_owned().to_string() { if owner_id != msg.author.id.to_string() {
msg.reply(ctx, "You don't even own this word").await?; msg.reply(ctx, "You don't even own this word").await?;
return Ok(()); return Ok(());
} }
} }
client db.execute(
.execute( "UPDATE words SET reg=$1 WHERE name=$2",
format!( &[&("(?i)".to_string() + queries[1]), &queries[0]],
"update words set reg='(?i){}' where name='{}'", )
queries[1], queries[0] .await?;
)
.as_str(),
&[],
)
.await?;
msg.reply(ctx, "Changed the value if it existed").await?; msg.reply(ctx, "Changed the value if it existed").await?;
Ok(()) Ok(())
} }
fn make_list_embed(cur: usize, group: &[Row]) -> CreateEmbed {
let mut e = CreateEmbed::default();
e.title(format!("List of words: Page {}", cur))
.color(Colour::TEAL);
for row in group {
let idx: i64 = row.get(0);
let name: String = row.get(1);
let owner_id: String = row.get(2);
e.field(
format!("{}. {}", idx, name),
format!(" by <@{}>", owner_id),
false,
);
}
e
}
#[command]
#[aliases("cls")]
pub async fn clist(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let size = if args.len() > 0 {
args.single::<usize>()?
} else {
5usize
};
if size > 15 {
msg.reply(ctx, "Please input a number less than 15").await?;
()
}
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let rows = db
.query(
"SELECT ROW_NUMBER() OVER (ORDER BY id), name, owner FROM words",
&[],
)
.await?;
if rows.is_empty() {
msg.reply(ctx, "No words stored").await?;
return Ok(());
}
let groups: Vec<&[Row]> = rows.chunks(size).collect();
let mut cur = 1;
let message = msg
.channel_id
.send_message(ctx, |m| {
m.set_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components("first", groups.len()))
})
.await?;
let mut collector = ComponentInteractionCollectorBuilder::new(&ctx)
.timeout(Duration::from_secs(90))
.author_id(msg.author.id)
.message_id(message.id)
.await;
while let Some(interaction) = collector.next().await {
match interaction.data.custom_id.as_ref() {
"next" => {
if cur != groups.len() {
cur += 1;
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components(
if cur == groups.len() { "last" } else { "mid" },
groups.len(),
))
})
})
.await;
}
}
"prev" => {
if cur != 1 {
cur -= 1;
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components(
if cur == 1 { "first" } else { "mid" },
groups.len(),
))
})
})
.await;
}
}
"first" => {
cur = 1;
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components("first", groups.len()))
})
})
.await;
}
"last" => {
cur = groups.len();
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components("last", groups.len()))
})
})
.await;
}
"delete" => {
message.delete(ctx).await?;
msg.delete(ctx).await?;
}
"range" => {
cur = interaction.data.values[0].parse().unwrap();
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components(
if cur == 1 {
"first"
} else if cur == groups.len() {
"last"
} else {
"mid"
},
groups.len(),
))
})
})
.await;
}
_ => {}
}
}
Ok(())
}

View File

@@ -1,9 +1,6 @@
use serenity::prelude::*; use serenity::framework::standard::{macros::command, CommandResult};
use serenity::model::prelude::*; use serenity::model::prelude::*;
use serenity::framework::standard::{ use serenity::prelude::*;
CommandResult,
macros::command
};
#[command] #[command]
pub async fn ping(ctx: &Context, msg: &Message) -> CommandResult { pub async fn ping(ctx: &Context, msg: &Message) -> CommandResult {

View File

@@ -1,3 +1,4 @@
pub mod general;
pub mod count; pub mod count;
pub mod general;
pub mod minigames; pub mod minigames;
pub mod tags;

394
src/commands/tags.rs Normal file
View File

@@ -0,0 +1,394 @@
use crate::lib::{components::make_terminal_components, messages::ExtractInfo};
use core::time::Duration;
use serenity::{
builder::CreateEmbed,
collector::component_interaction_collector::ComponentInteractionCollectorBuilder,
framework::standard::{macros::command, Args, CommandResult},
futures::StreamExt,
model::{interactions::InteractionResponseType, prelude::*},
prelude::*,
utils::Colour,
};
use tokio_postgres::Row;
const GUILD_ID: u64 = 874699899067838535;
const ROLE_ID: u64 = 957155053184102400;
#[command]
#[aliases("t")]
pub async fn tag(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query: String = args.raw().collect::<Vec<&str>>().join("");
if query == "" {
msg.reply(ctx, "Mention the tag retard").await?;
return Ok(());
}
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let query_helper = db
.query("SELECT name, value FROM tags WHERE name=$1", &[&query])
.await?;
if query_helper.is_empty() {
const DIST_LIMIT: i32 = 2;
let leven = db
.query(
"SELECT name, levenshtein_less_equal(name, $1, $2) AS lev FROM tags ORDER BY lev LIMIT 1 ",
&[&query, &DIST_LIMIT],
)
.await?;
let dist: i32 = leven[0].get(1);
let l = if dist > DIST_LIMIT {
"".to_string()
} else {
let leven_name: String = leven[0].get(0);
format!("\nDid you mean `{}`?", leven_name)
};
msg.reply(
ctx,
format!(
"No entry for '{}' found. If you want to add it, run `,tadd {} <value>`{}",
query, query, l
),
)
.await?;
return Ok(());
}
let value: String = query_helper[0].get(1);
msg.reply(ctx, value).await?;
Ok(())
}
#[command]
pub async fn tadd(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let tag_value = msg.extract_text(2, true);
if tag_value.is_none() {
msg.reply(
ctx,
"Please use the proper syntax: `,tadd <name> <value>` or attach something",
)
.await?;
return Ok(());
}
let tag_name = args.single::<String>().unwrap();
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let check_existense = db
.query("SELECT name FROM tags WHERE name=$1", &[&tag_name])
.await?;
if check_existense.len() != 0 {
msg.reply(ctx, format!("This tag already exists")).await?;
return Ok(());
}
db.execute(
"INSERT INTO tags(name, value, owner) VALUES($1, $2, $3)",
&[&tag_name, &tag_value, &msg.author.id.to_string()],
)
.await?;
msg.reply(ctx, "Added").await?;
Ok(())
}
#[command]
#[aliases("tcp")]
pub async fn tcopy(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let queries: Vec<&str> = args.raw().collect::<Vec<&str>>();
if queries.len() != 2 {
msg.reply(
ctx,
"Please use the proper syntax: `,tcopy <original> <new>`",
)
.await?;
return Ok(());
}
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let check_existense = db
.query("SELECT name FROM tags WHERE name=$1", &[&queries[0]])
.await?;
if check_existense.len() == 0 {
msg.reply(ctx, format!("This tag does not exist")).await?;
return Ok(());
}
db.execute(
"INSERT INTO tags(name, value, owner) SELECT $1, value, $2 FROM tags WHERE name=$3",
&[&queries[1], &msg.author.id.to_string(), &queries[0]],
)
.await?;
msg.reply(ctx, format!("Copied {} to {}", queries[0], queries[1]))
.await?;
Ok(())
}
#[command]
#[aliases("trm")]
pub async fn tremove(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query: String = args.raw().collect::<Vec<&str>>().join(" ");
if query == "" {
msg.reply(ctx, "remove what?").await?;
return Ok(());
}
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let owner = db
.query("SELECT owner FROM tags WHERE name=$1", &[&query])
.await?;
if owner.len() == 1 {
let owner_id: String = owner[0].get(0);
if owner_id != msg.author.id.to_string()
&& !msg.author.has_role(&ctx.http, GUILD_ID, ROLE_ID).await?
{
msg.reply(ctx, "You don't even own this tag").await?;
return Ok(());
}
}
db.execute("DELETE FROM tags WHERE name=$1", &[&query])
.await?;
msg.reply(ctx, format!("Deleted {} if it existed", query))
.await?;
Ok(())
}
#[command]
pub async fn tedit(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let tag_value = msg.extract_text(2, true);
if tag_value.is_none() {
msg.reply(
ctx,
"Please use the proper syntax: `,tadd <name> <value>` or attach something",
)
.await?;
return Ok(());
}
let tag_name = args.single::<String>().unwrap();
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let owner = db
.query("SELECT owner FROM tags WHERE name=$1", &[&tag_name])
.await?;
if owner.len() == 1 {
let owner_id: String = owner[0].get(0);
if owner_id != msg.author.id.to_string()
&& !msg.author.has_role(&ctx.http, GUILD_ID, ROLE_ID).await?
{
msg.reply(ctx, "You don't even own this tag").await?;
return Ok(());
}
}
db.execute(
"UPDATE tags SET value=$1 WHERE name=$2",
&[&tag_value, &tag_name],
)
.await?;
msg.reply(ctx, "Changed the value if it existed").await?;
Ok(())
}
fn make_list_embed(cur: usize, group: &[Row]) -> CreateEmbed {
let mut e = CreateEmbed::default();
e.title(format!("List of tags: Page {}", cur))
.color(Colour::FABLED_PINK);
for row in group {
let idx: i64 = row.get(0);
let name: String = row.get(1);
let owner_id: String = row.get(2);
e.field(
format!("{}. {}", idx, name),
format!(" by <@{}>", owner_id),
false,
);
}
e
}
#[command]
#[aliases("tls")]
pub async fn tlist(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let size = if args.len() > 0 {
args.single::<usize>()?
} else {
5usize
};
if size > 15 {
msg.reply(ctx, "Please input a number less than 15").await?;
()
}
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let rows = db
.query(
"SELECT ROW_NUMBER() OVER (ORDER BY id), name, owner FROM tags",
&[],
)
.await?;
if rows.is_empty() {
msg.reply(ctx, "No tags stored").await?;
return Ok(());
}
let groups: Vec<&[Row]> = rows.chunks(size).collect();
let mut cur = 1;
let message = msg
.channel_id
.send_message(ctx, |m| {
m.set_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components("first", groups.len()))
})
.await?;
let mut collector = ComponentInteractionCollectorBuilder::new(&ctx)
.timeout(Duration::from_secs(90))
.author_id(msg.author.id)
.message_id(message.id)
.await;
while let Some(interaction) = collector.next().await {
match interaction.data.custom_id.as_ref() {
"next" => {
if cur != groups.len() {
cur += 1;
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components(
if cur == groups.len() { "last" } else { "mid" },
groups.len(),
))
})
})
.await;
}
}
"prev" => {
if cur != 1 {
cur -= 1;
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components(
if cur == 1 { "first" } else { "mid" },
groups.len(),
))
})
})
.await;
}
}
"first" => {
cur = 1;
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components("first", groups.len()))
})
})
.await;
}
"last" => {
cur = groups.len();
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components("last", groups.len()))
})
})
.await;
}
"delete" => {
message.delete(ctx).await?;
msg.delete(ctx).await?;
}
"range" => {
cur = interaction.data.values[0].parse().unwrap();
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
m.add_embed(make_list_embed(cur, groups[cur - 1]))
.set_components(make_terminal_components(
if cur == 1 {
"first"
} else if cur == groups.len() {
"last"
} else {
"mid"
},
groups.len(),
))
})
})
.await;
}
_ => {}
}
}
Ok(())
}
#[command]
#[aliases(trand)]
pub async fn trandom(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let rand = db
.query(
"SELECT name, value, owner FROM tags OFFSET floor(random() * (SELECT COUNT(*) FROM tags)) LIMIT 1",
&[],
)
.await?;
let name: String = rand[0].get(0);
let value: String = rand[0].get(1);
let owner: String = rand[0].get(2);
let user_id = UserId::from(owner.parse::<u64>().unwrap());
msg.reply(
ctx,
format!(
"{}'s tag {}: \n{}",
user_id.to_user(&ctx.http).await?.name,
name,
value
),
)
.await?;
Ok(())
}

View File

@@ -1,77 +1,55 @@
use regex::Regex; use regex::Regex;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use std::env; use tokio_postgres::Client;
use tokio_postgres::NoTls;
pub async fn count(msg: Message) { pub async fn count(msg: Message, db: std::sync::Arc<Client>) {
let db: String = env::var("DB_URL").expect("bhay DB_URL daal na");
let (client, conn) = tokio_postgres::connect(&db, NoTls)
.await
.expect("cant connect bha");
tokio::spawn(async move {
if let Err(e) = conn.await {
eprintln!("connection error: {}", e);
}
});
let id = msg.author.id.as_u64().to_owned().to_string(); let id = msg.author.id.as_u64().to_owned().to_string();
client db.execute(
.execute( format!(
format!( r#"
" CREATE TABLE IF NOT EXISTS user{} (
CREATE TABLE IF NOT EXISTS user{} (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL, name VARCHAR NOT NULL,
count INTEGER NOT NULL count INTEGER NOT NULL
) )"#,
", id
id
)
.as_str(),
&[],
) )
.await .as_str(),
.expect("cant create table"); &[],
)
.await
.expect("Can't create a user table");
for row in client for row in db
.query("SELECT name, reg FROM words", &[]) .query("SELECT name, reg FROM words", &[])
.await .await
.expect("can't get the words to count") .expect("Can't get the words to count")
{ {
let name: &str = row.get(0); let name: &str = row.get(0);
let regex: Regex = Regex::new(row.get(1)).unwrap(); let regex: Regex = Regex::new(row.get(1)).unwrap();
let count = regex.captures_iter(&msg.content).count(); let count: i32 = regex.captures_iter(&msg.content).count() as i32;
if count > 0 { if count > 0 {
let query_result = client let query_result = db
.query( .query(
format!("SELECT count FROM user{} where name='{}'", id, name).as_str(), format!("SELECT count FROM user{} WHERE name=$1", id).as_str(),
&[], &[&name],
) )
.await .await
.expect("cant select the count"); .expect("Can't select count");
if query_result.is_empty() { if query_result.is_empty() {
client db.execute(
.execute( format!("INSERT INTO user{} (name, count) values ($1, 0)", id).as_str(),
format!( &[&name],
"insert into user{} (name, count) values ('{}', 0)",
id, name
)
.as_str(),
&[],
)
.await
.expect("cant insert shit");
}
client
.execute(
format!(
"UPDATE user{} SET count = count + {} where name='{}'",
id, count, name
)
.as_str(),
&[],
) )
.await .await
.expect("cant update"); .expect("Can't insert count");
}
db.execute(
format!("UPDATE user{} SET count = count + $1 WHERE name=$2", id).as_str(),
&[&count, &name],
)
.await
.expect("Can't update count");
} }
} }
} }

View File

@@ -1,16 +1,8 @@
mod interactions;
mod count; mod count;
mod interactions;
use serenity::{ use serenity::{
async_trait, async_trait,
model::{ model::{channel::Message, event::ResumedEvent, gateway::Ready},
event::ResumedEvent,
gateway::Ready,
channel::Message,
interactions::{
ApplicationCommand, Interaction, InteractionData, InteractionResponseType,
InteractionType,
},
},
prelude::*, prelude::*,
}; };
use tracing::info; use tracing::info;
@@ -19,34 +11,18 @@ pub struct Handler;
#[async_trait] #[async_trait]
impl EventHandler for Handler { impl EventHandler for Handler {
async fn ready(&self, ctx: Context, ready: Ready) { async fn ready(&self, _: Context, ready: Ready) {
info!("{} connected bhay", ready.user.name); println!("{} connected bhay", ready.user.name);
let _ = ApplicationCommand::create_global_application_commands(&ctx.http, |commands| {
commands.set_application_commands(interactions::general())
})
.await;
} }
async fn resume(&self, _: Context, _: ResumedEvent) { async fn resume(&self, _: Context, _: ResumedEvent) {
info!("how th when the"); info!("how th when the");
} }
async fn message(&self, _: Context, msg: Message) { async fn message(&self, ctx: Context, msg: Message) {
count::count(msg).await; let data_read = ctx.data.read().await;
} let db_client = data_read
.get::<crate::Database>()
async fn interaction_create(&self, ctx: Context, interaction: Interaction) { .expect("Expected Database in TypeMap.")
if interaction.kind == InteractionType::ApplicationCommand { .clone();
if let Some(InteractionData::ApplicationCommand(data)) = interaction.data.as_ref() { count::count(msg, db_client).await;
if let Err(why) = interaction
.create_interaction_response(&ctx.http, |response| {
response
.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|message| interactions::responses(data.name.to_string(), message))
})
.await
{
println!("Cannot respond to slash command: {}", why);
}
}
}
} }
} }

62
src/lib/components.rs Normal file
View File

@@ -0,0 +1,62 @@
use serenity::{
builder::{CreateComponents, CreateSelectMenu},
model::{channel::ReactionType, interactions::message_component::ButtonStyle},
};
pub fn make_range_select_menu(first: usize, last: usize) -> CreateSelectMenu {
let mut sm = CreateSelectMenu::default();
sm.custom_id("range")
.placeholder("Page No")
.options(|mut os| {
for x in first..=last {
os = os.create_option(|o| o.label(x).value(x));
}
os
});
sm
}
pub fn make_terminal_components(terminal: &str, pages: usize) -> CreateComponents {
let mut c = CreateComponents::default();
c.create_action_row(|ar| {
ar.create_button(|b| {
b.style(ButtonStyle::Primary)
.label("First")
.emoji(ReactionType::Unicode("\u{23EA}".to_string()))
.custom_id("first")
.disabled(terminal == "first")
})
.create_button(|b| {
b.style(ButtonStyle::Primary)
.label("Prev")
.emoji(ReactionType::Unicode("\u{25C0}".to_string()))
.custom_id("prev")
.disabled(terminal == "first")
})
.create_button(|b| {
b.style(ButtonStyle::Primary)
.label("Next")
.emoji(ReactionType::Unicode("\u{25B6}".to_string()))
.custom_id("next")
.disabled(terminal == "last")
})
.create_button(|b| {
b.style(ButtonStyle::Primary)
.label("Last")
.emoji(ReactionType::Unicode("\u{23E9}".to_string()))
.custom_id("last")
.disabled(terminal == "last")
})
.create_button(|b| {
b.style(ButtonStyle::Danger)
.label("Delete")
.emoji(ReactionType::Unicode("\u{1F5D1}".to_string()))
.custom_id("delete")
})
});
if pages <= 25 {
c.create_action_row(|ar| ar.add_select_menu(make_range_select_menu(1, pages)));
}
c
}

40
src/lib/messages.rs Normal file
View File

@@ -0,0 +1,40 @@
use serenity::model::channel::Message;
pub trait ExtractInfo {
fn extract_text(&self, skip: usize, with_ref: bool) -> Option<String>;
}
impl ExtractInfo for Message {
fn extract_text(&self, skip: usize, with_ref: bool) -> Option<String> {
let mut ret: String = String::from("");
let raw: Vec<&str> = self.content.splitn(skip + 1, " ").collect();
if raw.len() == skip + 1 {
ret += raw[skip];
ret += "\n";
} else if raw.len() < skip {
return None;
}
ret += &self
.attachments
.iter()
.map(|x| x.url.clone())
.collect::<Vec<String>>()
.join("\n");
if let Some(msg) = &self.referenced_message {
let ref_text = msg.extract_text(0, false);
if with_ref && !ref_text.is_none() {
ret += "\n";
ret += &ref_text.unwrap();
}
}
if ret.is_empty() {
return None;
} else {
return Some(ret);
}
}
}

2
src/lib/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod components;
pub mod messages;

View File

@@ -1,35 +1,71 @@
mod commands; mod commands;
mod handler; mod handler;
mod lib;
use commands::count::*;
use commands::general::*;
use commands::minigames::*;
use commands::tags::*;
use handler::Handler;
use serenity::{ use serenity::{
client::bridge::gateway::ShardManager, client::bridge::gateway::ShardManager,
framework::{standard::macros::group, StandardFramework}, framework::{
standard::{
help_commands,
macros::{group, help},
Args, CommandGroup, CommandResult, HelpOptions,
},
StandardFramework,
},
http::Http, http::Http,
model::{channel::Message, id::UserId},
prelude::*, prelude::*,
}; };
use std::{collections::HashSet, env, sync::Arc}; use std::{collections::HashSet, env, sync::Arc};
use tracing::error; use tracing::error;
pub struct ShardManagerContainer;
use commands::general::*;
use commands::count::*;
use commands::minigames::*;
use handler::Handler;
struct ShardManagerContainer;
impl TypeMapKey for ShardManagerContainer { impl TypeMapKey for ShardManagerContainer {
type Value = Arc<Mutex<ShardManager>>; type Value = Arc<Mutex<ShardManager>>;
} }
struct Database;
impl TypeMapKey for Database {
type Value = Arc<tokio_postgres::Client>;
}
#[group] #[group]
#[commands(ping)] #[commands(ping)]
struct General; struct General;
#[group] #[group]
#[commands(kitna,add,rm,change)] #[commands(count, cadd, cremove, cedit, clist)]
struct Count; struct Count;
#[group]
#[commands(tag, tadd, tcopy, tremove, tedit, tlist, trandom)]
pub struct Tags;
#[group] #[group]
#[commands(challenge)] #[commands(challenge)]
struct Minigames; struct Minigames;
#[help]
#[max_levenshtein_distance(2)]
#[indention_prefix = "+"]
#[lacking_role = "Nothing"]
#[wrong_channel = "Strike"]
async fn my_help(
context: &Context,
msg: &Message,
args: Args,
help_options: &'static HelpOptions,
groups: &[&'static CommandGroup],
owners: HashSet<UserId>,
) -> CommandResult {
let _ = help_commands::with_embeds(context, msg, args, help_options, groups, owners).await;
Ok(())
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let token = env::var("DISCORD_TOKEN").expect("Token daal madarchod"); let token = env::var("DISCORD_TOKEN").expect("Token daal madarchod");
@@ -47,9 +83,11 @@ async fn main() {
}; };
let framework = StandardFramework::new() let framework = StandardFramework::new()
.configure(|c| c.owners(owners).prefix("xx")) .configure(|c| c.owners(owners).prefix(","))
.help(&MY_HELP)
.group(&GENERAL_GROUP) .group(&GENERAL_GROUP)
.group(&COUNT_GROUP) .group(&COUNT_GROUP)
.group(&TAGS_GROUP)
.group(&MINIGAMES_GROUP); .group(&MINIGAMES_GROUP);
let mut client = Client::builder(&token) let mut client = Client::builder(&token)
@@ -60,7 +98,22 @@ async fn main() {
.expect("Client no wokey"); .expect("Client no wokey");
{ {
let db_url: String = env::var("DB_URL").expect("DB_URL not found");
let (db_client, conn) = tokio_postgres::connect(&db_url, tokio_postgres::NoTls)
.await
.expect("cant connect bha");
tokio::spawn(async move {
if let Err(e) = conn.await {
eprintln!("connection error: {}", e);
}
});
let init_script = std::include_str!("../init.sql");
db_client
.batch_execute(init_script)
.await
.expect("Couldn't run the init script");
let mut data = client.data.write().await; let mut data = client.data.write().await;
data.insert::<Database>(Arc::new(db_client));
data.insert::<ShardManagerContainer>(client.shard_manager.clone()); data.insert::<ShardManagerContainer>(client.shard_manager.clone());
} }