Compare commits

25 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
23 changed files with 3778 additions and 682 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/
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]
name = "singh3"
version = "0.1.0"
authors = [ "Amneesh Singh <natto@weirdnatto.in>" ]
edition = "2018"
edition = "2021"
[dependencies]
tracing = "*"
regex = "*"
regex = "1"
tokio-postgres = "*"
rand = "*"
[dependencies.serenity]
version = "0.10.*"
version = "0.10.10"
features = ["cache", "framework", "standard_framework", "rustls_backend", "unstable_discord_api", "collector"]
[dependencies.tokio]
version = "1.0"
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": {
"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": {
"locked": {
"lastModified": 1614513358,
"narHash": "sha256-LakhOx3S1dRjnh0b5Dg3mbZyH0ToC9I8Y2wKSkBaTzU=",
"lastModified": 1638122382,
"narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=",
"owner": "numtide",
"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"
},
"original": {
@@ -16,6 +51,22 @@
}
},
"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": {
"lastModified": 1622966049,
"narHash": "sha256-6g+28v94ISkVk9TBSsITVOnB2slK8plieWPIF2jo/l0=",
@@ -31,39 +82,66 @@
"type": "github"
}
},
"nixpkgs_2": {
"nixpkgs_3": {
"locked": {
"lastModified": 1617325113,
"narHash": "sha256-GksR0nvGxfZ79T91UUtWjjccxazv6Yh/MvEJ82v1Xmw=",
"owner": "nixos",
"lastModified": 1637453606,
"narHash": "sha256-Gy6cwUswft9xqsjWxFYEnx/63/qzaFUwatcbV5GF/GQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "54c1e44240d8a527a8f4892608c4bce5440c3ecb",
"rev": "8afc4e543663ca0a6a4f496262cd05233737e732",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"cargo2nix": "cargo2nix",
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay_2",
"utils": "utils"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
"flake-utils": [
"cargo2nix",
"flake-utils"
],
"nixpkgs": [
"cargo2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1623034161,
"narHash": "sha256-cbw9X+nVFcpIuBga0hkbtzXbW2fyDWBon6oUN/uQmu0=",
"lastModified": 1638152159,
"narHash": "sha256-Q0UHsm36cCxk16I/bF1rHJHxjIflESKk2ej76P39j90=",
"owner": "oxalica",
"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"
},
"original": {

View File

@@ -1,37 +1,54 @@
{
description = "A simple filehost written in rust";
description = "singh3 discord bot";
inputs = {
nixpkgs.url = github:nixos/nixpkgs/nixos-unstable;
utils.url = github:numtide/flake-utils;
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
(system:
let
overlays = [ (import rust-overlay) ];
overlays =
[
(import "${cargo2nix}/overlay")
rust-overlay.overlay
];
pkgs = import nixpkgs {
inherit system overlays;
};
rustPkgs = pkgs.rustBuilder.makePackageSet' {
rustChannel = "latest";
packageFun = import ./Cargo.nix;
};
in
{
rec {
devShell = with pkgs; mkShell {
buildInputs = [
rust-bin.nightly.latest.default
rust-analyzer
postgresql
];
};
defaultPackage = pkgs.rustPlatform.buildRustPackage rec {
pname = "singh3";
version = "0.1.0";
src = ./. ;
nativeBuildInputs = with pkgs; [
rust-bin.nightly.latest.default
];
cargoSha256 = "sha256-K+WHOEo6reNfcs7pOZZmHZfZl4pUqlykfTdqgSyVURU=";
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;
}
);
}

View File

@@ -3,3 +3,11 @@ CREATE TABLE IF NOT EXISTS words (
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;

View File

@@ -1,52 +1,71 @@
job "singh3" {
region = "global"
datacenters = [ "nazrin" ]
datacenters = ["nazrin"]
type = "service"
group "svc" {
count = 1
network {
mode = "bridge"
port "db" {
static = 5454
to = 5432
}
}
vault {
policies = [ "singh3-policy" ]
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" ]
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 = 256
memory = 128
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"}}
@@ -57,6 +76,7 @@ DISCORD_TOKEN="{{.Data.data.token}}"
{{end}}
RUST_BACKTRACE=1
EOF
destination = "${NOMAD_SECRETS_DIR}/data.env"
env = true
}

View File

@@ -1,50 +1,65 @@
use crate::lib::components::make_terminal_components;
use core::time::Duration;
use regex::Regex;
use serenity::{
builder::CreateEmbed,
collector::component_interaction_collector::ComponentInteractionCollectorBuilder,
framework::standard::{macros::command, Args, CommandResult},
futures::StreamExt,
model::{
channel::ReactionType,
interactions::{ButtonStyle, InteractionData},
prelude::*,
},
model::{interactions::InteractionResponseType, prelude::*},
prelude::*,
utils::Colour,
};
use tokio_postgres::Row;
#[command]
pub async fn kitna(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query: String = args.raw().collect::<Vec<&str>>().join(" ");
if query == "" {
msg.reply(ctx, "bruh kitna kya?").await?;
#[aliases("kitna", "c")]
pub async fn count(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
if args.len() > 2 || args.len() == 0 {
msg.reply(ctx, "Please use `,count <word> <user>`").await?;
return Ok(());
}
let query = args.single::<String>().unwrap();
let user = if args.len() == 2 {
let user = args.single::<UserId>();
match user {
Ok(id) => match id.to_user(&ctx.http).await {
Ok(u) => u,
Err(_) => {
msg.reply(ctx, "No such user").await?;
return Ok(());
}
},
Err(_) => {
msg.reply(ctx, "No such user").await?;
return Ok(());
}
}
} else {
msg.author.clone()
};
let data_read = ctx.data.read().await;
let db = data_read
.get::<crate::Database>()
.expect("Expected Database in TypeMap.")
.clone();
let id = msg.author.id.to_string();
let id = user.id.to_string();
let mut query_helper = db
.query(
format!("select name from words where '{}' ~ reg", query).as_str(),
&[],
)
.query("SELECT name FROM words WHERE $1 ~ reg", &[&query])
.await?;
if query_helper.is_empty() {
query_helper = db
.query(
format!("select name from words where name='{}'", query).as_str(),
&[],
)
.query("SELECT name FROM words WHERE name=$1", &[&query])
.await?;
if query_helper.is_empty() {
msg.reply(
ctx,
format!(
"No entry for '{}' found. If you want to add it, run ',count add {}&<regex>'",
"No entry for '{}' found. If you want to add it, run `,cadd {} <regex>`",
query, query
),
)
@@ -59,42 +74,49 @@ pub async fn kitna(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
};
for row in query_helper {
let name: &str = row.get(0);
let query_result: i32 = db
.query_one(
format!("select count from user{} where name='{}'", id, name).as_str(),
&[],
let count_query = db
.query(
format!("SELECT count FROM user{} WHERE name=$1", id).as_str(),
&[&name],
)
.await?
.get(0);
reply = reply + &format!("\n{} count for you: {}", name, query_result);
.await?;
let query_result: i32 = if count_query.is_empty() {
0
} else {
count_query[0].get(0)
};
reply += &format!("\n{} count for {}: {}", name, user.name, query_result);
}
msg.reply(ctx, reply).await?;
Ok(())
}
#[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 queries = query.splitn(2, "&").collect::<Vec<&str>>();
let queries = query.splitn(2, " ").collect::<Vec<&str>>();
if queries.len() != 2 {
msg.reply(ctx, "Please use the proper syntax: `,count add <name>&<regex>`\nIf you don't know what regex is, just do: `,count add <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?;
return Ok(());
}
if queries[1].contains(" ") {
msg.reply(ctx, "Not a valid regex").await?;
let r = Regex::new(&format!("(?i){}", queries[1]));
if r.is_err() {
msg.reply(ctx, "Please enter a valid regex").await?;
return Ok(());
}
let reg = r.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(
format!("select name, reg from words where name='{}'", queries[0]).as_str(),
&[],
)
.query("SELECT name, reg FROM words WHERE name=$1", &[&queries[0]])
.await?;
if check_existense.len() != 0 {
let reg: String = check_existense[0].get(1);
@@ -106,14 +128,8 @@ pub async fn add(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
return Ok(());
}
db.execute(
format!(
"insert into words(name, reg, owner) values('{}','(?i){}', '{}')",
queries[0],
queries[1],
msg.author.id.to_string()
)
.as_str(),
&[],
"INSERT INTO words(name, reg, owner) VALUES($1, $2, $3)",
&[&queries[0], &reg.to_string(), &msg.author.id.to_string()],
)
.await?;
msg.reply(ctx, "Added").await?;
@@ -121,7 +137,8 @@ pub async fn add(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
}
#[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(" ");
if query == "" {
msg.reply(ctx, "remove what?").await?;
@@ -133,10 +150,7 @@ pub async fn rm(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
.expect("Expected Database in TypeMap.")
.clone();
let owner = db
.query(
format!("select owner from words where name = '{}'", query).as_str(),
&[],
)
.query("SELECT owner FROM words WHERE name=$1", &[&query])
.await?;
if owner.len() == 1 {
let owner_id: String = owner[0].get(0);
@@ -145,24 +159,18 @@ pub async fn rm(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
return Ok(());
}
}
db.execute(
format!("delete from words where name='{}'", query,).as_str(),
&[],
)
db.execute("DELETE FROM words WHERE name=$1", &[&query])
.await?;
msg.reply(ctx, "Deleted if it existed").await?;
Ok(())
}
#[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 queries = query.splitn(2, "&").collect::<Vec<&str>>();
if queries.len() != 2 {
msg.reply(
ctx,
"Please use the proper syntax\n,count change <name>&<regex>",
)
msg.reply(ctx, "Please use the proper syntax\n,cedit <name>&<regex>")
.await?;
return Ok(());
}
@@ -176,10 +184,7 @@ pub async fn change(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
.expect("Expected Database in TypeMap.")
.clone();
let owner = db
.query(
format!("select owner from words where name = '{}'", queries[0]).as_str(),
&[],
)
.query("SELECT owner FROM words WHERE name=$1", &[&queries[0]])
.await?;
if owner.len() == 1 {
let owner_id: String = owner[0].get(0);
@@ -189,106 +194,93 @@ pub async fn change(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
}
}
db.execute(
format!(
"update words set reg='(?i){}' where name='{}'",
queries[1], queries[0]
)
.as_str(),
&[],
"UPDATE words SET reg=$1 WHERE name=$2",
&[&("(?i)".to_string() + queries[1]), &queries[0]],
)
.await?;
msg.reply(ctx, "Changed the value if it existed").await?;
Ok(())
}
macro_rules! make_embed {
($e: expr, $cur: expr, $group: expr) => {{
$e = $e
.title(format!("List of words: Page {}", $cur))
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: i32 = row.get(0);
for row in group {
let idx: i64 = row.get(0);
let name: String = row.get(1);
let owner_id: String = row.get(3);
$e = $e.field(
let owner_id: String = row.get(2);
e.field(
format!("{}. {}", idx, name),
format!(" by <@{}>", owner_id),
true,
false,
);
}
$e
}};
}
macro_rules! make_terminal_components {
($c: expr, $terminal: expr ) => {{
$c.create_action_row(|ar| {
ar.create_button(|b| {
b.style(ButtonStyle::Primary)
.label("Prev")
.emoji(ReactionType::Unicode("\u{2B05}".to_string()))
.custom_id("prev")
.disabled($terminal == "first")
})
.create_button(|b| {
b.style(ButtonStyle::Primary)
.label("Next")
.emoji(ReactionType::Unicode("\u{27A1}".to_string()))
.custom_id("next")
.disabled($terminal == "last")
})
})
}};
e
}
#[command]
pub async fn ls(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
#[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 * from words", &[]).await?;
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(5).collect();
let mut cur = 1;
let groups: Vec<&[Row]> = rows.chunks(size).collect();
let mut cur = 1;
let message = msg
.channel_id
.send_message(ctx, |m| {
m.embed(|mut e| make_embed!(e, cur, groups[cur - 1]))
.components(|c| make_terminal_components!(c, "first"))
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 {
if let InteractionData::MessageComponent(component) = interaction.data.as_ref().unwrap() {
match component.custom_id.as_ref() {
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| {
cur += 1;
m.create_embed(|mut e| make_embed!(e, cur, groups[cur - 1]))
.components(|c| {
make_terminal_components!(
c,
if cur == groups.len() {
"last"
} else {
"mid"
}
)
})
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;
@@ -296,25 +288,71 @@ pub async fn ls(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
}
"prev" => {
if cur != 1 {
cur -= 1;
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|m| {
cur -= 1;
m.create_embed(|mut e| make_embed!(e, cur, groups[cur - 1]))
.components(|c| {
make_terminal_components!(
c,
if cur == 1 { "first" } else { "mid" }
)
})
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::framework::standard::{
CommandResult,
macros::command
};
use serenity::prelude::*;
#[command]
pub async fn ping(ctx: &Context, msg: &Message) -> CommandResult {

View File

@@ -1,3 +1,4 @@
pub mod general;
pub mod count;
pub mod general;
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

@@ -18,46 +18,38 @@ pub async fn count(msg: Message, db: std::sync::Arc<Client>) {
&[],
)
.await
.expect("cant create a user table");
.expect("Can't create a user table");
for row in db
.query("SELECT name, reg FROM words", &[])
.await
.expect("can't get the words to count")
.expect("Can't get the words to count")
{
let name: &str = row.get(0);
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 {
let query_result = db
.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
.expect("cant select the count");
.expect("Can't select count");
if query_result.is_empty() {
db.execute(
format!(
"insert into user{} (name, count) values ('{}', 0)",
id, name
)
.as_str(),
&[],
format!("INSERT INTO user{} (name, count) values ($1, 0)", id).as_str(),
&[&name],
)
.await
.expect("cant insert shit");
.expect("Can't insert count");
}
db.execute(
format!(
"UPDATE user{} SET count = count + {} where name='{}'",
id, count, name
)
.as_str(),
&[],
format!("UPDATE user{} SET count = count + $1 WHERE name=$2", id).as_str(),
&[&count, &name],
)
.await
.expect("cant update");
.expect("Can't update count");
}
}
}

View File

@@ -2,15 +2,7 @@ mod count;
mod interactions;
use serenity::{
async_trait,
model::{
channel::Message,
event::ResumedEvent,
gateway::Ready,
interactions::{
ApplicationCommand, Interaction, InteractionData, InteractionResponseType,
InteractionType,
},
},
model::{channel::Message, event::ResumedEvent, gateway::Ready},
prelude::*,
};
use tracing::info;
@@ -19,12 +11,8 @@ pub struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn ready(&self, ctx: Context, ready: Ready) {
info!("{} connected bhay", ready.user.name);
let _ = ApplicationCommand::create_global_application_commands(&ctx.http, |commands| {
commands.set_application_commands(interactions::general())
})
.await;
async fn ready(&self, _: Context, ready: Ready) {
println!("{} connected bhay", ready.user.name);
}
async fn resume(&self, _: Context, _: ResumedEvent) {
info!("how th when the");
@@ -37,23 +25,4 @@ impl EventHandler for Handler {
.clone();
count::count(msg, db_client).await;
}
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
if interaction.kind == InteractionType::ApplicationCommand {
if let Some(InteractionData::ApplicationCommand(data)) = interaction.data.as_ref() {
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,8 +1,10 @@
mod commands;
mod handler;
mod lib;
use commands::count::*;
use commands::general::*;
use commands::minigames::*;
use commands::tags::*;
use handler::Handler;
use serenity::{
client::bridge::gateway::ShardManager,
@@ -36,10 +38,13 @@ impl TypeMapKey for Database {
struct General;
#[group]
#[prefix = "count"]
#[commands(kitna, add, rm, change, ls)]
#[commands(count, cadd, cremove, cedit, clist)]
struct Count;
#[group]
#[commands(tag, tadd, tcopy, tremove, tedit, tlist, trandom)]
pub struct Tags;
#[group]
#[commands(challenge)]
struct Minigames;
@@ -82,6 +87,7 @@ async fn main() {
.help(&MY_HELP)
.group(&GENERAL_GROUP)
.group(&COUNT_GROUP)
.group(&TAGS_GROUP)
.group(&MINIGAMES_GROUP);
let mut client = Client::builder(&token)