From a77ab24e0207458eaec8eae0a9f2decfaec82f31 Mon Sep 17 00:00:00 2001 From: Nix Krystik Date: Thu, 27 Aug 2026 22:26:12 +0800 Subject: [PATCH] (init): initial files. includes basic client & database template using postgres. --- .env.sample | 2 + .gitattributes | 1 + .gitignore | 6 + .npmrc | 1 + README.md | 5 + package-lock.json | 597 ++++++++++++++++++++++++++++++++ package.json | 17 + src/events/client/ready.ts | 11 + src/events/guild/guildCreate.ts | 14 + src/events/guild/guildDelete.ts | 14 + src/events/index.ts | 11 + src/events/shard/shardReady.ts | 9 + src/index.ts | 21 ++ src/types/index.d.ts | 1 + src/types/settings.d.ts | 52 +++ src/utils/database.ts | 113 ++++++ src/utils/index.ts | 1 + 17 files changed, 876 insertions(+) create mode 100644 .env.sample create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/events/client/ready.ts create mode 100644 src/events/guild/guildCreate.ts create mode 100644 src/events/guild/guildDelete.ts create mode 100644 src/events/index.ts create mode 100644 src/events/shard/shardReady.ts create mode 100644 src/index.ts create mode 100644 src/types/index.d.ts create mode 100644 src/types/settings.d.ts create mode 100644 src/utils/database.ts create mode 100644 src/utils/index.ts diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..b62c405 --- /dev/null +++ b/.env.sample @@ -0,0 +1,2 @@ +DATABASE_URL= +DISCORD_TOKEN= diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c07b5d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Directories +dist/ +node_modules/ + +# Files. +.env diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1aa135d --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +registry=https://npm.hep.gg diff --git a/README.md b/README.md new file mode 100644 index 0000000..0eb905c --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Synapse +An open-source, self-hostable moderation bot for Discord. + +## Getting Started +Getting started with Synapse is as easy as typing `docker compose up -d`. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..81ba922 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,597 @@ +{ + "name": "synapse", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "athena-prime": "4.4.0", + "dotenv": "17.4.2", + "postgres": "3.4.9" + }, + "devDependencies": { + "@types/node": "26.4.0", + "esbuild": "0.28.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "26.4.0", + "resolved": "https://npm.hep.gg/@types/node/-/node-26.4.0.tgz", + "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/athena-prime": { + "version": "4.4.0", + "resolved": "https://npm.hep.gg/athena-prime/-/athena-prime-4.4.0.tgz", + "integrity": "sha512-uXsXeDsCRR6QzYkGNIwYgSmZLiFamMEP3Aj6Js3s72HEDtnEuBvPWJrGyDgDYhNIDPZ+bMfiUeU18ON7WjjjXQ==", + "license": "NOLICENSE", + "dependencies": { + "discord-api-types": "^0.38.48", + "eventemitter3": "^5.0.1" + }, + "engines": { + "node": ">=22.15.0" + }, + "optionalDependencies": { + "ws": "^8.21.0" + } + }, + "node_modules/discord-api-types": { + "version": "0.38.53", + "resolved": "https://npm.hep.gg/discord-api-types/-/discord-api-types-0.38.53.tgz", + "integrity": "sha512-HL1zz/UuZ+bbJjA/X8Kbxx9gk8v9rJAbTeWRNYKmIdjwJ7EovjlHgoJTxcLpATfNJ+AonOtMdy3Y5MVIJAAd/A==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://npm.hep.gg/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://npm.hep.gg/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://npm.hep.gg/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/postgres": { + "version": "3.4.9", + "resolved": "https://npm.hep.gg/postgres/-/postgres-3.4.9.tgz", + "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==", + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://npm.hep.gg/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://npm.hep.gg/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8f3ecf5 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "athena-prime": "4.4.0", + "dotenv": "17.4.2", + "postgres": "3.4.9" + }, + + "devDependencies": { + "@types/node": "26.4.0", + "esbuild": "0.28.2" + }, + + "scripts": { + "start": "node dist/index.js", + "build": "esbuild 'src/**/*.ts' --platform=node --format=cjs --outdir=dist" + } +} diff --git a/src/events/client/ready.ts b/src/events/client/ready.ts new file mode 100644 index 0000000..6699b98 --- /dev/null +++ b/src/events/client/ready.ts @@ -0,0 +1,11 @@ +import { Event, CommandClient } from 'athena-prime'; + +class ReadyEvent extends Event { + event: string = 'ready' as const; + + async handle(context: CommandClient) { + await context.deployCommands(); + } +} + +export default new ReadyEvent('ready'); diff --git a/src/events/guild/guildCreate.ts b/src/events/guild/guildCreate.ts new file mode 100644 index 0000000..b59b6e5 --- /dev/null +++ b/src/events/guild/guildCreate.ts @@ -0,0 +1,14 @@ +import { CommandClient, Event, Guild } from "athena-prime"; +import { createConfiguration } from "../../utils/database"; + +// ---------- + +class GuildCreateEvent extends Event { + event: string = 'guildCreate' as const; + + async handle(context: CommandClient, guild: Guild) { + await createConfiguration(guild.id); + } +} + +export default new GuildCreateEvent('guildCreate'); diff --git a/src/events/guild/guildDelete.ts b/src/events/guild/guildDelete.ts new file mode 100644 index 0000000..a41b50d --- /dev/null +++ b/src/events/guild/guildDelete.ts @@ -0,0 +1,14 @@ +import { CommandClient, Event, Guild } from "athena-prime"; +import { deleteConfiguration } from "../../utils/database"; + +// ---------- + +class GuildDeleteEvent extends Event { + event: string = 'guildDelete' as const; + + async handle(context: CommandClient, guild: Guild | { id: string }) { + await deleteConfiguration(guild.id); + } +} + +export default new GuildDeleteEvent('guildDelete'); diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..61ec880 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,11 @@ +import guildCreate from './guild/guildCreate'; +import guildDelete from './guild/guildDelete'; +import ready from './client/ready'; +import shardReady from './shard/shardReady'; + +export default [ + guildCreate, + guildDelete, + ready, + shardReady +]; diff --git a/src/events/shard/shardReady.ts b/src/events/shard/shardReady.ts new file mode 100644 index 0000000..82bb560 --- /dev/null +++ b/src/events/shard/shardReady.ts @@ -0,0 +1,9 @@ +import { Event, CommandClient } from 'athena-prime'; + +class ShardReadyEvent extends Event { + event: string = 'shardReady' as const; + + async handle(context: CommandClient, shardId: number) {} +} + +export default new ShardReadyEvent('shardReady'); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..739b732 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,21 @@ +import { CommandClient, Constants } from "athena-prime"; +import events from "./events"; + +// ---------- + +const client = new CommandClient({ + token: process.env["DISCORD_TOKEN"]!, + + options: { + intents: [ + Constants.GatewayIntentBits.Guilds, + Constants.GatewayIntentBits.GuildMembers, + Constants.GatewayIntentBits.GuildMessages, + Constants.GatewayIntentBits.MessageContent + ], + }, +}); + +events.forEach(event => client.registerEvent(event)); + +client.connect(); diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 0000000..60c4bfa --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1 @@ +export * from './settings.d'; diff --git a/src/types/settings.d.ts b/src/types/settings.d.ts new file mode 100644 index 0000000..04c8616 --- /dev/null +++ b/src/types/settings.d.ts @@ -0,0 +1,52 @@ +// --- Bot Settings --- + +/** + * Bot Settings. + */ +export type Settings = { + branding?: BrandingSettings; +}; + +/** + * Branding settings. + */ +export type BrandingSettings = { + status?: string; +}; + +// --- Guild Configuration --- + +/** + * Guild configuration. + */ +export type Configuration = { + admin?: AdminConfiguration; + censor?: CensorConfiguration; + logging?: LoggingConfiguration; + spam?: SpamConfiguration; +}; + +/** + * Admin configuration. + */ +export type AdminConfiguration = {}; + +/** + * Censor configuration. + */ +export type CensorConfiguration = {}; + +/** + * Core configuration. + */ +export type CoreConfiguration = {}; + +/** + * Logging configuration. + */ +export type LoggingConfiguration = {}; + +/** + * Spam configuration. + */ +export type SpamConfiguration = {}; diff --git a/src/utils/database.ts b/src/utils/database.ts new file mode 100644 index 0000000..1d70a90 --- /dev/null +++ b/src/utils/database.ts @@ -0,0 +1,113 @@ +import postgres from "postgres"; +import { Configuration, Settings } from "../types"; + +// ---------- + +const client = postgres(process.env["DATABASE_URL"]!); + +// --- Settings --- + +export async function init(): Promise { + await client` + CREATE TABLE IF NOT EXISTS settings ( + id TEXT PRIMARY KEY, + config JSONB NOT NULL DEFAULT '{}'::jsonb + ); + `; + + // Infractions. + await client` + CREATE TABLE IF NOT EXISTS infractions ( + id BIGSERIAL PRIMARY KEY, + user_id TEXT NOT NULL, + guild_id TEXT NOT NULL, + moderator_id TEXT NOT NULL, + type TEXT NOT NULL, + reason TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ); + + CREATE INDEX IF NOT EXISTS infractions_user_id_idx + ON infractions (user_id); + + CREATE INDEX IF NOT EXISTS infractions_guild_id_idx + ON infractions (guild_id); + + CREATE INDEX IF NOT EXISTS infractions_user_guild_idx + ON infractions (user_id, guild_id); + `; + + // Messages + await client` + CREATE TABLE IF NOT EXISTS messages ( + message_id TEXT PRIMARY KEY, + channel_id TEXT NOT NULL, + guild_id TEXT NOT NULL, + content TEXT NOT NULL + ); + + CREATE INDEX IF NOT EXISTS messages_guild_id_idx + ON messages (guild_id); + + CREATE INDEX IF NOT EXISTS messages_channel_id_idx + ON messages (channel_id); + `; + + // Ensure global bot settings exist. + await client` + INSERT INTO settings (id, config) + VALUES (0, '{}'::jsonb) + ON CONFLICT (id) DO NOTHING; + `; +} + +export async function getSettings(): Promise { + const [row] = await client` + SELECT config FROM settings + WHERE id = '0'; + `; + + return row.config as Settings; +} + +export async function updateSettings(settings: Partial): Promise { + await client` + UPDATE settings + SET config = config || ${client.json(settings)} + WHERE id = '0'; + `; +} + +// Guild Configuration + +export async function getConfiguration(guildId: string): Promise { + const [row] = await client` + SELECT config FROM settings + WHERE guild_id = ${guildId}; + `; + + return (row?.config ?? {}) as Configuration; +} + +export async function createConfiguration(guildId: string): Promise { + await client` + INSERT INTO settings (guild_id, config) + VALUES (${guildId}, ${client.json({})}) + ON CONFLICT (guild_id) DO NOTHING; + `; +} + +export async function updateConfiguration(guildId: string, configuration: Partial): Promise { + await client` + UPDATE settings + SET config = jsonb_deep_merge(config, ${client.json(configuration)}) + WHERE guild_id = ${guildId}; + `; +} + +export async function deleteConfiguration(guildId: string): Promise { + await client` + DELETE FROM settings + WHERE guild_id = ${guildId}; + `; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..9cd8c5a --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export * from './database';