Add permission checks to p.prefix command

This commit is contained in:
2025-08-20 02:29:40 -03:00
parent 3cc37cfc0c
commit ee49b0a984
23 changed files with 296 additions and 766 deletions
Generated
+15 -292
View File
@@ -363,11 +363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core",
"memchr", "memchr",
"pin-project-lite",
"tokio",
"tokio-util",
] ]
[[package]] [[package]]
@@ -471,36 +467,6 @@ dependencies = [
"parking_lot_core", "parking_lot_core",
] ]
[[package]]
name = "deadpool"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ed5957ff93768adf7a65ab167a17835c3d2c3c50d084fe305174c112f468e2f"
dependencies = [
"deadpool-runtime",
"num_cpus",
"tokio",
]
[[package]]
name = "deadpool-redis"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0965b977f1244bc3783bb27cd79cfcff335a8341da18f79232d00504b18eb1a"
dependencies = [
"deadpool",
"redis",
]
[[package]]
name = "deadpool-runtime"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
dependencies = [
"tokio",
]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.4.0" version = "0.4.0"
@@ -510,17 +476,6 @@ dependencies = [
"powerfmt", "powerfmt",
] ]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "dotenvy" name = "dotenvy"
version = "0.15.7" version = "0.15.7"
@@ -771,12 +726,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]] [[package]]
name = "http" name = "http"
version = "1.3.1" version = "1.3.1"
@@ -912,119 +861,12 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "icu_collections"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]] [[package]]
name = "include_dir" name = "include_dir"
version = "0.7.4" version = "0.7.4"
@@ -1203,12 +1045,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "litemap"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.13" version = "0.4.13"
@@ -1310,31 +1146,12 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.1.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -1344,16 +1161,6 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]] [[package]]
name = "num_threads" name = "num_threads"
version = "0.1.7" version = "0.1.7"
@@ -1431,14 +1238,13 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]] [[package]]
name = "payphone" name = "payphone"
version = "4.0.15" version = "4.0.25"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
"clap", "clap",
"colored", "colored",
"csv", "csv",
"deadpool-redis",
"dotenvy", "dotenvy",
"include_dir", "include_dir",
"itertools", "itertools",
@@ -1452,11 +1258,11 @@ dependencies = [
"regex", "regex",
"scc", "scc",
"scylla", "scylla",
"serde",
"thiserror 2.0.12", "thiserror 2.0.12",
"time", "time",
"tokio", "tokio",
"tower-http", "tower-http",
"twilight-cache-inmemory",
"twilight-gateway", "twilight-gateway",
"twilight-http", "twilight-http",
"twilight-model", "twilight-model",
@@ -1490,15 +1296,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
dependencies = [
"zerovec",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@@ -1576,27 +1373,6 @@ dependencies = [
"rand_core", "rand_core",
] ]
[[package]]
name = "redis"
version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd3650deebc68526b304898b192fa4102a4ef0b9ada24da096559cb60e0eef8"
dependencies = [
"bytes",
"cfg-if",
"combine",
"futures-util",
"itoa",
"num-bigint",
"percent-encoding",
"pin-project-lite",
"ryu",
"socket2 0.6.0",
"tokio",
"tokio-util",
"url",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.17" version = "0.5.17"
@@ -2166,16 +1942,6 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tinystr"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.47.1" version = "1.47.1"
@@ -2343,6 +2109,19 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "twilight-cache-inmemory"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc9de5f02fa89a48d3fa989811d67a3f3cc3179a46ce71089afa5412ff21fb41"
dependencies = [
"bitflags 2.9.1",
"dashmap",
"serde",
"twilight-model",
"twilight-util",
]
[[package]] [[package]]
name = "twilight-gateway" name = "twilight-gateway"
version = "0.16.0" version = "0.16.0"
@@ -2477,23 +2256,6 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.2" version = "0.2.2"
@@ -2944,12 +2706,6 @@ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
] ]
[[package]]
name = "writeable"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.0" version = "0.8.0"
@@ -3020,36 +2776,3 @@ name = "zeroize"
version = "1.8.1" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+2 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "payphone" name = "payphone"
version = "4.0.15" version = "4.0.25"
edition = "2024" edition = "2024"
default-run = "payphone" default-run = "payphone"
@@ -10,7 +10,6 @@ axum = "0.8.4"
clap = { version = "4.5.42", features = ["derive", "env", "string"] } clap = { version = "4.5.42", features = ["derive", "env", "string"] }
colored = "3.0.0" colored = "3.0.0"
csv = "1.3.1" csv = "1.3.1"
deadpool-redis = "0.22.0"
dotenvy = "0.15.7" dotenvy = "0.15.7"
include_dir = "0.7.4" include_dir = "0.7.4"
itertools = "0.14.0" itertools = "0.14.0"
@@ -24,11 +23,11 @@ rand_chacha = { version = "0.9.0", features = ["os_rng"] }
regex = "1.11.1" regex = "1.11.1"
scc = "2.3.4" scc = "2.3.4"
scylla = { version = "1.3.1", features = ["time-03"] } scylla = { version = "1.3.1", features = ["time-03"] }
serde = { version = "1.0.219", features = ["derive"] }
thiserror = "2.0.12" thiserror = "2.0.12"
time = { version = "0.3.41", features = ["formatting", "local-offset", "macros"] } time = { version = "0.3.41", features = ["formatting", "local-offset", "macros"] }
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.6.6", features = ["fs"] } tower-http = { version = "0.6.6", features = ["fs"] }
twilight-cache-inmemory = { version = "0.16.0", features = ["permission-calculator"] }
twilight-gateway = "0.16.0" twilight-gateway = "0.16.0"
twilight-http = { version = "0.16.0", features = ["simd-json"] } twilight-http = { version = "0.16.0", features = ["simd-json"] }
twilight-model = "0.16.0" twilight-model = "0.16.0"
+25
View File
@@ -0,0 +1,25 @@
apiVersion: v1
kind: Namespace
metadata:
name: redis
---
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
namespace: redis
data:
update-node.sh: |
#!/bin/sh
REDIS_NODES="/data/nodes.conf"
sed -i -e "/myself/ s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/${POD_IP}/" ${REDIS_NODES}
exec "$@"
redis.conf: |+
cluster-enabled yes
cluster-require-full-coverage no
cluster-node-timeout 15000
cluster-config-file /data/nodes.conf
cluster-migration-barrier 1
save ""
appendonly no
protected-mode no
+51
View File
@@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
namespace: redis
spec:
serviceName: redis-cluster
replicas: 6
selector:
matchLabels:
app: redis-cluster
template:
metadata:
labels:
app: redis-cluster
spec:
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
name: client
- containerPort: 16379
name: gossip
command: ["/conf/update-node.sh", "redis-server", "/conf/redis.conf"]
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
volumeMounts:
- name: conf
mountPath: /conf
readOnly: false
- name: data
mountPath: /data
readOnly: false
volumes:
- name: conf
configMap:
name: redis-config
defaultMode: 0755
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: local-path
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: redis-cluster
namespace: redis
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
name: client
- port: 16379
targetPort: 16379
name: gossip
selector:
app: redis-cluster
+2 -1
View File
@@ -8,4 +8,5 @@ uv run ansible-playbook -i inventory.ini playbooks/04-prometheus-operator.yml
uv run ansible-playbook -i inventory.ini playbooks/05-scylla-operator.yml uv run ansible-playbook -i inventory.ini playbooks/05-scylla-operator.yml
uv run ansible-playbook -i inventory.ini playbooks/06-scylla-cluster.yml uv run ansible-playbook -i inventory.ini playbooks/06-scylla-cluster.yml
uv run ansible-playbook -i inventory.ini playbooks/07-scylla-manager.yml uv run ansible-playbook -i inventory.ini playbooks/07-scylla-manager.yml
uv run ansible-playbook -i inventory.ini playbooks/08-payphone.yml --vault-password-file playbooks/00-vault-pwd.txt uv run ansible-playbook -i inventory.ini playbooks/08-redis.yml
uv run ansible-playbook -i inventory.ini playbooks/09-payphone.yml --vault-password-file playbooks/00-vault-pwd.txt
+56
View File
@@ -0,0 +1,56 @@
- name: Set Up Redis Cluster
hosts: init
become: true
tasks:
- name: Create Redis Config Map
kubernetes.core.k8s:
state: present
src: /tmp/k8s/redis/01-config-map.yml
kubeconfig: /etc/rancher/k3s/k3s.yaml
- name: Create Redis Nodes
kubernetes.core.k8s:
state: present
src: /tmp/k8s/redis/02-redis.yml
kubeconfig: /etc/rancher/k3s/k3s.yaml
- name: Wait for Redis Nodes
ansible.builtin.command:
kubectl -n=redis rollout status --timeout=10m sts/redis-cluster
changed_when: false
- name: Create ClusterIP Service
kubernetes.core.k8s:
state: present
src: /tmp/k8s/redis/03-cluster-ip.yml
kubeconfig: /etc/rancher/k3s/k3s.yaml
- name: Grab Node IPs
ansible.builtin.shell:
echo $(kubectl get pods -l app=redis-cluster -n redis -o json | jq -r '.items | map(.status.podIP) | join(":6379 ")'):6379
args:
executable: /bin/bash
register: redis_node_ips
changed_when: false
tags:
- skip_ansible_lint
- name: Display Node IPs
ansible.builtin.debug:
msg: "{{ redis_node_ips.stdout }}"
- name: Check if Redis cluster is already active
ansible.builtin.shell: >
set -o pipefail
kubectl exec redis-cluster-0 -n redis -- redis-cli -p 6379 cluster info | grep cluster_state
args:
executable: /bin/bash
register: redis_cluster_state
changed_when: false
failed_when: false
- name: Activate Redis Cluster
ansible.builtin.shell: >
kubectl exec redis-cluster-0 -n redis -- redis-cli --cluster create --cluster-replicas 1 {{ redis_node_ips.stdout }} --cluster-yes
when: "'cluster_state:ok' not in redis_cluster_state.stdout"
changed_when: false
@@ -97,7 +97,7 @@
"--shard", "--shard",
"$(SHARD_ID):$(SHARD_COUNT)", "$(SHARD_ID):$(SHARD_COUNT)",
"--database-addr", "--database-addr",
"scylladb-client.scylla.svc.cluster.local", "scylladb-client.scylla",
"--database-username", "--database-username",
"cassandra", "cassandra",
"--database-password", "--database-password",
-432
View File
@@ -1,432 +0,0 @@
use std::{collections::HashMap, sync::Arc};
use clap::Parser;
use payphone::{
database::{
self,
models::{
MessageData, Report as PayphoneReport, ReportResolution, Server as PayphoneServer,
UserIncident, UserIncidentType,
},
},
utils::{Bye, get_random_u64, u64_to_i64},
};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use tokio::{sync::Semaphore, time::Instant};
#[derive(Parser)]
struct Args {
dir: String,
#[arg(long, default_value_t = String::from("127.0.0.1"))]
database_addr: String,
#[arg(long)]
database_username: Option<String>,
#[arg(long)]
database_password: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Report {
id: u64,
message_id: u64,
message_content: String,
channel_id: u64,
user_id: u64,
created_at: String,
resolved: String,
resolved_at: Option<String>,
resolved_by_id: Option<u64>,
user_avatar_url: String,
user_name: String,
reply_user_name: Option<String>,
reply_message_content: Option<String>,
reply_user_avatar_url: Option<String>,
reply_user_id: Option<u64>,
reported_by_id: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
id: u64,
is_masked: String,
is_moderator: String,
is_supporter_until: String,
warns_count: u64,
bans_count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Ban {
id: u64,
user_id: u64,
created_at: String,
expires_at: Option<String>,
created_by_id: u64,
expired_by_id: Option<u64>,
report_id: Option<u64>,
reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Server {
id: u64,
prefix: String,
language: String,
name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Warn {
id: u64,
user_id: u64,
reason: String,
created_at: String,
created_by_id: u64,
report_id: u64,
}
#[tokio::main]
async fn main() {
let args = Args::parse();
println!("Testing DB connection...");
let db = Arc::new(
database::init(
args.database_addr,
args.database_username,
args.database_password,
)
.await
.expect("Failed to initialize database"),
);
println!("Reading migration file {}/reports.csv", args.dir);
let mut rdr = csv::Reader::from_path(format!("{}/reports.csv", args.dir))
.expect("Failed to open reports file");
let mut reports = Vec::new();
let format = time::format_description::parse(
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour]",
)
.expect("Failed to parse format description");
for result in rdr.deserialize() {
let report: Report = result.expect("Failed to deserialize report");
reports.push(report);
}
println!("Finished reading reports, found {} reports", reports.len());
println!("Reading migration file {}/users.csv", args.dir);
let mut rdr = csv::Reader::from_path(format!("{}/users.csv", args.dir))
.expect("Failed to open users file");
let mut users = HashMap::new();
for result in rdr.deserialize() {
let user: User = result.expect("Failed to deserialize user");
users.insert(user.id, user).bye();
}
println!("Finished reading users, found {} users", users.len());
println!("Reading migration file {}/bans.csv", args.dir);
let mut rdr =
csv::Reader::from_path(format!("{}/bans.csv", args.dir)).expect("Failed to open bans file");
let mut bans = HashMap::new();
for result in rdr.deserialize() {
let ban: Ban = result.expect("Failed to deserialize ban");
bans.insert(ban.id, ban).bye();
}
println!("Finished reading bans, found {} bans", bans.len());
println!("Reading migration file {}/servers.csv", args.dir);
let mut rdr = csv::Reader::from_path(format!("{}/servers.csv", args.dir))
.expect("Failed to open servers file");
let mut servers = HashMap::new();
for result in rdr.deserialize() {
let server: Server = result.expect("Failed to deserialize server");
servers.insert(server.id, server).bye();
}
println!("Finished reading servers, found {} servers", servers.len());
println!("Reading migration file {}/warns.csv", args.dir);
let mut rdr = csv::Reader::from_path(format!("{}/warns.csv", args.dir))
.expect("Failed to open warns file");
let mut warns = HashMap::new();
for result in rdr.deserialize() {
let warn: Warn = result.expect("Failed to deserialize warn");
warns.insert(warn.id, warn).bye();
}
println!("Finished reading warns, found {} warns", warns.len());
println!("Starting migration...");
println!("Inserting server configurations...");
let semaphore = Arc::new(Semaphore::new(50));
let mut tasks = Vec::new();
for (i, server) in servers.values().enumerate() {
let payphone_server = PayphoneServer {
id: server.id,
prefix: server.prefix.clone(),
language: server.language.clone(),
name: server.name.clone(),
};
let task_db = db.clone();
let task_semaphore = semaphore.clone();
tasks.push(tokio::spawn(async move {
let _permit = task_semaphore.acquire().await;
let start = Instant::now();
payphone_server
.insert(&task_db)
.await
.expect("Failed to insert server");
let duration = start.elapsed();
println!(
"({i}) Inserted server {} in {:?}",
payphone_server.id, duration
);
}));
}
let bans = Arc::new(bans);
let warns = Arc::new(warns);
let reports = Arc::new(reports);
for (i, report) in reports.iter().enumerate() {
let task_db = db.clone();
let task_semaphore = semaphore.clone();
let task_report = report.clone();
let task_bans = bans.clone();
let task_warns = warns.clone();
let task_format = format.clone();
tasks.push(tokio::spawn(async move {
let _permit = task_semaphore.acquire().await;
let start = Instant::now();
let resolution = if task_report.resolved == "t" {
if task_bans.iter().any(|(_id, ban)| {
ban.report_id
.map(|report_id| task_report.id == report_id)
.unwrap_or(false)
}) {
Some(ReportResolution::Ban)
} else if task_warns
.iter()
.any(|(_id, warn)| warn.report_id == task_report.id)
{
Some(ReportResolution::Warn)
} else {
Some(ReportResolution::Skip)
}
} else {
None
};
let payphone_report = PayphoneReport {
id: task_report.id,
user_id: task_report.user_id,
message: MessageData {
id: u64_to_i64(task_report.message_id),
call_id: 0,
author_id: u64_to_i64(task_report.user_id),
author_name: task_report.user_name,
author_avatar_url: task_report.user_avatar_url,
author_mask: None,
content: task_report.message_content,
attachments: vec![],
sticker: None,
propagation_message_id: None,
propagation_channel_id: None,
replying_to_message_id: None,
is_deleted: false,
is_edited: false,
is_censored: false,
},
created_at: OffsetDateTime::parse(&task_report.created_at, &task_format)
.expect("Failed to parse created_at")
.to_utc(),
created_by_ids: task_report
.reported_by_id
.map(|id| vec![id])
.unwrap_or_default(),
replying_to: None,
reviewed_at: task_report.resolved_at.as_ref().map(|s| {
OffsetDateTime::parse(s, &task_format)
.expect("Failed to parse resolved_at")
.to_utc()
}),
resolution,
reviewed_by_id: task_report.resolved_by_id,
};
payphone_report
.insert(&task_db)
.await
.expect("Failed to insert report");
let duration = start.elapsed();
println!(
"({i}) Inserted report {} in {:?}",
payphone_report.id, duration
);
}));
}
for (i, ban) in bans.values().enumerate() {
let task_semaphore = semaphore.clone();
let task_format = format.clone();
let task_db = db.clone();
let task_reports = reports.clone();
let task_ban = Arc::new(ban.clone());
tasks.push(tokio::spawn(async move {
let _permit = task_semaphore.acquire().await;
let start = Instant::now();
let message = task_reports
.iter()
.find(|report| {
task_ban
.report_id
.map(|id| id == report.id)
.unwrap_or(false)
})
.map(|report| MessageData {
id: u64_to_i64(report.message_id),
call_id: 0,
author_id: u64_to_i64(report.user_id),
author_name: report.user_name.clone(),
author_avatar_url: report.user_avatar_url.clone(),
author_mask: None,
content: report.message_content.clone(),
attachments: vec![],
sticker: None,
propagation_message_id: None,
propagation_channel_id: None,
replying_to_message_id: None,
is_deleted: false,
is_edited: false,
is_censored: false,
});
let payphone_incident = UserIncident {
incident_id: get_random_u64(),
user_id: task_ban.user_id,
report_id: task_ban.report_id,
r#type: UserIncidentType::Ban,
message,
reason: task_ban.reason.clone(),
created_at: OffsetDateTime::parse(&task_ban.created_at, &task_format)
.expect("Failed to parse created_at")
.to_utc(),
created_by_id: Some(task_ban.created_by_id),
duration: None,
expires_at: task_ban.expires_at.as_ref().map(|expires_at| {
OffsetDateTime::parse(expires_at, &task_format)
.expect("Failed to parse expires_at")
.to_utc()
}),
};
payphone_incident
.insert(&task_db)
.await
.expect("Failed to insert ban");
let duration = start.elapsed();
println!(
"({i}) Inserted incident (ban) {} in {:?}",
payphone_incident.incident_id, duration
);
}));
}
for (i, warn) in warns.values().enumerate() {
let task_semaphore = semaphore.clone();
let task_format = format.clone();
let task_db = db.clone();
let task_reports = reports.clone();
let task_warn = Arc::new(warn.clone());
tasks.push(tokio::spawn(async move {
let _permit = task_semaphore.acquire().await;
let start = Instant::now();
let message = task_reports
.iter()
.find(|report| report.id == task_warn.report_id)
.map(|report| MessageData {
id: u64_to_i64(report.message_id),
call_id: 0,
author_id: u64_to_i64(report.user_id),
author_name: report.user_name.clone(),
author_avatar_url: report.user_avatar_url.clone(),
author_mask: None,
content: report.message_content.clone(),
attachments: vec![],
sticker: None,
propagation_message_id: None,
propagation_channel_id: None,
replying_to_message_id: None,
is_deleted: false,
is_edited: false,
is_censored: false,
});
let payphone_incident = UserIncident {
incident_id: get_random_u64(),
user_id: task_warn.user_id,
report_id: Some(task_warn.report_id),
r#type: UserIncidentType::Warning,
message,
reason: task_warn.reason.clone(),
created_at: OffsetDateTime::parse(&task_warn.created_at, &task_format)
.expect("Failed to parse created_at")
.to_utc(),
created_by_id: Some(task_warn.created_by_id),
duration: None,
expires_at: None,
};
payphone_incident
.insert(&task_db)
.await
.expect("Failed to insert warn");
let duration = start.elapsed();
println!(
"({i}) Inserted incident (warn) {} in {:?}",
payphone_incident.incident_id, duration
);
}));
}
for task in tasks {
task.await.expect("Failed to join task");
}
}
+3 -10
View File
@@ -196,21 +196,14 @@ pub enum Commands {
database_addr: String, database_addr: String,
/// The username to use when connecting to the database. /// The username to use when connecting to the database.
#[arg( #[arg(long, env = "PAYPHONE_DATABASE_USERNAME", value_name = "USERNAME")]
long,
env = "PAYPHONE_DATABASE_USERNAME",
value_name = "USERNAME"
)]
database_username: Option<String>, database_username: Option<String>,
/// The password to use when connecting to the database. /// The password to use when connecting to the database.
#[arg( #[arg(long, env = "PAYPHONE_DATABASE_PASSWORD", value_name = "PASSWORD")]
long,
env = "PAYPHONE_DATABASE_PASSWORD",
value_name = "PASSWORD"
)]
database_password: Option<String>, database_password: Option<String>,
/// The current shard being run.
#[arg( #[arg(
long, long,
short, short,
+1 -1
View File
@@ -144,7 +144,7 @@ CREATE TABLE IF NOT EXISTS payphone.user_incidents (
expires_at TIMESTAMP, expires_at TIMESTAMP,
duration ASCII, duration ASCII,
PRIMARY KEY (user_id) PRIMARY KEY (user_id, incident_id)
); );
CREATE TABLE IF NOT EXISTS payphone.server_incidents ( CREATE TABLE IF NOT EXISTS payphone.server_incidents (
+25 -1
View File
@@ -2,6 +2,7 @@ use std::{fmt::Debug, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use thiserror::Error; use thiserror::Error;
use twilight_cache_inmemory::{DefaultInMemoryCache, ResourceType};
use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt}; use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt};
use twilight_http::Client; use twilight_http::Client;
@@ -88,11 +89,12 @@ where
} }
} }
pub struct Bot<T: Send + Sync = ()> { pub struct Bot<T: Send + Sync> {
shard: ShardId, shard: ShardId,
state: Arc<T>, state: Arc<T>,
prefixes: Arc<dyn PrefixGetter<T> + Send + Sync>, prefixes: Arc<dyn PrefixGetter<T> + Send + Sync>,
intents: Intents, intents: Intents,
cached_resource_types: Vec<ResourceType>,
commands: Vec<Arc<dyn Command<T>>>, commands: Vec<Arc<dyn Command<T>>>,
components: Vec<Arc<dyn Component<T>>>, components: Vec<Arc<dyn Component<T>>>,
on_ready_handlers: Vec<Arc<dyn OnReadyHandler<T>>>, on_ready_handlers: Vec<Arc<dyn OnReadyHandler<T>>>,
@@ -113,6 +115,7 @@ impl<T: 'static + Send + Sync> Bot<T> {
state: Arc::new(state), state: Arc::new(state),
prefixes: Arc::new(prefixes), prefixes: Arc::new(prefixes),
intents: Intents::empty(), intents: Intents::empty(),
cached_resource_types: Vec::new(),
commands: Vec::new(), commands: Vec::new(),
components: Vec::new(), components: Vec::new(),
on_ready_handlers: Vec::new(), on_ready_handlers: Vec::new(),
@@ -147,6 +150,14 @@ impl<T: 'static + Send + Sync> Bot<T> {
self self
} }
pub fn cache_resource_type(mut self, resource_type: ResourceType) -> Self {
if !self.cached_resource_types.contains(&resource_type) {
self.cached_resource_types.push(resource_type);
}
self
}
pub fn on_ready(mut self, handler: impl OnReadyHandler<T> + 'static) -> Self { pub fn on_ready(mut self, handler: impl OnReadyHandler<T> + 'static) -> Self {
self.on_ready_handlers.push(Arc::new(handler)); self.on_ready_handlers.push(Arc::new(handler));
self self
@@ -219,6 +230,16 @@ impl<T: 'static + Send + Sync> Bot<T> {
let bot = Arc::new(self); let bot = Arc::new(self);
let mut resource_types = ResourceType::empty();
for resource_type in &bot.cached_resource_types {
resource_types.insert(*resource_type);
}
let cache = DefaultInMemoryCache::builder()
.resource_types(resource_types)
.build();
let runtime = Arc::new(Runtime { let runtime = Arc::new(Runtime {
http, http,
app, app,
@@ -226,6 +247,7 @@ impl<T: 'static + Send + Sync> Bot<T> {
state: bot.state.clone(), state: bot.state.clone(),
prefixes: bot.prefixes.clone(), prefixes: bot.prefixes.clone(),
commands: bot.commands.clone(), commands: bot.commands.clone(),
cache,
}); });
let mut gateway = Shard::new(bot.shard, token.to_string(), bot.intents); let mut gateway = Shard::new(bot.shard, token.to_string(), bot.intents);
@@ -236,6 +258,8 @@ impl<T: 'static + Send + Sync> Bot<T> {
let task_bot = bot.clone(); let task_bot = bot.clone();
tokio::spawn(async move { tokio::spawn(async move {
task_rt.cache.update(&event);
match event { match event {
Event::Ready(event) => { Event::Ready(event) => {
for handler in &task_bot.on_ready_handlers { for handler in &task_bot.on_ready_handlers {
-3
View File
@@ -1,3 +0,0 @@
pub struct RedisCache {
}
+3
View File
@@ -33,6 +33,9 @@ pub enum CommandError {
#[error(transparent)] #[error(transparent)]
DiscordDeserializationFailed(#[from] DeserializeBodyError), DiscordDeserializationFailed(#[from] DeserializeBodyError),
#[error("The cache's state was inconsistent.")]
CacheStateInconsistent,
} }
#[async_trait] #[async_trait]
-1
View File
@@ -158,7 +158,6 @@
//! ``` //! ```
pub mod bot; pub mod bot;
pub mod cache;
pub mod commands; pub mod commands;
pub mod events; pub mod events;
pub mod interactions; pub mod interactions;
+3
View File
@@ -19,6 +19,7 @@
use std::sync::Arc; use std::sync::Arc;
use twilight_cache_inmemory::InMemoryCache;
use twilight_http::Client; use twilight_http::Client;
use twilight_model::oauth::Application; use twilight_model::oauth::Application;
@@ -45,6 +46,8 @@ pub struct Runtime<T> {
/// The application state. /// The application state.
pub state: Arc<T>, pub state: Arc<T>,
pub cache: InMemoryCache,
} }
impl<T> Runtime<T> { impl<T> Runtime<T> {
@@ -4,3 +4,7 @@ stupid
dumb dumb
moron moron
fool fool
stfu
syfm
sybau
gtfo
@@ -75,6 +75,8 @@ goonbeam
goonbeaming goonbeaming
gooncream gooncream
gooncreaming gooncreaming
goonbud
goonbuds
boty hole boty hole
suck my pp suck my pp
ass cracker ass cracker
@@ -98,3 +100,22 @@ tickle your pickle
tickle yr pickle tickle yr pickle
tickle his pickle tickle his pickle
tickle my pickle tickle my pickle
peg
peged
peging
butbang
butbanged
eat my ass
eat my ahh
eat mi ass
eat my ahh
eat my bum
eat mi bum
ima touch you
ima touch u
il touch you
il touch u
im going to touch you
im going to touch u
im gonna touch you
im gonna touch u
@@ -32,15 +32,17 @@ dumb fck
dumb fuk dumb fuk
dumb fuh dumb fuh
dumb fuhk dumb fuhk
stfu shut the fvck up
shut the fck up
shut the fuck up shut the fuck up
syfm
shut your fucking mouth shut your fucking mouth
sybau shut your fcking mouth
shut your fvcking mouth
shut your bitch as up shut your bitch as up
shut your bih as up shut your bih as up
gtfo
get the fuck out get the fuck out
get the fvck out
get the fck out
get tfo
blackie blackie
motherfucker motherfucker
mf
+9
View File
@@ -8,7 +8,9 @@ use crate::{
Command, CommandError, Command, CommandError,
arguments::{Argument, GreedyString, ParseArguments}, arguments::{Argument, GreedyString, ParseArguments},
context::CommandContext, context::CommandContext,
permissions::Permission,
}, },
groups::servers::permissions::{GuildOnlyPermission, ServerModeratorPermission},
state::State, state::State,
}; };
@@ -37,6 +39,13 @@ impl Command<State> for PrefixCommand {
}] }]
} }
fn permissions(&self) -> Vec<Box<dyn Permission<State> + Send + Sync + 'static>> {
vec![
Box::new(GuildOnlyPermission),
Box::new(ServerModeratorPermission),
]
}
async fn execute( async fn execute(
&self, &self,
ctx: CommandContext<'_, State>, ctx: CommandContext<'_, State>,
+41 -2
View File
@@ -1,4 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use twilight_model::guild::Permissions;
use crate::{ use crate::{
framework::commands::{CommandError, context::CommandContext, permissions::Permission}, framework::commands::{CommandError, context::CommandContext, permissions::Permission},
@@ -11,7 +12,15 @@ pub struct GuildOnlyPermission;
#[async_trait] #[async_trait]
impl Permission<State> for GuildOnlyPermission { impl Permission<State> for GuildOnlyPermission {
async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> { async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> {
Ok(ctx.event.guild_id.is_none()) Ok(ctx.event.guild_id.is_some())
}
async fn on_handle_denied(&self, ctx: &CommandContext<State>) -> Result<(), CommandError> {
ctx.send()
.content("❌ This command can only be used in a server.")
.await?;
Ok(())
} }
} }
@@ -20,7 +29,37 @@ pub struct ServerModeratorPermission;
#[async_trait] #[async_trait]
impl Permission<State> for ServerModeratorPermission { impl Permission<State> for ServerModeratorPermission {
async fn check(&self, _ctx: &CommandContext<State>) -> Result<bool, CommandError> { async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> {
if let Some(guild_id) = ctx.event.guild_id {
let permissions = ctx
.rt
.cache
.permissions()
.root(ctx.event.author.id, guild_id);
if let Ok(permissions) = permissions {
let guild = ctx
.rt
.cache
.guild(guild_id)
.ok_or(CommandError::CacheStateInconsistent)?;
Ok(permissions
.contains(Permissions::MANAGE_CHANNELS | Permissions::MANAGE_MESSAGES)
|| guild.owner_id() == ctx.event.author.id)
} else {
Err(CommandError::CacheStateInconsistent)
}
} else {
Ok(true) Ok(true)
} }
} }
async fn on_handle_denied(&self, ctx: &CommandContext<State>) -> Result<(), CommandError> {
ctx.send()
.content("❌ This command can only be run by a server moderator.\n-# 🛈 Moderators must have permission to manage messages and to manage channels.")
.await?;
Ok(())
}
}
-9
View File
@@ -1,9 +0,0 @@
pub mod cli;
pub mod constants;
pub mod database;
pub mod discord;
pub mod framework;
pub mod groups;
pub mod logging;
pub mod state;
pub mod utils;
+7 -1
View File
@@ -113,6 +113,7 @@ use clap::Parser;
use notify::{Event, EventKind, Watcher}; use notify::{Event, EventKind, Watcher};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use twilight_cache_inmemory::ResourceType;
use twilight_gateway::{Intents, ShardId}; use twilight_gateway::{Intents, ShardId};
use crate::{ use crate::{
@@ -227,7 +228,7 @@ async fn start(
.await .await
.expect("Could not connect to the database!"); .expect("Could not connect to the database!");
log::info!("Database connection established successfully!"); log::info!("Database connection established successfully.");
let state = State { db }; let state = State { db };
@@ -235,6 +236,7 @@ async fn start(
.shard(shard) .shard(shard)
.intent(Intents::GUILDS) .intent(Intents::GUILDS)
.intent(Intents::GUILD_MESSAGES) .intent(Intents::GUILD_MESSAGES)
.intent(Intents::GUILD_MEMBERS)
.intent(Intents::MESSAGE_CONTENT) .intent(Intents::MESSAGE_CONTENT)
.command(HelpCommand) .command(HelpCommand)
.command(CallCommand::new()) .command(CallCommand::new())
@@ -243,6 +245,10 @@ async fn start(
.command(FriendRequestCommand) .command(FriendRequestCommand)
.command(PrefixCommand) .command(PrefixCommand)
.component(HelpGoBackButton) .component(HelpGoBackButton)
.cache_resource_type(ResourceType::GUILD)
.cache_resource_type(ResourceType::CHANNEL)
.cache_resource_type(ResourceType::MEMBER)
.cache_resource_type(ResourceType::ROLE)
.on_ready(ReadyHandler) .on_ready(ReadyHandler)
.on_reconnect(ReconnectHandler) .on_reconnect(ReconnectHandler)
.on_message(CallMessageHandler) .on_message(CallMessageHandler)