generated from nhcarrigan/template
Compare commits
No commits in common. "ebc793d833f7e6b4720ac155893c7e00464641f9" and "9c48f600515ac45d27e7958d2412eee8e78ea2b7" have entirely different histories.
ebc793d833
...
9c48f60051
@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@nhcarrigan",
|
|
||||||
"rules": {
|
|
||||||
"camelcase": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"allow": [
|
|
||||||
"serverId_userId",
|
|
||||||
"userId_serverId",
|
|
||||||
"pull_request",
|
|
||||||
"issue_number",
|
|
||||||
"issue_comment",
|
|
||||||
"serverId_level_roleId",
|
|
||||||
"serverId_roleId",
|
|
||||||
"invites_disabled_until",
|
|
||||||
"dms_disabled_until"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["src/server/github/*.ts"],
|
|
||||||
"rules": {
|
|
||||||
"camelcase": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"allow": ["icon_url"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"jsdoc/require-jsdoc": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["src/modules/subcommands/config/*.ts"],
|
|
||||||
"rules": {
|
|
||||||
"jsdoc/require-param": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["src/modules/data/*.ts"],
|
|
||||||
"rules": {
|
|
||||||
"require-atomic-updates": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
69
.gitea/issue_template/bug_report.yaml
Normal file
69
.gitea/issue_template/bug_report.yaml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
name: 🐛 Bug Report
|
||||||
|
description: Something isn't working as expected? Let us know!
|
||||||
|
title: '[BUG] - '
|
||||||
|
labels:
|
||||||
|
- "status/awaiting triage"
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: attestations
|
||||||
|
attributes:
|
||||||
|
label: Attestations
|
||||||
|
description: "By checking the boxes below, I certify that:"
|
||||||
|
options:
|
||||||
|
- label: "I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have confirmed that the issue I am opening is unique, and has not already been reported (whether closed or not).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have reviewed the [Security Policy](https://docs.nhcarrigan.com/legal/security/) and have determined that this is not a security vulnerability.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: "Describe your Issue:"
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: reproduce
|
||||||
|
attributes:
|
||||||
|
label: Can you reproduce this issue?
|
||||||
|
options:
|
||||||
|
- Yes
|
||||||
|
- No
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: "Steps to Reproduce:"
|
||||||
|
description: Steps to reproduce the behavior.
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: "Operating System:"
|
||||||
|
description: The operating system you are using, including the version/build number.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
# Remove this section for non-web apps.
|
||||||
|
- type: input
|
||||||
|
id: browser
|
||||||
|
attributes:
|
||||||
|
label: "Browser:"
|
||||||
|
description: The browser you are using, including the version number.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Are you willing and able to contribute a fix?
|
||||||
|
options:
|
||||||
|
- Yes
|
||||||
|
- No
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
5
.gitea/issue_template/config.yml
Normal file
5
.gitea/issue_template/config.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: "Discord"
|
||||||
|
url: "https://chat.nhcarrigan.com"
|
||||||
|
about: "Chat with us directly."
|
46
.gitea/issue_template/feature_proposal.yml
Normal file
46
.gitea/issue_template/feature_proposal.yml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: 💭 Feature Proposal
|
||||||
|
description: Have an idea for how we can improve? Share it here!
|
||||||
|
title: '[FEAT] - '
|
||||||
|
labels:
|
||||||
|
- "status/awaiting triage"
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: attestations
|
||||||
|
attributes:
|
||||||
|
label: Attestations
|
||||||
|
description: "By checking the boxes below, I certify that:"
|
||||||
|
options:
|
||||||
|
- label: "I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have confirmed that the issue I am opening is unique, and has not already been reported (whether closed or not).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have reviewed the [Security Policy](https://docs.nhcarrigan.com/legal/security/) and have determined that this is not a security vulnerability.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: "Describe your Idea:"
|
||||||
|
description: A clear and concise description of the feature you would like added.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: "What problem does this feature solve?"
|
||||||
|
description: Why are you requesting this feature? How would it improve your experience with the product?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Are you willing and able to contribute this feature?
|
||||||
|
options:
|
||||||
|
- Yes
|
||||||
|
- No
|
||||||
|
validations:
|
||||||
|
required: true
|
34
.gitea/issue_template/other.yml
Normal file
34
.gitea/issue_template/other.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: ❓ Other Issue
|
||||||
|
description: I have something that is neither a bug nor a feature request.
|
||||||
|
title: '[OTHER] - '
|
||||||
|
labels:
|
||||||
|
- "status/awaiting triage"
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: attestations
|
||||||
|
attributes:
|
||||||
|
label: Attestations
|
||||||
|
description: "By checking the boxes below, I certify that:"
|
||||||
|
options:
|
||||||
|
- label: "I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have confirmed that the issue I am opening is unique, and has not already been reported (whether closed or not).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have reviewed the [Security Policy](https://docs.nhcarrigan.com/legal/security/) and have determined that this is not a security vulnerability.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: This is not a feature request or bug report that I am mis-filing to avoid the issue template.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: "Share your thoughts:"
|
||||||
|
description: Why are you opening this issue?
|
||||||
|
validations:
|
||||||
|
required: true
|
91
.gitea/pull_request_template.yml
Normal file
91
.gitea/pull_request_template.yml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
name: "Pull Request Template"
|
||||||
|
about: "Template for pulls"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: explain
|
||||||
|
attributes:
|
||||||
|
label: "Explanation"
|
||||||
|
description: "Briefly explain WHY this pull request is necessary. Do not explain what it does, as that's evidenced in the changes."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: issue
|
||||||
|
attributes:
|
||||||
|
label: "Issue"
|
||||||
|
description: "My pull request relates to or resolves the following issue number:"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
is_number: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: attestations
|
||||||
|
attributes:
|
||||||
|
label: Attestations
|
||||||
|
description: "By checking the boxes below, I certify that:"
|
||||||
|
options:
|
||||||
|
- label: "I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: dependencies
|
||||||
|
attributes:
|
||||||
|
label: Dependencies
|
||||||
|
description: "My pull request adds or updates dependencies, so:"
|
||||||
|
options:
|
||||||
|
- label: I have pinned the dependencies to a specific patch version.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: checkboxes
|
||||||
|
id: style
|
||||||
|
attributes:
|
||||||
|
label: Style
|
||||||
|
description: "My contribution adheres to the following style guidelines:"
|
||||||
|
options:
|
||||||
|
- label: I have run the linter and resolved any errors.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: My pull request uses an appropriate title, matching the conventional commit standards.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: tests
|
||||||
|
attributes:
|
||||||
|
label: Tests
|
||||||
|
description: "My contribution includes the following tests:"
|
||||||
|
options:
|
||||||
|
- label: My contribution adds new code, and I have added tests to cover it.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- label: My contribution modifies existing code, and I have updated the tests to reflect these changes.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- label: All new and existing tests pass locally with my changes.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- label: Code coverage remains at or above the configured threshold.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: docs
|
||||||
|
attributes:
|
||||||
|
label: Documentation
|
||||||
|
description: "I have made the following PR to update the documentation site with my changes:"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Versioning
|
||||||
|
description: "I believe my changes should be included in the following release:"
|
||||||
|
options:
|
||||||
|
- "Major - My pull request introduces a breaking change."
|
||||||
|
- "Minor - My pull request introduces a new non-breaking feature."
|
||||||
|
- "Patch - My pull request introduces bug fixes ONLY."
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
/node_modules/
|
|
||||||
/prod/
|
|
||||||
.env
|
|
||||||
.scannerwork
|
|
15
.knip.jsonc
15
.knip.jsonc
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://unpkg.com/knip@2/schema.json",
|
|
||||||
"entry": [
|
|
||||||
"src/index.ts",
|
|
||||||
/**
|
|
||||||
* Because the commands are dynamically generated, knip can't follow them.
|
|
||||||
* Treat them as entry files to allow for dependency and import/export validation.
|
|
||||||
*/
|
|
||||||
"src/commands/*.ts",
|
|
||||||
"src/contexts/*.ts"
|
|
||||||
],
|
|
||||||
"project": ["src/**/*.ts"],
|
|
||||||
"ignore": [],
|
|
||||||
"ignoreDependencies": []
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
"@nhcarrigan/prettier-config"
|
|
20
README.md
20
README.md
@ -1,6 +1,20 @@
|
|||||||
# Mod Bot
|
# New Repository Template
|
||||||
|
|
||||||
My personal mod bot
|
This template contains all of our basic files for a new GitHub repository. There is also a handy workflow that will create an issue on a new repository made from this template, with a checklist for the steps we usually take in setting up a new repository.
|
||||||
|
|
||||||
|
If you're starting a Node.JS project with TypeScript, we have a [specific template](https://github.com/naomi-lgbt/nodejs-typescript-template) for that purpose.
|
||||||
|
|
||||||
|
## Readme
|
||||||
|
|
||||||
|
Delete all of the above text (including this line), and uncomment the below text to use our standard readme template.
|
||||||
|
|
||||||
|
<!-- # Project Name
|
||||||
|
|
||||||
|
Project Description
|
||||||
|
|
||||||
|
## Live Version
|
||||||
|
|
||||||
|
This page is currently deployed. [View the live website.]
|
||||||
|
|
||||||
## Feedback and Bugs
|
## Feedback and Bugs
|
||||||
|
|
||||||
@ -22,4 +36,4 @@ Copyright held by Naomi Carrigan.
|
|||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
We may be contacted through our [Chat Server](http://chat.nhcarrigan.com) or via email at `contact@nhcarrigan.com`.
|
We may be contacted through our [Chat Server](http://chat.nhcarrigan.com) or via email at `contact@nhcarrigan.com`. -->
|
||||||
|
464
export.csv
464
export.csv
@ -1,464 +0,0 @@
|
|||||||
userId,birthday
|
|
||||||
960685326425276467,950947200000
|
|
||||||
593911525396447252,9.544896e+11
|
|
||||||
806306626779742238,9.571644e+11
|
|
||||||
369158860906299393,9.47232e+11
|
|
||||||
897994614139682816,9.70902e+11
|
|
||||||
301972134471663616,9.664956e+11
|
|
||||||
814106185615540244,9.724572e+11
|
|
||||||
930992122004381746,9.499104e+11
|
|
||||||
842325321990406144,9.468864e+11
|
|
||||||
804601870454751233,9.525024e+11
|
|
||||||
942411503980642305,9.510336e+11
|
|
||||||
884137592910659714,9.776448e+11
|
|
||||||
952725532590604319,9.620892e+11
|
|
||||||
817257291081842728,9.482688e+11
|
|
||||||
836140378985594930,9.630396e+11
|
|
||||||
706727650663596073,9.766944e+11
|
|
||||||
942773725491785798,9.530208e+11
|
|
||||||
799701704521547828,9.54576e+11
|
|
||||||
883789675200913429,9.568188e+11
|
|
||||||
751930684679127091,9.693468e+11
|
|
||||||
920308846809985034,9.7047e+11
|
|
||||||
751434580460044298,9.777312e+11
|
|
||||||
937849237545320508,9.571644e+11
|
|
||||||
762170361486770196,9.655452e+11
|
|
||||||
969858846015238184,9.737568e+11
|
|
||||||
925855248902402138,9.739296e+11
|
|
||||||
986617067547529267,9.608796e+11
|
|
||||||
889603830738739211,9.749664e+11
|
|
||||||
958318665755922462,9.537984e+11
|
|
||||||
982393273446441010,9.58374e+11
|
|
||||||
949446766187667477,9.734112e+11
|
|
||||||
969491819014520882,9.715068e+11
|
|
||||||
978141442524471386,9.469728e+11
|
|
||||||
501147423603949568,9.469728e+11
|
|
||||||
850106457471189067,9.470592e+11
|
|
||||||
883623136883515442,9.471456e+11
|
|
||||||
814497580679561256,9.471456e+11
|
|
||||||
536306727482818573,9.473184e+11
|
|
||||||
968335702620266527,9.473184e+11
|
|
||||||
864468315756822528,9.474048e+11
|
|
||||||
940054871686651974,9.477504e+11
|
|
||||||
929252976256778251,9.477504e+11
|
|
||||||
748954804298186893,9.478368e+11
|
|
||||||
829236923951480852,9.482688e+11
|
|
||||||
569996979086819349,9.484416e+11
|
|
||||||
888500205287247932,9.484416e+11
|
|
||||||
847509003214127114,9.484416e+11
|
|
||||||
892914216976154634,9.487008e+11
|
|
||||||
830696113291919389,9.487872e+11
|
|
||||||
978249464030584843,9.4896e+11
|
|
||||||
926800470616461353,9.49392e+11
|
|
||||||
900175581193338900,9.49824e+11
|
|
||||||
916960888861392896,9.499104e+11
|
|
||||||
809579580318416897,9.499104e+11
|
|
||||||
885625974887186495,9.501696e+11
|
|
||||||
970862084692803644,9.50256e+11
|
|
||||||
817972713188360253,9.510336e+11
|
|
||||||
528887802386055179,9.513792e+11
|
|
||||||
933364111885086780,9.513792e+11
|
|
||||||
911030353949507644,9.517248e+11
|
|
||||||
125851949131235328,9.518112e+11
|
|
||||||
828997905025204244,9.521568e+11
|
|
||||||
963338353644818482,9.526752e+11
|
|
||||||
954079146932326431,9.533664e+11
|
|
||||||
776866758933479434,9.537984e+11
|
|
||||||
927782055645962270,9.540576e+11
|
|
||||||
964177218660102244,9.544896e+11
|
|
||||||
927747007857172520,9.546624e+11
|
|
||||||
923716338474688652,9.54918e+11
|
|
||||||
698916189962895470,9.550908e+11
|
|
||||||
772100671271862292,9.550908e+11
|
|
||||||
938297364387606548,9.550908e+11
|
|
||||||
896276665397813318,9.554364e+11
|
|
||||||
883909693976739881,9.563004e+11
|
|
||||||
782025516668289075,9.564732e+11
|
|
||||||
943321273575038976,9.565596e+11
|
|
||||||
798279539158941716,9.56646e+11
|
|
||||||
869849413592449064,9.5751e+11
|
|
||||||
737642652329050112,9.575964e+11
|
|
||||||
822853107871514684,9.582012e+11
|
|
||||||
960353974798647376,9.58374e+11
|
|
||||||
929168350544810004,9.584604e+11
|
|
||||||
949735064374239334,9.584604e+11
|
|
||||||
405583819786289152,9.584604e+11
|
|
||||||
947041852127854592,9.585468e+11
|
|
||||||
902936994156544111,9.587196e+11
|
|
||||||
898030525573386290,9.589788e+11
|
|
||||||
923373033194917918,9.591516e+11
|
|
||||||
949966822273331230,9.591516e+11
|
|
||||||
968976718545174529,9.591516e+11
|
|
||||||
392171690672521218,9.593244e+11
|
|
||||||
884854878751567902,9.593244e+11
|
|
||||||
793752852367540235,9.593244e+11
|
|
||||||
921211997406756954,9.594108e+11
|
|
||||||
795731344784752701,9.594972e+11
|
|
||||||
786504021417918494,9.595836e+11
|
|
||||||
909975385301712948,9.600156e+11
|
|
||||||
789271116433850398,9.601884e+11
|
|
||||||
727451062729965598,9.602748e+11
|
|
||||||
938771903328419901,9.602748e+11
|
|
||||||
903677510821220383,9.604476e+11
|
|
||||||
888350648935002113,9.604476e+11
|
|
||||||
777874981072928768,9.60534e+11
|
|
||||||
961635755107696761,9.606204e+11
|
|
||||||
320350590091919361,9.607068e+11
|
|
||||||
947616232646017085,9.608796e+11
|
|
||||||
899571770334523402,9.610524e+11
|
|
||||||
901423313551773706,9.611388e+11
|
|
||||||
933270792421134366,9.613116e+11
|
|
||||||
939754221807501342,9.61398e+11
|
|
||||||
929652531934691419,9.6183e+11
|
|
||||||
933736442356715540,9.6183e+11
|
|
||||||
927939361037754399,9.619164e+11
|
|
||||||
830705497798869012,9.619164e+11
|
|
||||||
907848580092526622,9.620892e+11
|
|
||||||
952330255622864928,9.621756e+11
|
|
||||||
773386458475659285,9.62694e+11
|
|
||||||
624943341003341824,9.628668e+11
|
|
||||||
883785280023302164,9.629532e+11
|
|
||||||
731578489588809850,9.630396e+11
|
|
||||||
876113741157138475,9.63126e+11
|
|
||||||
954268835127640084,9.632988e+11
|
|
||||||
952339587135590420,9.632988e+11
|
|
||||||
884455553361920100,9.634716e+11
|
|
||||||
857271670876143636,9.634716e+11
|
|
||||||
957140361208283166,9.636444e+11
|
|
||||||
355163845330731019,9.637308e+11
|
|
||||||
944742898707099699,9.637308e+11
|
|
||||||
955926904169447444,9.638172e+11
|
|
||||||
818024688122462218,9.638172e+11
|
|
||||||
753353117419962388,9.639036e+11
|
|
||||||
969766207785934898,9.640764e+11
|
|
||||||
916072244504035359,9.642492e+11
|
|
||||||
870697425453584444,9.64422e+11
|
|
||||||
863804410521452614,9.645948e+11
|
|
||||||
946617741744406530,9.64854e+11
|
|
||||||
751780892585885716,9.651132e+11
|
|
||||||
706860532195524720,9.65286e+11
|
|
||||||
873030420919377951,9.654588e+11
|
|
||||||
926993801581572127,9.655452e+11
|
|
||||||
892987879423348777,9.65718e+11
|
|
||||||
928935641587261440,9.659772e+11
|
|
||||||
976400825532436500,9.660636e+11
|
|
||||||
619944471726915584,9.664092e+11
|
|
||||||
853697904529899531,9.664092e+11
|
|
||||||
690240980925808652,9.66582e+11
|
|
||||||
822804221425614903,9.669276e+11
|
|
||||||
745682978822291602,9.671868e+11
|
|
||||||
962098921482633246,9.675324e+11
|
|
||||||
807440022282305556,9.677916e+11
|
|
||||||
871906778684747778,9.67878e+11
|
|
||||||
797727287968923649,9.679644e+11
|
|
||||||
750469279131893760,9.679644e+11
|
|
||||||
822624645374279713,9.681372e+11
|
|
||||||
498134028768116736,9.683964e+11
|
|
||||||
938612713901396029,9.684828e+11
|
|
||||||
813983780452171806,9.684828e+11
|
|
||||||
903991458472800297,9.688284e+11
|
|
||||||
892462297962926130,9.690012e+11
|
|
||||||
860523345890902018,9.690876e+11
|
|
||||||
817975261748002836,9.69174e+11
|
|
||||||
725034380061442058,9.692604e+11
|
|
||||||
865665545646899222,9.694332e+11
|
|
||||||
766085113514950688,9.694332e+11
|
|
||||||
871119390551183391,9.695196e+11
|
|
||||||
821207737253494804,9.701244e+11
|
|
||||||
862420898852896797,9.702972e+11
|
|
||||||
925757921869070336,9.705564e+11
|
|
||||||
982551055231492136,9.707292e+11
|
|
||||||
677085534920900650,9.710748e+11
|
|
||||||
911739781048827964,9.711612e+11
|
|
||||||
853995029643264030,9.711612e+11
|
|
||||||
917903365453779074,9.71334e+11
|
|
||||||
851599010308423710,9.719388e+11
|
|
||||||
897553187723182120,9.720252e+11
|
|
||||||
869067139938664460,9.722844e+11
|
|
||||||
834910688983384156,9.722844e+11
|
|
||||||
908168554409496596,9.724572e+11
|
|
||||||
784535670932373515,9.7263e+11
|
|
||||||
801027228208726046,9.727164e+11
|
|
||||||
909520793241813024,9.728928e+11
|
|
||||||
870252602238066708,9.73152e+11
|
|
||||||
828145876873641985,9.73584e+11
|
|
||||||
939127356931600434,9.738432e+11
|
|
||||||
934907505946021939,9.739296e+11
|
|
||||||
881406978969174038,9.74016e+11
|
|
||||||
815144239893577728,9.74016e+11
|
|
||||||
848601063485472818,9.74448e+11
|
|
||||||
342990951834189824,9.74448e+11
|
|
||||||
943133386329968712,9.747936e+11
|
|
||||||
905204204241584161,9.75312e+11
|
|
||||||
815994799769124874,9.755712e+11
|
|
||||||
876897882173739018,9.759168e+11
|
|
||||||
667192664202149898,9.759168e+11
|
|
||||||
887128769071038464,9.76176e+11
|
|
||||||
890997720930721823,9.763488e+11
|
|
||||||
889639434927095869,9.763488e+11
|
|
||||||
805802130064670751,9.766944e+11
|
|
||||||
707976108104220673,9.768672e+11
|
|
||||||
970360770036641852,9.769536e+11
|
|
||||||
901092878376386600,9.773856e+11
|
|
||||||
740133215687409674,9.77472e+11
|
|
||||||
825749318140166144,9.77904e+11
|
|
||||||
986737571369480232,9.547452e+11
|
|
||||||
716005462654320741,9.507744e+11
|
|
||||||
753493140681523260,9.482688e+11
|
|
||||||
942122987149230191,9.645948e+11
|
|
||||||
723587327343067187,9.559548e+11
|
|
||||||
808534409136701472,9.488736e+11
|
|
||||||
713234483972669451,9.723708e+11
|
|
||||||
861765725429039104,9.739296e+11
|
|
||||||
943284431312023552,9.780768e+11
|
|
||||||
988711957244821514,9.684828e+11
|
|
||||||
465650873650118659,9.639036e+11
|
|
||||||
906540161251303465,9.743616e+11
|
|
||||||
885786249867177984,9.54918e+11
|
|
||||||
964698414837272587,9.74016e+11
|
|
||||||
986242698744823808,9.50256e+11
|
|
||||||
993145412233994330,9.6399e+11
|
|
||||||
994034635988287619,9.638172e+11
|
|
||||||
781227238745833513,9.769536e+11
|
|
||||||
999713824611512390,9.492192e+11
|
|
||||||
856553597352148992,9.73584e+11
|
|
||||||
996092350944247878,9.671004e+11
|
|
||||||
716784769874133042,9.663228e+11
|
|
||||||
466350927800958976,9.742752e+11
|
|
||||||
886741601928486914,9.491328e+11
|
|
||||||
962530967799619584,9.67878e+11
|
|
||||||
933714145109491722,9.730656e+11
|
|
||||||
978325559807459398,9.72198e+11
|
|
||||||
456109470284644362,9.552636e+11
|
|
||||||
1000385188460773466,9.491328e+11
|
|
||||||
830973274720174100,9.468864e+11
|
|
||||||
1005415001877655563,9.7047e+11
|
|
||||||
783417366708486154,9.483552e+11
|
|
||||||
934212646696259634,9.76176e+11
|
|
||||||
947582240353812580,9.64854e+11
|
|
||||||
675462080488407040,9.544032e+11
|
|
||||||
967186748553695242,9.750528e+11
|
|
||||||
658623086266023967,9.5112e+11
|
|
||||||
931498801468936222,9.512928e+11
|
|
||||||
880972339339223122,9.486144e+11
|
|
||||||
745024269255442454,9.529344e+11
|
|
||||||
1018935880792166420,9.671004e+11
|
|
||||||
993504739373363240,9.508608e+11
|
|
||||||
983097023261712524,9.634716e+11
|
|
||||||
969459597867896853,9.551772e+11
|
|
||||||
699310108693495851,9.681372e+11
|
|
||||||
998018739817693205,9.536256e+11
|
|
||||||
1026133045343953008,9.747936e+11
|
|
||||||
1013502860090155080,9.54144e+11
|
|
||||||
1004202436807897099,9.703836e+11
|
|
||||||
1042061436236611645,9.590652e+11
|
|
||||||
888458203577327686,9.51984e+11
|
|
||||||
1013685906139533385,9.664092e+11
|
|
||||||
940514631636631593,9.499968e+11
|
|
||||||
616678407253655612,9.561276e+11
|
|
||||||
1043477069914452049,9.5112e+11
|
|
||||||
447224284243427329,9.484416e+11
|
|
||||||
840804417476362270,9.561276e+11
|
|
||||||
929838053332246549,9.781632e+11
|
|
||||||
766382066852954192,9.518976e+11
|
|
||||||
1050487602412789821,9.680508e+11
|
|
||||||
885464067240779816,9.47232e+11
|
|
||||||
1032714210133688380,9.689148e+11
|
|
||||||
1056146445889183796,9.614844e+11
|
|
||||||
1047295595074830336,9.500832e+11
|
|
||||||
1003207378184982538,9.47232e+11
|
|
||||||
1043104995194716170,9.743616e+11
|
|
||||||
923369644520247296,9.606204e+11
|
|
||||||
1032574355516969002,9.540576e+11
|
|
||||||
1002390101227208714,9.584604e+11
|
|
||||||
1058975267713982565,9.531072e+11
|
|
||||||
980743925180530708,9.624348e+11
|
|
||||||
795057210940981318,9.499104e+11
|
|
||||||
812841462248767499,9.724572e+11
|
|
||||||
1037356055933497364,9.772992e+11
|
|
||||||
587719422643535873,9.659772e+11
|
|
||||||
1066473378816467054,9.732384e+11
|
|
||||||
1027400955249041480,9.639036e+11
|
|
||||||
1043228779876716584,9.564732e+11
|
|
||||||
858058607731146762,9.513792e+11
|
|
||||||
838060361894068264,9.548316e+11
|
|
||||||
971147645475258368,9.539712e+11
|
|
||||||
936056120420761611,9.550908e+11
|
|
||||||
1041288670570889297,9.671868e+11
|
|
||||||
952332159979503677,9.573372e+11
|
|
||||||
708153903425912952,9.77472e+11
|
|
||||||
345756943450898433,9.702972e+11
|
|
||||||
692453290427941005,9.737568e+11
|
|
||||||
654102447320596510,9.629532e+11
|
|
||||||
722597580214632461,9.533664e+11
|
|
||||||
476907947277025295,9.555228e+11
|
|
||||||
349328356338302976,9.561276e+11
|
|
||||||
443126436157456394,9.530208e+11
|
|
||||||
758788702389403648,9.556092e+11
|
|
||||||
478752726612967435,9.578556e+11
|
|
||||||
836048120718688266,9.590652e+11
|
|
||||||
796537667218178119,9.667548e+11
|
|
||||||
1083891438393233568,9.63126e+11
|
|
||||||
1048169103950614638,9.733248e+11
|
|
||||||
1081648317823463488,9.712476e+11
|
|
||||||
1065061496842879017,9.685692e+11
|
|
||||||
804529541716508694,9.542304e+11
|
|
||||||
1053901402772078652,9.682236e+11
|
|
||||||
1000032163414163476,9.573372e+11
|
|
||||||
1085892495470239834,9.656316e+11
|
|
||||||
893146906329501756,9.693468e+11
|
|
||||||
1013149541035425878,9.782496e+11
|
|
||||||
1006707714803650590,9.638172e+11
|
|
||||||
1064576791051776010,9.69606e+11
|
|
||||||
1064167725800370176,9.467136e+11
|
|
||||||
914058373857706034,9.633852e+11
|
|
||||||
1096401339996717108,9.772128e+11
|
|
||||||
1073783318799196301,9.5328e+11
|
|
||||||
1050421303775080469,9.561276e+11
|
|
||||||
816328583810383922,9.572508e+11
|
|
||||||
1068941746026852423,9.629532e+11
|
|
||||||
1083443026149519411,9.76608e+11
|
|
||||||
1066726987139391579,9.7263e+11
|
|
||||||
812796288666959892,9.509472e+11
|
|
||||||
975232564270886983,9.711612e+11
|
|
||||||
922956516624052285,9.646812e+11
|
|
||||||
996471123623546890,9.544032e+11
|
|
||||||
697750438887555072,9.569916e+11
|
|
||||||
1100069245565546506,9.664956e+11
|
|
||||||
1073834635320107059,9.782496e+11
|
|
||||||
936771696575262751,9.642492e+11
|
|
||||||
1089986976888918158,9.612252e+11
|
|
||||||
959020201540718596,9.632124e+11
|
|
||||||
886181760587665468,9.645084e+11
|
|
||||||
1092800350383255612,9.55782e+11
|
|
||||||
1079749357789786193,9.64854e+11
|
|
||||||
993247623265914880,9.658044e+11
|
|
||||||
1083189908992163871,9.715068e+11
|
|
||||||
882843628907671562,9.539712e+11
|
|
||||||
736566913597177907,9.638172e+11
|
|
||||||
1099193351531671659,9.619164e+11
|
|
||||||
1111663268134662224,9.538848e+11
|
|
||||||
892147474091888680,9.76176e+11
|
|
||||||
986427592972308490,9.654588e+11
|
|
||||||
160794456004624385,9.696924e+11
|
|
||||||
924667929142919209,9.734112e+11
|
|
||||||
1097067552926076989,9.709884e+11
|
|
||||||
1121177427327058010,9.720252e+11
|
|
||||||
1122221632916815882,9.702108e+11
|
|
||||||
893610763137200148,9.620028e+11
|
|
||||||
925962397926182942,9.484416e+11
|
|
||||||
792776499777896460,9.503424e+11
|
|
||||||
1124390303269408889,9.74016e+11
|
|
||||||
1124127967208018022,9.632124e+11
|
|
||||||
793268834739159060,9.74448e+11
|
|
||||||
946646420834906114,9.715932e+11
|
|
||||||
948342978169151488,9.633852e+11
|
|
||||||
700324066128822334,9.504288e+11
|
|
||||||
1076338388547928064,9.550044e+11
|
|
||||||
1013593008232476723,9.575964e+11
|
|
||||||
1124112953550053378,9.651132e+11
|
|
||||||
1048569035022934036,9.742752e+11
|
|
||||||
1129738558324883587,9.719388e+11
|
|
||||||
824775420070199346,9.758304e+11
|
|
||||||
1044373961502367754,9.646812e+11
|
|
||||||
971870039772901436,9.673596e+11
|
|
||||||
982494441766010930,9.672732e+11
|
|
||||||
958360658238382112,9.473184e+11
|
|
||||||
1029865425397354536,9.76176e+11
|
|
||||||
966845743635787846,9.51984e+11
|
|
||||||
1015670876827553792,9.494784e+11
|
|
||||||
1052253666414972938,9.725436e+11
|
|
||||||
1088634101977845770,9.499968e+11
|
|
||||||
1107924271386333314,9.52848e+11
|
|
||||||
1099709536585138217,9.664956e+11
|
|
||||||
1089269275040174080,9.539712e+11
|
|
||||||
1134924437876129943,9.732384e+11
|
|
||||||
974006072497020928,9.736704e+11
|
|
||||||
1145995037423968386,9.564732e+11
|
|
||||||
1124005776114061393,9.755712e+11
|
|
||||||
1074110463111090256,9.676188e+11
|
|
||||||
1024785963756560394,9.559548e+11
|
|
||||||
931805733807329331,9.772128e+11
|
|
||||||
747208441155813386,9.514656e+11
|
|
||||||
1112116656794255413,9.555228e+11
|
|
||||||
1155432498612932669,9.663228e+11
|
|
||||||
975433081060216843,9.625212e+11
|
|
||||||
1108729730758344704,9.480096e+11
|
|
||||||
992644193849716766,9.591516e+11
|
|
||||||
1161920897809129473,9.520704e+11
|
|
||||||
1130970063512535050,9.544032e+11
|
|
||||||
970352694827036712,9.582876e+11
|
|
||||||
1086855977401340016,9.602748e+11
|
|
||||||
966731025155768351,9.736704e+11
|
|
||||||
962594606879502356,9.47664e+11
|
|
||||||
910537967473414174,9.61398e+11
|
|
||||||
1142887920366260397,9.747936e+11
|
|
||||||
1173460045850214472,9.61398e+11
|
|
||||||
1113241385689161819,9.636444e+11
|
|
||||||
1048366042130415647,9.76608e+11
|
|
||||||
1143672078898315364,9.501696e+11
|
|
||||||
882437468899659797,9.688284e+11
|
|
||||||
1123479840306241546,9.54144e+11
|
|
||||||
832341844695711804,9.548316e+11
|
|
||||||
1202014923660730442,9.501696e+11
|
|
||||||
1059181289560875038,9.702972e+11
|
|
||||||
1138800113318383688,9.747936e+11
|
|
||||||
1214577235223257171,9.62262e+11
|
|
||||||
969983248384032890,9.526752e+11
|
|
||||||
1191169941299282048,9.53712e+11
|
|
||||||
1221863172538110112,9.57078e+11
|
|
||||||
1173287717853986840,9.727164e+11
|
|
||||||
791344978529091624,9.637308e+11
|
|
||||||
1113449820229730314,9.582876e+11
|
|
||||||
1072593794123452567,9.499968e+11
|
|
||||||
965324768712736809,9.58806e+11
|
|
||||||
1152295675254550558,9.493056e+11
|
|
||||||
1158434359259430982,9.538848e+11
|
|
||||||
1129051334302236683,9.620028e+11
|
|
||||||
1222277835746574339,9.659772e+11
|
|
||||||
245692567172284416,9.64422e+11
|
|
||||||
1113794793588396115,9.666684e+11
|
|
||||||
998373604288892948,9.752256e+11
|
|
||||||
1106896766873391185,9.547452e+11
|
|
||||||
1209356657746771992,9.73152e+11
|
|
||||||
1085080386217967656,9.480096e+11
|
|
||||||
1089260336441471157,9.5967e+11
|
|
||||||
1076529106793009182,9.508608e+11
|
|
||||||
947300599119085618,9.51552e+11
|
|
||||||
1234432274670555207,9.76176e+11
|
|
||||||
878870207182032906,9.627804e+11
|
|
||||||
1117584772793897061,9.588924e+11
|
|
||||||
1071461349328175306,9.4896e+11
|
|
||||||
1236077514103853089,9.523296e+11
|
|
||||||
904008772446453781,9.581148e+11
|
|
||||||
1114332546755465288,9.780768e+11
|
|
||||||
929654937565155359,9.710748e+11
|
|
||||||
1236391959535550524,9.520704e+11
|
|
||||||
986011723448328284,9.645084e+11
|
|
||||||
1091538778444808202,9.671004e+11
|
|
||||||
1227230559751639115,9.718524e+11
|
|
||||||
1084799653771481108,9.6831e+11
|
|
||||||
1069059534435389460,9.599292e+11
|
|
||||||
582536628904394762,9.638172e+11
|
|
||||||
872829194210512987,9.6831e+11
|
|
||||||
1159667407841927188,9.483552e+11
|
|
||||||
1247109607864664156,9.494784e+11
|
|
||||||
1238867447944056856,9.525024e+11
|
|
||||||
1181335824424509563,9.654588e+11
|
|
||||||
1169915475518558318,9.490464e+11
|
|
||||||
1236647018420502570,9.54144e+11
|
|
||||||
961988895829012540,9.69174e+11
|
|
||||||
1089619893902651502,9.508608e+11
|
|
||||||
1162165396988764241,9.65718e+11
|
|
||||||
1147045357658853428,9.513792e+11
|
|
||||||
1212163717655961695,9.616572e+11
|
|
||||||
778641350006013982,9.73152e+11
|
|
||||||
949815669023703050,9.756576e+11
|
|
||||||
570621131652988953,9.779904e+11
|
|
||||||
1189442227827654677,9.572508e+11
|
|
||||||
703924532959772682,9.5967e+11
|
|
||||||
988803994161905705,9.604476e+11
|
|
||||||
1201179652488699985,9.483552e+11
|
|
||||||
808282311887159326,9.483552e+11
|
|
||||||
1224875525890375863,9.54144e+11
|
|
||||||
1197350521116311742,9.479232e+11
|
|
|
463
export.json
463
export.json
@ -1,463 +0,0 @@
|
|||||||
[{"_id":{"$oid":"62abe68fac5a7307f5535a4f"},"userId":"960685326425276467","birthday":9.509472E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abe6f8ac5a7307f5535a5b"},"userId":"593911525396447252","birthday":9.544896E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abe718ac5a7307f5535a5f"},"userId":"806306626779742238","birthday":9.571644E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abe728ac5a7307f5535a63"},"userId":"369158860906299393","birthday":9.47232E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abe7a3ac5a7307f5535a6b"},"userId":"897994614139682816","birthday":9.70902E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abe819ac5a7307f5535a6f"},"userId":"301972134471663616","birthday":9.664956E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abeab4ac5a7307f5535a77"},"userId":"814106185615540244","birthday":9.724572E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abed38ac5a7307f5535a7b"},"userId":"930992122004381746","birthday":9.499104E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abeda1ac5a7307f5535a7f"},"userId":"842325321990406144","birthday":9.468864E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abef9cac5a7307f5535a83"},"userId":"804601870454751233","birthday":9.525024E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abf1d9ac5a7307f5535a87"},"userId":"942411503980642305","birthday":9.510336E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62abf874ac5a7307f5535a8c"},"userId":"884137592910659714","birthday":9.776448E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac0f0fac5a7307f5535a98"},"userId":"952725532590604319","birthday":9.620892E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac14b1ac5a7307f5535a9c"},"userId":"817257291081842728","birthday":9.482688E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac1c47ac5a7307f5535aa0"},"userId":"836140378985594930","birthday":9.630396E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac2eb9ac5a7307f5535aa4"},"userId":"706727650663596073","birthday":9.766944E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac448bac5a7307f5535aa8"},"userId":"942773725491785798","birthday":9.530208E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac5391ac5a7307f5535aad"},"userId":"799701704521547828","birthday":9.54576E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac5726ac5a7307f5535ab1"},"userId":"883789675200913429","birthday":9.568188E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac6226ac5a7307f5535ab9"},"userId":"751930684679127091","birthday":9.693468E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac6b02ac5a7307f5535ac1"},"userId":"920308846809985034","birthday":9.7047E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ac6da5ac5a7307f5535ac5"},"userId":"751434580460044298","birthday":9.777312E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62aca34aac5a7307f5535aca"},"userId":"937849237545320508","birthday":9.571644E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ada767ac5a7307f5535ad7"},"userId":"762170361486770196","birthday":9.655452E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ae246fdb10fb7b32761f38"},"userId":"969858846015238184","birthday":9.737568E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62ae4edfdb10fb7b32761f3c"},"userId":"925855248902402138","birthday":9.739296E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62b0a93cdb10fb7b32761f4b"},"userId":"986617067547529267","birthday":9.608796E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62b0ceecdb10fb7b32761f4f"},"userId":"889603830738739211","birthday":9.749664E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62b321b6db10fb7b32761f5b"},"userId":"958318665755922462","birthday":9.537984E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62b6207edb10fb7b32761f6b"},"userId":"982393273446441010","birthday":9.58374E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62bc0ff9db10fb7b32761f9b"},"userId":"949446766187667477","birthday":9.734112E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62be526fdb10fb7b32761faf"},"userId":"969491819014520882","birthday":9.715068E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ce6de6499a3c3954e"},"userId":"978141442524471386","birthday":9.469728E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ce6de6499a3c39551"},"userId":"501147423603949568","birthday":9.469728E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ce6de6499a3c39554"},"userId":"850106457471189067","birthday":9.470592E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ce6de6499a3c39557"},"userId":"883623136883515442","birthday":9.471456E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ce6de6499a3c3955a"},"userId":"814497580679561256","birthday":9.471456E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c3955e"},"userId":"536306727482818573","birthday":9.473184E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39561"},"userId":"968335702620266527","birthday":9.473184E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39567"},"userId":"864468315756822528","birthday":9.474048E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c3956a"},"userId":"940054871686651974","birthday":9.477504E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c3956d"},"userId":"929252976256778251","birthday":9.477504E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39570"},"userId":"748954804298186893","birthday":9.478368E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39573"},"userId":"829236923951480852","birthday":9.482688E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39577"},"userId":"569996979086819349","birthday":9.484416E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c3957a"},"userId":"888500205287247932","birthday":9.484416E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c3957d"},"userId":"847509003214127114","birthday":9.484416E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39580"},"userId":"892914216976154634","birthday":9.487008E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39583"},"userId":"830696113291919389","birthday":9.487872E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39589"},"userId":"978249464030584843","birthday":9.4896E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98de6de6499a3c39590"},"userId":"926800470616461353","birthday":9.49392E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c39593"},"userId":"900175581193338900","birthday":9.49824E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c39596"},"userId":"916960888861392896","birthday":9.499104E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c3959a"},"userId":"809579580318416897","birthday":9.499104E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c3959d"},"userId":"885625974887186495","birthday":9.501696E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395a3"},"userId":"970862084692803644","birthday":9.50256E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395ac"},"userId":"817972713188360253","birthday":9.510336E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395af"},"userId":"528887802386055179","birthday":9.513792E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395b2"},"userId":"933364111885086780","birthday":9.513792E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395b5"},"userId":"911030353949507644","birthday":9.517248E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395b8"},"userId":"125851949131235328","birthday":9.518112E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395bb"},"userId":"828997905025204244","birthday":9.521568E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395bf"},"userId":"963338353644818482","birthday":9.526752E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395c5"},"userId":"954079146932326431","birthday":9.533664E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395c8"},"userId":"776866758933479434","birthday":9.537984E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98ee6de6499a3c395cb"},"userId":"927782055645962270","birthday":9.540576E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395d2"},"userId":"964177218660102244","birthday":9.544896E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395d5"},"userId":"927747007857172520","birthday":9.546624E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395db"},"userId":"923716338474688652","birthday":9.54918E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395de"},"userId":"698916189962895470","birthday":9.550908E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395e1"},"userId":"772100671271862292","birthday":9.550908E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395e4"},"userId":"938297364387606548","birthday":9.550908E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395e7"},"userId":"896276665397813318","birthday":9.554364E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395ee"},"userId":"883909693976739881","birthday":9.563004E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395f1"},"userId":"782025516668289075","birthday":9.564732E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395f4"},"userId":"943321273575038976","birthday":9.565596E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395f7"},"userId":"798279539158941716","birthday":9.56646E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c395ff"},"userId":"869849413592449064","birthday":9.5751E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f98fe6de6499a3c39602"},"userId":"737642652329050112","birthday":9.575964E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c3960b"},"userId":"822853107871514684","birthday":9.582012E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c3960f"},"userId":"960353974798647376","birthday":9.58374E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c39612"},"userId":"929168350544810004","birthday":9.584604E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c39615"},"userId":"949735064374239334","birthday":9.584604E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c39618"},"userId":"405583819786289152","birthday":9.584604E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c3961e"},"userId":"947041852127854592","birthday":9.585468E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c39621"},"userId":"902936994156544111","birthday":9.587196E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c39624"},"userId":"898030525573386290","birthday":9.589788E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c3962a"},"userId":"923373033194917918","birthday":9.591516E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c3962d"},"userId":"949966822273331230","birthday":9.591516E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c39630"},"userId":"968976718545174529","birthday":9.591516E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f990e6de6499a3c39633"},"userId":"392171690672521218","birthday":9.593244E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39636"},"userId":"884854878751567902","birthday":9.593244E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39639"},"userId":"793752852367540235","birthday":9.593244E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c3963c"},"userId":"921211997406756954","birthday":9.594108E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c3963f"},"userId":"795731344784752701","birthday":9.594972E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39642"},"userId":"786504021417918494","birthday":9.595836E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39645"},"userId":"909975385301712948","birthday":9.600156E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39648"},"userId":"789271116433850398","birthday":9.601884E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c3964e"},"userId":"727451062729965598","birthday":9.602748E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39651"},"userId":"938771903328419901","birthday":9.602748E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39654"},"userId":"903677510821220383","birthday":9.604476E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39657"},"userId":"888350648935002113","birthday":9.604476E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c3965a"},"userId":"777874981072928768","birthday":9.60534E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c3965d"},"userId":"961635755107696761","birthday":9.606204E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39660"},"userId":"320350590091919361","birthday":9.607068E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c39669"},"userId":"947616232646017085","birthday":9.608796E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f991e6de6499a3c3966f"},"userId":"899571770334523402","birthday":9.610524E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c39672"},"userId":"901423313551773706","birthday":9.611388E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c39675"},"userId":"933270792421134366","birthday":9.613116E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c39678"},"userId":"939754221807501342","birthday":9.61398E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c3967e"},"userId":"929652531934691419","birthday":9.6183E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c39681"},"userId":"933736442356715540","birthday":9.6183E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c39687"},"userId":"927939361037754399","birthday":9.619164E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c3968a"},"userId":"830705497798869012","birthday":9.619164E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c3968d"},"userId":"907848580092526622","birthday":9.620892E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c39690"},"userId":"952330255622864928","birthday":9.621756E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c39696"},"userId":"773386458475659285","birthday":9.62694E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c39699"},"userId":"624943341003341824","birthday":9.628668E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c3969c"},"userId":"883785280023302164","birthday":9.629532E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f992e6de6499a3c3969f"},"userId":"731578489588809850","birthday":9.630396E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396a5"},"userId":"876113741157138475","birthday":9.63126E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396a8"},"userId":"954268835127640084","birthday":9.632988E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396ab"},"userId":"952339587135590420","birthday":9.632988E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396ae"},"userId":"884455553361920100","birthday":9.634716E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396b1"},"userId":"857271670876143636","birthday":9.634716E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396b4"},"userId":"957140361208283166","birthday":9.636444E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396b7"},"userId":"355163845330731019","birthday":9.637308E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396ba"},"userId":"944742898707099699","birthday":9.637308E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396bd"},"userId":"955926904169447444","birthday":9.638172E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396c0"},"userId":"818024688122462218","birthday":9.638172E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396c3"},"userId":"753353117419962388","birthday":9.639036E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396c6"},"userId":"969766207785934898","birthday":9.640764E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396c9"},"userId":"916072244504035359","birthday":9.642492E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f993e6de6499a3c396cc"},"userId":"870697425453584444","birthday":9.64422E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396cf"},"userId":"863804410521452614","birthday":9.645948E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396d2"},"userId":"946617741744406530","birthday":9.64854E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396d5"},"userId":"751780892585885716","birthday":9.651132E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396db"},"userId":"706860532195524720","birthday":9.65286E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396de"},"userId":"873030420919377951","birthday":9.654588E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396e1"},"userId":"926993801581572127","birthday":9.655452E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396e4"},"userId":"892987879423348777","birthday":9.65718E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396ea"},"userId":"928935641587261440","birthday":9.659772E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396ed"},"userId":"976400825532436500","birthday":9.660636E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396f0"},"userId":"619944471726915584","birthday":9.664092E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396f3"},"userId":"853697904529899531","birthday":9.664092E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f994e6de6499a3c396f6"},"userId":"690240980925808652","birthday":9.66582E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c396fc"},"userId":"822804221425614903","birthday":9.669276E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c396ff"},"userId":"745682978822291602","birthday":9.671868E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c39702"},"userId":"962098921482633246","birthday":9.675324E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c39705"},"userId":"807440022282305556","birthday":9.677916E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c39708"},"userId":"871906778684747778","birthday":9.67878E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c3970b"},"userId":"797727287968923649","birthday":9.679644E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c3970e"},"userId":"750469279131893760","birthday":9.679644E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c39714"},"userId":"822624645374279713","birthday":9.681372E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c3971a"},"userId":"498134028768116736","birthday":9.683964E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c3971d"},"userId":"938612713901396029","birthday":9.684828E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c39720"},"userId":"813983780452171806","birthday":9.684828E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c39723"},"userId":"903991458472800297","birthday":9.688284E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c39726"},"userId":"892462297962926130","birthday":9.690012E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c39729"},"userId":"860523345890902018","birthday":9.690876E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f995e6de6499a3c3972c"},"userId":"817975261748002836","birthday":9.69174E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c3972f"},"userId":"725034380061442058","birthday":9.692604E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39732"},"userId":"865665545646899222","birthday":9.694332E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39735"},"userId":"766085113514950688","birthday":9.694332E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39738"},"userId":"871119390551183391","birthday":9.695196E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c3973b"},"userId":"821207737253494804","birthday":9.701244E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c3973e"},"userId":"862420898852896797","birthday":9.702972E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39741"},"userId":"925757921869070336","birthday":9.705564E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39744"},"userId":"982551055231492136","birthday":9.707292E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c3974a"},"userId":"677085534920900650","birthday":9.710748E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c3974d"},"userId":"911739781048827964","birthday":9.711612E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39750"},"userId":"853995029643264030","birthday":9.711612E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39753"},"userId":"917903365453779074","birthday":9.71334E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39756"},"userId":"851599010308423710","birthday":9.719388E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c39759"},"userId":"897553187723182120","birthday":9.720252E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c3975c"},"userId":"869067139938664460","birthday":9.722844E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f996e6de6499a3c3975f"},"userId":"834910688983384156","birthday":9.722844E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39762"},"userId":"908168554409496596","birthday":9.724572E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39769"},"userId":"784535670932373515","birthday":9.7263E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c3976c"},"userId":"801027228208726046","birthday":9.727164E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c3976f"},"userId":"909520793241813024","birthday":9.728928E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39772"},"userId":"870252602238066708","birthday":9.73152E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c3977b"},"userId":"828145876873641985","birthday":9.73584E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c3977f"},"userId":"939127356931600434","birthday":9.738432E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39782"},"userId":"934907505946021939","birthday":9.739296E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39785"},"userId":"881406978969174038","birthday":9.74016E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39788"},"userId":"815144239893577728","birthday":9.74016E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39792"},"userId":"848601063485472818","birthday":9.74448E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39795"},"userId":"342990951834189824","birthday":9.74448E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f997e6de6499a3c39798"},"userId":"943133386329968712","birthday":9.747936E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c3979e"},"userId":"905204204241584161","birthday":9.75312E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397a1"},"userId":"815994799769124874","birthday":9.755712E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397a5"},"userId":"876897882173739018","birthday":9.759168E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397a8"},"userId":"667192664202149898","birthday":9.759168E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397ae"},"userId":"887128769071038464","birthday":9.76176E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397b1"},"userId":"890997720930721823","birthday":9.763488E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397b4"},"userId":"889639434927095869","birthday":9.763488E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397b7"},"userId":"805802130064670751","birthday":9.766944E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397ba"},"userId":"707976108104220673","birthday":9.768672E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397bd"},"userId":"970360770036641852","birthday":9.769536E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397c0"},"userId":"901092878376386600","birthday":9.773856E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397c3"},"userId":"740133215687409674","birthday":9.77472E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c1f998e6de6499a3c397c7"},"userId":"825749318140166144","birthday":9.77904E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c240930ec74ae79aabc0d6"},"userId":"986737571369480232","birthday":9.547452E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c67bb80ec74ae79aabc0fb"},"userId":"716005462654320741","birthday":9.507744E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c6d3220ec74ae79aabc0ff"},"userId":"753493140681523260","birthday":9.482688E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c6ee580ec74ae79aabc103"},"userId":"942122987149230191","birthday":9.645948E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c704360ec74ae79aabc108"},"userId":"723587327343067187","birthday":9.559548E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62c9ac3a6806b181cdd154a6"},"userId":"808534409136701472","birthday":9.488736E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62cb89a56806b181cdd154bb"},"userId":"713234483972669451","birthday":9.723708E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d024b3d5b72abf2f9dacfd"},"userId":"861765725429039104","birthday":9.739296E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d03297d5b72abf2f9dad01"},"userId":"943284431312023552","birthday":9.780768E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d04f47d5b72abf2f9dad08"},"userId":"988711957244821514","birthday":9.684828E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d0716ad5b72abf2f9dad0f"},"userId":"465650873650118659","birthday":9.639036E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d0a663d5b72abf2f9dad14"},"userId":"906540161251303465","birthday":9.743616E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d16d96d5b72abf2f9dad1a"},"userId":"885786249867177984","birthday":9.54918E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d34512d5b72abf2f9dad2d"},"userId":"964698414837272587","birthday":9.74016E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d450b1d5b72abf2f9dad34"},"userId":"986242698744823808","birthday":9.50256E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62d9c433d5b72abf2f9dad54"},"userId":"993145412233994330","birthday":9.6399E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62da1d96d5b72abf2f9dad58"},"userId":"994034635988287619","birthday":9.638172E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62da2ef3d5b72abf2f9dad5c"},"userId":"781227238745833513","birthday":9.769536E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62dafbbcd5b72abf2f9dad67"},"userId":"999713824611512390","birthday":9.492192E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62f90bb1431a767828839993"},"userId":"856553597352148992","birthday":9.73584E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62fa0169431a7678288399ac"},"userId":"996092350944247878","birthday":9.671004E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62fa9739431a7678288399b5"},"userId":"716784769874133042","birthday":9.663228E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"62fa9982431a7678288399b9"},"userId":"466350927800958976","birthday":9.742752E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6306a897431a767828839a17"},"userId":"886741601928486914","birthday":9.491328E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6308f53d431a767828839a30"},"userId":"962530967799619584","birthday":9.67878E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"630ad109431a767828839a46"},"userId":"933714145109491722","birthday":9.730656E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"630b0f2f431a767828839a4a"},"userId":"978325559807459398","birthday":9.72198E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"630f4abd431a767828839a75"},"userId":"456109470284644362","birthday":9.552636E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63165d5d431a767828839aae"},"userId":"1000385188460773466","birthday":9.491328E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"632191eb431a767828839afd"},"userId":"830973274720174100","birthday":9.468864E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6321a086431a767828839b01"},"userId":"1005415001877655563","birthday":9.7047E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63263089431a767828839b23"},"userId":"783417366708486154","birthday":9.483552E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"632798a7431a767828839b31"},"userId":"934212646696259634","birthday":9.76176E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6327ac7a431a767828839b35"},"userId":"947582240353812580","birthday":9.64854E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"632b7e5b431a767828839b53"},"userId":"675462080488407040","birthday":9.544032E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"632f2b05431a767828839b6d"},"userId":"967186748553695242","birthday":9.750528E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6335c258431a767828839b95"},"userId":"658623086266023967","birthday":9.5112E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6341eb7b431a767828839bec"},"userId":"931498801468936222","birthday":9.512928E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"634c49c6431a767828839c35"},"userId":"880972339339223122","birthday":9.486144E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"634d6af1431a767828839c3d"},"userId":"745024269255442454","birthday":9.529344E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"635989b6431a767828839c8d"},"userId":"1018935880792166420","birthday":9.671004E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"635aa4ab431a767828839ca2"},"userId":"993504739373363240","birthday":9.508608E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"635abb3d431a767828839ca7"},"userId":"983097023261712524","birthday":9.634716E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"635d5493431a767828839cc1"},"userId":"969459597867896853","birthday":9.551772E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63626a1f431a767828839ceb"},"userId":"699310108693495851","birthday":9.681372E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63671108431a767828839d10"},"userId":"998018739817693205","birthday":9.536256E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6368253a431a767828839d19"},"userId":"1026133045343953008","birthday":9.747936E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6368e4c7431a767828839d1e"},"userId":"1013502860090155080","birthday":9.54144E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"636e982e431a767828839d48"},"userId":"1004202436807897099","birthday":9.703836E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6374ebfd3c5a6e0d3faf8ca6"},"userId":"1042061436236611645","birthday":9.590652E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6388b6a03c5a6e0d3faf8d29"},"userId":"888458203577327686","birthday":9.51984E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"638a785d3c5a6e0d3faf8d35"},"userId":"1013685906139533385","birthday":9.664092E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"638f6afb3c5a6e0d3faf8d5d"},"userId":"940514631636631593","birthday":9.499968E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6391d5e93c5a6e0d3faf8d69"},"userId":"616678407253655612","birthday":9.561276E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"639302833c5a6e0d3faf8d79"},"userId":"1043477069914452049","birthday":9.5112E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63942cb33c5a6e0d3faf8d81"},"userId":"447224284243427329","birthday":9.484416E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"639c38e53c5a6e0d3faf8da5"},"userId":"840804417476362270","birthday":9.561276E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"639d18f13c5a6e0d3faf8daf"},"userId":"929838053332246549","birthday":9.781632E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"639fd7c83c5a6e0d3faf8dc3"},"userId":"766382066852954192","birthday":9.518976E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63ad610b3c5a6e0d3faf8e1d"},"userId":"1050487602412789821","birthday":9.680508E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63ad670f3c5a6e0d3faf8e21"},"userId":"885464067240779816","birthday":9.47232E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63adf70d3c5a6e0d3faf8e2b"},"userId":"1032714210133688380","birthday":9.689148E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63afd9993c5a6e0d3faf8e40"},"userId":"1056146445889183796","birthday":9.614844E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63b1b7a23c5a6e0d3faf8e5a"},"userId":"1047295595074830336","birthday":9.500832E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63b873bb3c5a6e0d3faf8e96"},"userId":"1003207378184982538","birthday":9.47232E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63b94f6b3c5a6e0d3faf8ea1"},"userId":"1043104995194716170","birthday":9.743616E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63c1afb73c5a6e0d3faf8ee1"},"userId":"923369644520247296","birthday":9.606204E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63c1ccf53c5a6e0d3faf8ee5"},"userId":"1032574355516969002","birthday":9.540576E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63c5e83f3c5a6e0d3faf8f14"},"userId":"1002390101227208714","birthday":9.584604E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63c8b0693c5a6e0d3faf8f2b"},"userId":"1058975267713982565","birthday":9.531072E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63ca54d63c5a6e0d3faf8f3a"},"userId":"980743925180530708","birthday":9.624348E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63d1b16b3c5a6e0d3faf8f69"},"userId":"795057210940981318","birthday":9.499104E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63d1e2d43c5a6e0d3faf8f6f"},"userId":"812841462248767499","birthday":9.724572E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63d8dcc83c5a6e0d3faf8fa6"},"userId":"1037356055933497364","birthday":9.772992E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63dd404c3c5a6e0d3faf8fc5"},"userId":"587719422643535873","birthday":9.659772E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63df4c7d3c5a6e0d3faf8fe5"},"userId":"1066473378816467054","birthday":9.732384E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63e421953c5a6e0d3faf9007"},"userId":"1027400955249041480","birthday":9.639036E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63e4219a3c5a6e0d3faf900b"},"userId":"1043228779876716584","birthday":9.564732E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63e6a90b3c5a6e0d3faf901e"},"userId":"858058607731146762","birthday":9.513792E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63fa823aa7dbdfe5f54def3d"},"userId":"838060361894068264","birthday":9.548316E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63ff7aa4e89252f1e5413127"},"userId":"971147645475258368","birthday":9.539712E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"63ffeb11e89252f1e541312d"},"userId":"936056120420761611","birthday":9.550908E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64031954e89252f1e5413145"},"userId":"1041288670570889297","birthday":9.671868E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64077b2831c81c55c6db1a75"},"userId":"952332159979503677","birthday":9.573372E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407ed5931c81c55c6db1a80"},"userId":"708153903425912952","birthday":9.77472E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407ed6e31c81c55c6db1a84"},"userId":"345756943450898433","birthday":9.702972E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407ed7d31c81c55c6db1a88"},"userId":"692453290427941005","birthday":9.737568E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407ed8931c81c55c6db1a8c"},"userId":"654102447320596510","birthday":9.629532E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407ed9531c81c55c6db1a90"},"userId":"722597580214632461","birthday":9.533664E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407edef31c81c55c6db1a94"},"userId":"476907947277025295","birthday":9.555228E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407ee0531c81c55c6db1a98"},"userId":"349328356338302976","birthday":9.561276E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407ee1331c81c55c6db1a9c"},"userId":"443126436157456394","birthday":9.530208E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407ee9d31c81c55c6db1aa0"},"userId":"758788702389403648","birthday":9.556092E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407eec431c81c55c6db1aa4"},"userId":"478752726612967435","birthday":9.578556E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6407efe531c81c55c6db1aa8"},"userId":"836048120718688266","birthday":9.590652E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6408978331c81c55c6db1aae"},"userId":"796537667218178119","birthday":9.667548E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"640ceae631c81c55c6db1acf"},"userId":"1083891438393233568","birthday":9.63126E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"640dfb1231c81c55c6db1adf"},"userId":"1048169103950614638","birthday":9.733248E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"640e382e31c81c55c6db1ae7"},"userId":"1081648317823463488","birthday":9.712476E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"641125a331c81c55c6db1afa"},"userId":"1065061496842879017","birthday":9.685692E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6415f5bd631db651639cec55"},"userId":"804529541716508694","birthday":9.542304E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"641b272f631db651639cec71"},"userId":"1053901402772078652","birthday":9.682236E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"641cffcc631db651639cec81"},"userId":"1000032163414163476","birthday":9.573372E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"641f29f7631db651639cec98"},"userId":"1085892495470239834","birthday":9.656316E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6424a422631db651639cecbb"},"userId":"893146906329501756","birthday":9.693468E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6428c359631db651639cecd1"},"userId":"1013149541035425878","birthday":9.782496E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6429e5a4631db651639cecdd"},"userId":"1006707714803650590","birthday":9.638172E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6431b78c229464ee5ecebd16"},"userId":"1064576791051776010","birthday":9.69606E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64325b7a229464ee5ecebd1d"},"userId":"1064167725800370176","birthday":9.467136E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6434f59c229464ee5ecebd2f"},"userId":"914058373857706034","birthday":9.633852E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64394293229464ee5ecebd54"},"userId":"1096401339996717108","birthday":9.772128E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6439c8b9229464ee5ecebd5a"},"userId":"1073783318799196301","birthday":9.5328E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"643ed185229464ee5ecebd82"},"userId":"1050421303775080469","birthday":9.561276E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6440ae85229464ee5ecebd8f"},"userId":"816328583810383922","birthday":9.572508E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6442980b229464ee5ecebd99"},"userId":"1068941746026852423","birthday":9.629532E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"644402eff59be8a132d3cd20"},"userId":"1083443026149519411","birthday":9.76608E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64457b16f59be8a132d3cd2d"},"userId":"1066726987139391579","birthday":9.7263E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64458773f59be8a132d3cd31"},"userId":"812796288666959892","birthday":9.509472E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6445d3dcf59be8a132d3cd37"},"userId":"975232564270886983","birthday":9.711612E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6446f8bff59be8a132d3cd3f"},"userId":"922956516624052285","birthday":9.646812E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64473afdf59be8a132d3cd44"},"userId":"996471123623546890","birthday":9.544032E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"644c5f50f59be8a132d3cd64"},"userId":"697750438887555072","birthday":9.569916E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"644eebbdf59be8a132d3cd76"},"userId":"1100069245565546506","birthday":9.664956E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"645098b0f59be8a132d3cd7f"},"userId":"1073834635320107059","birthday":9.782496E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6452a0fcf59be8a132d3cd8e"},"userId":"936771696575262751","birthday":9.642492E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64540800f59be8a132d3cd9d"},"userId":"1089986976888918158","birthday":9.612252E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"645e6e37f59be8a132d3cdd7"},"userId":"959020201540718596","birthday":9.632124E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"646b78001ba9f3e4174c5996"},"userId":"886181760587665468","birthday":9.645084E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"646f01da1ba9f3e4174c59a1"},"userId":"1092800350383255612","birthday":9.55782E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"646f44211ba9f3e4174c59a6"},"userId":"1079749357789786193","birthday":9.64854E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"646f8ee21ba9f3e4174c59ac"},"userId":"993247623265914880","birthday":9.658044E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64730d361ba9f3e4174c59c3"},"userId":"1083189908992163871","birthday":9.715068E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64731d991ba9f3e4174c59c8"},"userId":"882843628907671562","birthday":9.539712E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6476279c1ba9f3e4174c59d9"},"userId":"736566913597177907","birthday":9.638172E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"647721f71ba9f3e4174c59e2"},"userId":"1099193351531671659","birthday":9.619164E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"647a40c01ba9f3e4174c59f9"},"userId":"1111663268134662224","birthday":9.538848E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"647cd37d1ba9f3e4174c5a0d"},"userId":"892147474091888680","birthday":9.76176E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6487d7fc1ba9f3e4174c5a31"},"userId":"986427592972308490","birthday":9.654588E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"648856ce1ba9f3e4174c5a37"},"userId":"160794456004624385","birthday":9.696924E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64895cfa1ba9f3e4174c5a3d"},"userId":"924667929142919209","birthday":9.734112E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"648f6de41ba9f3e4174c5a5a"},"userId":"1097067552926076989","birthday":9.709884E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6495d3a51ba9f3e4174c5a7f"},"userId":"1121177427327058010","birthday":9.720252E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"649935fb1ba9f3e4174c5a90"},"userId":"1122221632916815882","birthday":9.702108E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"649a10b91ba9f3e4174c5a99"},"userId":"893610763137200148","birthday":9.620028E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"649adb1b1ba9f3e4174c5aa0"},"userId":"925962397926182942","birthday":9.484416E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"649e5a75321252ba093ad0af"},"userId":"792776499777896460","birthday":9.503424E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"649f1431321252ba093ad0b9"},"userId":"1124390303269408889","birthday":9.74016E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64a45e21321252ba093ad0d7"},"userId":"1124127967208018022","birthday":9.632124E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64a483c0321252ba093ad0db"},"userId":"793268834739159060","birthday":9.74448E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64a79ed9321252ba093ad0ef"},"userId":"946646420834906114","birthday":9.715932E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64ad88b5321252ba093ad10f"},"userId":"948342978169151488","birthday":9.633852E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64adb7d5321252ba093ad114"},"userId":"700324066128822334","birthday":9.504288E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64adc26d321252ba093ad118"},"userId":"1076338388547928064","birthday":9.550044E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64b31030321252ba093ad136"},"userId":"1013593008232476723","birthday":9.575964E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64b829fe321252ba093ad15d"},"userId":"1124112953550053378","birthday":9.651132E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64bce53309fa233882a3a7e2"},"userId":"1048569035022934036","birthday":9.742752E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64c90b1d09fa233882a3a821"},"userId":"1129738558324883587","birthday":9.719388E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64cfe93609fa233882a3a848"},"userId":"824775420070199346","birthday":9.758304E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64d0414f09fa233882a3a84f"},"userId":"1044373961502367754","birthday":9.646812E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64d15355c60c340085bb3de2"},"userId":"971870039772901436","birthday":9.673596E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64d15a31c60c340085bb3de7"},"userId":"982494441766010930","birthday":9.672732E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64d1a84cc60c340085bb3deb"},"userId":"958360658238382112","birthday":9.473184E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64d28629c60c340085bb3df3"},"userId":"1029865425397354536","birthday":9.76176E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64d3862dc60c340085bb3df8"},"userId":"966845743635787846","birthday":9.51984E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64d4d5abc60c340085bb3e0c"},"userId":"1015670876827553792","birthday":9.494784E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64dceda5243eff166d5301e8"},"userId":"1052253666414972938","birthday":9.725436E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64e23d14243eff166d530211"},"userId":"1088634101977845770","birthday":9.499968E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64e45f90aa8d00e46554b876"},"userId":"1107924271386333314","birthday":9.52848E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64e464c1aa8d00e46554b87a"},"userId":"1099709536585138217","birthday":9.664956E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64ea810799afd89b04a0a9a4"},"userId":"1089269275040174080","birthday":9.539712E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64eab16099afd89b04a0a9a9"},"userId":"1134924437876129943","birthday":9.732384E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64ef721c99afd89b04a0a9c4"},"userId":"974006072497020928","birthday":9.736704E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64ef83b899afd89b04a0a9c8"},"userId":"1145995037423968386","birthday":9.564732E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64f20ded99afd89b04a0a9d7"},"userId":"1124005776114061393","birthday":9.755712E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64f90c2299afd89b04a0a9f8"},"userId":"1074110463111090256","birthday":9.676188E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"64fe6693230ff8808addf13a"},"userId":"1024785963756560394","birthday":9.559548E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6510e8f9e5ad402eb45bf867"},"userId":"931805733807329331","birthday":9.772128E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6510e996e5ad402eb45bf86b"},"userId":"747208441155813386","birthday":9.514656E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"651b90e6e5ad402eb45bf899"},"userId":"1112116656794255413","birthday":9.555228E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"651e62d6e5ad402eb45bf8ab"},"userId":"1155432498612932669","birthday":9.663228E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65218f58e5ad402eb45bf8c5"},"userId":"975433081060216843","birthday":9.625212E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6522d2afe5ad402eb45bf8d1"},"userId":"1108729730758344704","birthday":9.480096E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65238bace5ad402eb45bf8d7"},"userId":"992644193849716766","birthday":9.591516E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65281ad2e5ad402eb45bf8f1"},"userId":"1161920897809129473","birthday":9.520704E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"653d99cbe5ad402eb45bf96f"},"userId":"1130970063512535050","birthday":9.544032E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6543c965e5ad402eb45bf98f"},"userId":"970352694827036712","birthday":9.582876E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65484252e5ad402eb45bf9ab"},"userId":"1086855977401340016","birthday":9.602748E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65524f77e5ad402eb45bf9d2"},"userId":"966731025155768351","birthday":9.736704E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65582568e5ad402eb45bf9eb"},"userId":"962594606879502356","birthday":9.47664E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6564d690e5ad402eb45bfa3f"},"userId":"910537967473414174","birthday":9.61398E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65673218e5ad402eb45bfa54"},"userId":"1142887920366260397","birthday":9.747936E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"656c813ee5ad402eb45bfa6e"},"userId":"1173460045850214472","birthday":9.61398E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65765d36e5ad402eb45bfaa2"},"userId":"1113241385689161819","birthday":9.636444E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"657788e5e5ad402eb45bfaab"},"userId":"1048366042130415647","birthday":9.76608E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65875660e5ad402eb45bfafa"},"userId":"1143672078898315364","birthday":9.501696E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65af744d48b225ceab353024"},"userId":"882437468899659797","birthday":9.688284E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65b3aea248b225ceab353042"},"userId":"1123479840306241546","birthday":9.54144E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65c54e9348b225ceab3530b4"},"userId":"832341844695711804","birthday":9.548316E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65c70d7b48b225ceab3530c3"},"userId":"1202014923660730442","birthday":9.501696E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65d7f94348b225ceab35313f"},"userId":"1059181289560875038","birthday":9.702972E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65df9a2248b225ceab35317e"},"userId":"1138800113318383688","birthday":9.747936E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65e9e21948b225ceab3531d1"},"userId":"1214577235223257171","birthday":9.62262E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65f0797048b225ceab3531fa"},"userId":"969983248384032890","birthday":9.526752E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"65fc545748b225ceab353236"},"userId":"1191169941299282048","birthday":9.53712E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6601ad6248b225ceab353257"},"userId":"1221863172538110112","birthday":9.57078E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6602c74d48b225ceab35325c"},"userId":"1173287717853986840","birthday":9.727164E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66059bf148b225ceab35326d"},"userId":"791344978529091624","birthday":9.637308E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"660a22fc48b225ceab353291"},"userId":"1113449820229730314","birthday":9.582876E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6617aaab81e34be8b2892c18"},"userId":"1072593794123452567","birthday":9.499968E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6619882b81e34be8b2892c23"},"userId":"965324768712736809","birthday":9.58806E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66216f8681e34be8b2892c46"},"userId":"1152295675254550558","birthday":9.493056E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66272f87083d1922b9fe5bc0"},"userId":"1158434359259430982","birthday":9.538848E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6627991d083d1922b9fe5bc5"},"userId":"1129051334302236683","birthday":9.620028E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6627dbdd083d1922b9fe5bcb"},"userId":"1222277835746574339","birthday":9.659772E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"663393c8083d1922b9fe5c05"},"userId":"245692567172284416","birthday":9.64422E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"663877e8083d1922b9fe5c27"},"userId":"1113794793588396115","birthday":9.666684E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66388e36083d1922b9fe5c2b"},"userId":"998373604288892948","birthday":9.752256E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"663a5045083d1922b9fe5c36"},"userId":"1106896766873391185","birthday":9.547452E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"663eb69e083d1922b9fe5c4d"},"userId":"1209356657746771992","birthday":9.73152E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66402948083d1922b9fe5c59"},"userId":"1085080386217967656","birthday":9.480096E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66429b6e083d1922b9fe5c64"},"userId":"1089260336441471157","birthday":9.5967E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6642d8fa083d1922b9fe5c69"},"userId":"1076529106793009182","birthday":9.508608E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66441ff2083d1922b9fe5c71"},"userId":"947300599119085618","birthday":9.51552E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66465cba083d1922b9fe5c88"},"userId":"1234432274670555207","birthday":9.76176E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6648e652083d1922b9fe5c94"},"userId":"878870207182032906","birthday":9.627804E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"664bb922083d1922b9fe5caf"},"userId":"1117584772793897061","birthday":9.588924E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"664bbbc7083d1922b9fe5cb5"},"userId":"1071461349328175306","birthday":9.4896E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"664fc18f083d1922b9fe5ccf"},"userId":"1236077514103853089","birthday":9.523296E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66549600083d1922b9fe5cf1"},"userId":"904008772446453781","birthday":9.581148E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66550c29083d1922b9fe5cfa"},"userId":"1114332546755465288","birthday":9.780768E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6657777a083d1922b9fe5d19"},"userId":"929654937565155359","birthday":9.710748E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66578a3e083d1922b9fe5d1d"},"userId":"1236391959535550524","birthday":9.520704E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"665852a9083d1922b9fe5d2a"},"userId":"986011723448328284","birthday":9.645084E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6658eee4083d1922b9fe5d38"},"userId":"1091538778444808202","birthday":9.671004E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"665a21c6083d1922b9fe5d48"},"userId":"1227230559751639115","birthday":9.718524E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"665b7754083d1922b9fe5d56"},"userId":"1084799653771481108","birthday":9.6831E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"665beb33083d1922b9fe5d62"},"userId":"1069059534435389460","birthday":9.599292E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"665c950c083d1922b9fe5d6a"},"userId":"582536628904394762","birthday":9.638172E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"665cc69c083d1922b9fe5d71"},"userId":"872829194210512987","birthday":9.6831E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"665cdda3083d1922b9fe5d76"},"userId":"1159667407841927188","birthday":9.483552E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"665d8821083d1922b9fe5d7d"},"userId":"1247109607864664156","birthday":9.494784E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6660965c083d1922b9fe5da0"},"userId":"1238867447944056856","birthday":9.525024E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6662edc1083d1922b9fe5db8"},"userId":"1181335824424509563","birthday":9.654588E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6664027e083d1922b9fe5dc2"},"userId":"1169915475518558318","birthday":9.490464E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6666ccbd083d1922b9fe5ddc"},"userId":"1236647018420502570","birthday":9.54144E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66671dba083d1922b9fe5de1"},"userId":"961988895829012540","birthday":9.69174E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"666cdd90083d1922b9fe5e0c"},"userId":"1089619893902651502","birthday":9.508608E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"666dd4e0083d1922b9fe5e14"},"userId":"1162165396988764241","birthday":9.65718E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"666de816083d1922b9fe5e19"},"userId":"1147045357658853428","birthday":9.513792E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"666e4a43083d1922b9fe5e21"},"userId":"1212163717655961695","birthday":9.616572E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66702757083d1922b9fe5e2d"},"userId":"778641350006013982","birthday":9.73152E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6670518d083d1922b9fe5e32"},"userId":"949815669023703050","birthday":9.756576E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66707294083d1922b9fe5e37"},"userId":"570621131652988953","birthday":9.779904E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"66748a22083d1922b9fe5e5b"},"userId":"1189442227827654677","birthday":9.572508E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6676b191083d1922b9fe5e69"},"userId":"703924532959772682","birthday":9.5967E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6679507b083d1922b9fe5e7b"},"userId":"988803994161905705","birthday":9.604476E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"6679fb40083d1922b9fe5e80"},"userId":"1201179652488699985","birthday":9.483552E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"667aa820083d1922b9fe5e88"},"userId":"808282311887159326","birthday":9.483552E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"667b42d6083d1922b9fe5e90"},"userId":"1224875525890375863","birthday":9.54144E+11,"__v":0},
|
|
||||||
{"_id":{"$oid":"667c55ba083d1922b9fe5e9b"},"userId":"1197350521116311742","birthday":9.479232E+11,"__v":0}]
|
|
51
package.json
51
package.json
@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "naomis-moderation-bot",
|
|
||||||
"version": "1.3.0",
|
|
||||||
"description": "A public paid moderation bot for Discord.",
|
|
||||||
"main": "prod/index.js",
|
|
||||||
"scripts": {
|
|
||||||
"audit": "knip",
|
|
||||||
"prebuild": "rm -rf prod && prisma generate",
|
|
||||||
"build": "tsc",
|
|
||||||
"lint": "eslint src --max-warnings 0 && prettier src --check",
|
|
||||||
"start": "op run --env-file='./prod.env' -- node prod/index.js",
|
|
||||||
"test": "echo 'no tests yet'",
|
|
||||||
"scan": "SONAR_TOKEN='op://Environment Variables - Development/SonarCloud/mod-bot' op run -- sonar-scanner -Dsonar.organization=nhcarrigan -Dsonar.projectKey=nhcarrigan_mod-bot -Dsonar.sources=. -Dsonar.host.url=https://sonarcloud.io"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/nhcarrigan/mod-bot.git"
|
|
||||||
},
|
|
||||||
"author": "Naomi Carrigan",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/nhcarrigan/mod-bot/issues"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "22",
|
|
||||||
"pnpm": "9"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/nhcarrigan/mod-bot#readme",
|
|
||||||
"devDependencies": {
|
|
||||||
"@nhcarrigan/eslint-config": "3.2.0",
|
|
||||||
"@nhcarrigan/prettier-config": "3.2.0",
|
|
||||||
"@nhcarrigan/typescript-config": "3.0.0",
|
|
||||||
"@types/express": "4.17.21",
|
|
||||||
"@types/node-schedule": "2.1.7",
|
|
||||||
"eslint": "8.57.0",
|
|
||||||
"knip": "5.15.0",
|
|
||||||
"prettier": "3.2.5",
|
|
||||||
"prisma": "5.13.0",
|
|
||||||
"typescript": "5.4.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@octokit/rest": "20.1.1",
|
|
||||||
"@prisma/client": "5.13.0",
|
|
||||||
"discord.js": "14.15.2",
|
|
||||||
"dotenv": "16.4.5",
|
|
||||||
"express": "4.19.2",
|
|
||||||
"node-html-to-image": "4.0.0",
|
|
||||||
"node-schedule": "2.1.1",
|
|
||||||
"prom-client": "15.1.3",
|
|
||||||
"winston": "3.13.0"
|
|
||||||
}
|
|
||||||
}
|
|
4783
pnpm-lock.yaml
generated
4783
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,101 +0,0 @@
|
|||||||
// This is your Prisma schema file,
|
|
||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
||||||
|
|
||||||
generator client {
|
|
||||||
provider = "prisma-client-js"
|
|
||||||
}
|
|
||||||
|
|
||||||
datasource db {
|
|
||||||
provider = "mongodb"
|
|
||||||
url = env("MONGO_URI")
|
|
||||||
}
|
|
||||||
|
|
||||||
model cases {
|
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
||||||
serverId String
|
|
||||||
userId String
|
|
||||||
number Int
|
|
||||||
action String
|
|
||||||
reason String
|
|
||||||
evidence String[]
|
|
||||||
timestamp String
|
|
||||||
moderator String
|
|
||||||
|
|
||||||
@@unique([serverId, number])
|
|
||||||
@@index([serverId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model levelRoles {
|
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
||||||
serverId String
|
|
||||||
level Int
|
|
||||||
roleId String
|
|
||||||
|
|
||||||
@@unique([serverId, level, roleId])
|
|
||||||
@@index([serverId], map: "serverId")
|
|
||||||
}
|
|
||||||
|
|
||||||
model levels {
|
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
||||||
serverId String
|
|
||||||
userId String
|
|
||||||
points Int @default(0)
|
|
||||||
level Int @default(0)
|
|
||||||
username String
|
|
||||||
avatar String
|
|
||||||
backgroundColour String @default("000000")
|
|
||||||
colour String @default("ffffff")
|
|
||||||
backgroundImage String @default("https://cdn.nhcarrigan.com/banner.png")
|
|
||||||
cooldown DateTime @default(now())
|
|
||||||
|
|
||||||
@@unique([serverId, userId], map: "serverId_userId")
|
|
||||||
}
|
|
||||||
|
|
||||||
model configs {
|
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
||||||
serverId String
|
|
||||||
inviteLink String @default("")
|
|
||||||
banAppealLink String @default("")
|
|
||||||
modLogChannel String @default("")
|
|
||||||
eventLogChannel String @default("")
|
|
||||||
messageReportChannel String @default("")
|
|
||||||
joinRole String @default("")
|
|
||||||
birthdayChannel String @default("")
|
|
||||||
|
|
||||||
@@unique([serverId], map: "serverId")
|
|
||||||
}
|
|
||||||
|
|
||||||
model entitlements {
|
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
||||||
serverId String
|
|
||||||
purchaserId String
|
|
||||||
notes String @default("")
|
|
||||||
|
|
||||||
@@unique([serverId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model roles {
|
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
||||||
serverId String
|
|
||||||
roleId String
|
|
||||||
|
|
||||||
@@unique([serverId, roleId], map: "serverId_roleId")
|
|
||||||
}
|
|
||||||
|
|
||||||
model birthdays {
|
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
||||||
serverId String
|
|
||||||
userId String
|
|
||||||
birthday DateTime
|
|
||||||
|
|
||||||
@@unique([serverId, userId], map: "serverId_userId")
|
|
||||||
}
|
|
||||||
|
|
||||||
model security {
|
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
||||||
serverId String
|
|
||||||
lockDms Boolean @default(false)
|
|
||||||
lockInvites Boolean @default(false)
|
|
||||||
|
|
||||||
@@unique([serverId], map: "serverId")
|
|
||||||
}
|
|
13
prod.env
13
prod.env
@ -1,13 +0,0 @@
|
|||||||
## Global Values
|
|
||||||
BOT_TOKEN="op://Environment Variables - Naomi/Mod Bot/token"
|
|
||||||
MONGO_URI="op://Environment Variables - Naomi/Mod Bot/mongo_uri"
|
|
||||||
DEBUG_HOOK="op://Environment Variables - Naomi/Mod Bot/webhook"
|
|
||||||
NODE_ENV="op://Environment Variables - Naomi/Mod Bot/environment"
|
|
||||||
LOG_HOOK="op://Environment Variables - Naomi/Mod Bot/webhook"
|
|
||||||
STAFF_GUILD="op://Environment Variables - Naomi/Mod Bot/home_server"
|
|
||||||
|
|
||||||
## Server Shit
|
|
||||||
GITHUB_WEBHOOK_SECRET="op://Environment Variables - Naomi/Mod Bot/github_webhook_secret"
|
|
||||||
PATREON_WEBHOOK_SECRET="op://Environment Variables - Naomi/Mod Bot/patreon_webhook_secret"
|
|
||||||
KOFI_WEBHOOK_SECRET="op://Environment Variables - Naomi/Mod Bot/kofi_webhook_secret"
|
|
||||||
GITHUB_TOKEN="op://Environment Variables - Naomi/Mod Bot/github_pat"
|
|
11
sample.env
11
sample.env
@ -1,11 +0,0 @@
|
|||||||
## Global Values
|
|
||||||
BOT_TOKEN=-""
|
|
||||||
MONGO_URI=""
|
|
||||||
DEBUG_HOOK=""
|
|
||||||
NODE_ENV="development"
|
|
||||||
|
|
||||||
## Server Shit
|
|
||||||
GITHUB_WEBHOOK_SECRET=""
|
|
||||||
PATREON_WEBHOOK_SECRET=""
|
|
||||||
KOFI_WEBHOOK_SECRET=""
|
|
||||||
GITHUB_TOKEN=""
|
|
@ -1,99 +0,0 @@
|
|||||||
import {
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
import { processModAction } from "../utils/processModAction";
|
|
||||||
|
|
||||||
export const ban: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("ban")
|
|
||||||
.setDescription("Ban a user from the server.")
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to ban.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("reason")
|
|
||||||
.setDescription("The reason for banning.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinLength(1)
|
|
||||||
.setMaxLength(400)
|
|
||||||
)
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("prune")
|
|
||||||
.setDescription("Number of days to prune messages.")
|
|
||||||
.setMinValue(0)
|
|
||||||
.setMaxValue(7)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("evidence")
|
|
||||||
.setDescription(
|
|
||||||
"A link to the evidence for the ban. For multiple links, separate with a space."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(PermissionFlagsBits.BanMembers)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reason = interaction.options.getString("reason", true);
|
|
||||||
const evidence =
|
|
||||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
|
||||||
const prune = interaction.options.getInteger("prune") || 0;
|
|
||||||
const user = interaction.options.getUser("user", true);
|
|
||||||
const target =
|
|
||||||
guild.members.cache.get(user.id) ||
|
|
||||||
(await guild.members.fetch(user.id).catch(() => null));
|
|
||||||
|
|
||||||
if (target && isModerator(target)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You cannot ban a moderator."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await processModAction(
|
|
||||||
bot,
|
|
||||||
interaction,
|
|
||||||
guild,
|
|
||||||
user,
|
|
||||||
"ban",
|
|
||||||
reason,
|
|
||||||
evidence,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
prune
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "ban command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,143 +0,0 @@
|
|||||||
import { SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates that the day provided is a valid day of the month.
|
|
||||||
*
|
|
||||||
* @param {string} month The month to validate.
|
|
||||||
* @param {number} day The day to validate.
|
|
||||||
* @returns {boolean} True if the day is within the month's range.
|
|
||||||
*/
|
|
||||||
const validateDate = (month: string, day: number): boolean => {
|
|
||||||
switch (month) {
|
|
||||||
case "Jan":
|
|
||||||
case "Mar":
|
|
||||||
case "May":
|
|
||||||
case "Jul":
|
|
||||||
case "Aug":
|
|
||||||
case "Oct":
|
|
||||||
case "Dec":
|
|
||||||
return day >= 1 && day <= 31;
|
|
||||||
case "Feb":
|
|
||||||
return day >= 1 && day <= 29;
|
|
||||||
case "Apr":
|
|
||||||
case "Jun":
|
|
||||||
case "Sep":
|
|
||||||
case "Nov":
|
|
||||||
return day >= 1 && day <= 30;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const birthday: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("birthday")
|
|
||||||
.setDescription("Set your birthday!")
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("month")
|
|
||||||
.setDescription("Your Birth Month")
|
|
||||||
.setRequired(true)
|
|
||||||
.setChoices(
|
|
||||||
{
|
|
||||||
name: "January",
|
|
||||||
value: "Jan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "February",
|
|
||||||
value: "Feb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "March",
|
|
||||||
value: "Mar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "April",
|
|
||||||
value: "Apr"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "May",
|
|
||||||
value: "May"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "June",
|
|
||||||
value: "Jun"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "July",
|
|
||||||
value: "Jul"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "August",
|
|
||||||
value: "Aug"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "September",
|
|
||||||
value: "Sep"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "October",
|
|
||||||
value: "Oct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "November",
|
|
||||||
value: "Nov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "December",
|
|
||||||
value: "Dec"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("day")
|
|
||||||
.setDescription("Your Birth Day (1-31)")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinValue(1)
|
|
||||||
.setMaxValue(31)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply();
|
|
||||||
const month = interaction.options.getString("month", true);
|
|
||||||
const day = interaction.options.getInteger("day", true);
|
|
||||||
|
|
||||||
if (!validateDate(month, day)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `${month} ${day} is not a valid date!`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await bot.db.birthdays.upsert({
|
|
||||||
where: {
|
|
||||||
serverId_userId: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
userId: interaction.user.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
birthday: new Date(`${month}-${day}-2000`)
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
userId: interaction.user.id,
|
|
||||||
birthday: new Date(`${month}-${day}-2000`)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await interaction.editReply(
|
|
||||||
`Your birthday has been set to ${month}-${day}!`
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "birthday command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,90 +0,0 @@
|
|||||||
import { GuildMember, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { customSubstring } from "../utils/customSubstring";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
|
|
||||||
export const cases: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("case")
|
|
||||||
.setDescription("View a specific moderation case.")
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("number")
|
|
||||||
.setDescription("The case number to view.")
|
|
||||||
.setRequired(true)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isModerator(member as GuildMember)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = interaction.options.getUser("user", true);
|
|
||||||
const number = interaction.options.getInteger("number", true);
|
|
||||||
|
|
||||||
const requestedCase = await bot.db.cases.findFirst({
|
|
||||||
where: {
|
|
||||||
userId: target.id,
|
|
||||||
serverId: guild.id,
|
|
||||||
number
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!requestedCase) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "That user doesn't seem to have a moderation history yet."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewEmbed = new EmbedBuilder();
|
|
||||||
viewEmbed.setTitle(
|
|
||||||
`Case ${requestedCase.number} - ${requestedCase.action}`
|
|
||||||
);
|
|
||||||
viewEmbed.setAuthor({
|
|
||||||
name: target.tag,
|
|
||||||
iconURL: target.displayAvatarURL()
|
|
||||||
});
|
|
||||||
viewEmbed.setDescription(customSubstring(requestedCase.reason, 4000));
|
|
||||||
viewEmbed.addFields(
|
|
||||||
{
|
|
||||||
name: "Evidence",
|
|
||||||
value:
|
|
||||||
customSubstring(requestedCase.evidence.join("\n"), 2000) ||
|
|
||||||
"No evidence provided"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Date",
|
|
||||||
value: requestedCase.timestamp
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Moderator",
|
|
||||||
value: requestedCase.moderator
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [viewEmbed]
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "cases command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,161 +0,0 @@
|
|||||||
import {
|
|
||||||
SlashCommandBuilder,
|
|
||||||
SlashCommandSubcommandBuilder,
|
|
||||||
Guild,
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { logChannelChoices } from "../config/LogChannelChoices";
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { CommandHandler } from "../interfaces/CommandHandler";
|
|
||||||
import { getConfig } from "../modules/data/getConfig";
|
|
||||||
import { handleAppealLink } from "../modules/subcommands/config/handleAppealLink";
|
|
||||||
import { handleBirthdayChannel } from "../modules/subcommands/config/handleBirthdayChannel";
|
|
||||||
import { handleInviteLink } from "../modules/subcommands/config/handleInviteLink";
|
|
||||||
import { handleJoinRole } from "../modules/subcommands/config/handleJoinRole";
|
|
||||||
import { handleList } from "../modules/subcommands/config/handleList";
|
|
||||||
import { handleLogging } from "../modules/subcommands/config/handleLogging";
|
|
||||||
import { handleRole } from "../modules/subcommands/config/handleRole";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
const handlers: { [key: string]: CommandHandler } = {
|
|
||||||
list: handleList,
|
|
||||||
"invite-link": handleInviteLink,
|
|
||||||
"appeal-link": handleAppealLink,
|
|
||||||
logging: handleLogging,
|
|
||||||
role: handleRole,
|
|
||||||
"join-role": handleJoinRole,
|
|
||||||
"birthday-channel": handleBirthdayChannel
|
|
||||||
};
|
|
||||||
|
|
||||||
export const config: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("config")
|
|
||||||
.setDescription("Modify the config settings.")
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("list")
|
|
||||||
.setDescription("List your server's current config settings")
|
|
||||||
)
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("invite-link")
|
|
||||||
.setDescription(
|
|
||||||
"Set the link to be sent to someone to rejoin the server after they are kicked."
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setRequired(true)
|
|
||||||
.setName("link")
|
|
||||||
.setDescription("The invite link to send.")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("appeal-link")
|
|
||||||
.setDescription(
|
|
||||||
"Set the link to be sent to someone when they are banned to appeal the decision."
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setRequired(true)
|
|
||||||
.setName("link")
|
|
||||||
.setDescription("The appeal link to send.")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("logging")
|
|
||||||
.setDescription("Configure a logging channel.")
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("log-type")
|
|
||||||
.setDescription("The type of log to configure.")
|
|
||||||
.addChoices(...logChannelChoices)
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addChannelOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("channel")
|
|
||||||
.setDescription("The channel to log to.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("roles")
|
|
||||||
.setDescription("Toggle roles to be self-assignable by users.")
|
|
||||||
.addRoleOption((o) =>
|
|
||||||
o
|
|
||||||
.setName("role")
|
|
||||||
.setDescription("The role to toggle.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("join-role")
|
|
||||||
.setDescription("Configure a role to be assigned when a member joins.")
|
|
||||||
.addRoleOption((o) =>
|
|
||||||
o
|
|
||||||
.setName("role")
|
|
||||||
.setDescription("The role to assign.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("birthday-channel")
|
|
||||||
.setDescription(
|
|
||||||
"Configure a channel where members can be wished a happy birthday."
|
|
||||||
)
|
|
||||||
.addChannelOption((o) =>
|
|
||||||
o
|
|
||||||
.setName("channel")
|
|
||||||
.setDescription("The channel to send birthday messages in.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
|
|
||||||
const member = interaction.member as GuildMember;
|
|
||||||
const guild = interaction.guild as Guild;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You must be in a server to use this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
|
|
||||||
if (!member.permissions.has(PermissionFlagsBits.ManageGuild)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to use this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subcommand = interaction.options.getSubcommand();
|
|
||||||
|
|
||||||
const handler = handlers[subcommand];
|
|
||||||
|
|
||||||
if (handler) {
|
|
||||||
await handler(bot, interaction, config);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "This is an invalid subcommand. Please contact Naomi."
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "config command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,88 +0,0 @@
|
|||||||
import { execSync } from "child_process";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActionRowBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
ButtonStyle,
|
|
||||||
EmbedBuilder,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { checkEntitledGuild } from "../utils/checkEntitledGuild";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const help: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("help")
|
|
||||||
.setDMPermission(false)
|
|
||||||
.setDescription("Get help with the bot."),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
const version = process.env.npm_package_version;
|
|
||||||
const commit = execSync("git rev-parse HEAD").toString().trim();
|
|
||||||
const subscribed = await checkEntitledGuild(bot, interaction.guild);
|
|
||||||
|
|
||||||
const servers = bot.guilds.cache.size;
|
|
||||||
const members = bot.guilds.cache.reduce(
|
|
||||||
(sum, guild) => sum + guild.memberCount,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder();
|
|
||||||
embed.setTitle("Naomi's Moderation Bot");
|
|
||||||
embed.setDescription(
|
|
||||||
"This is a highly focused moderation bot designed to deliver the best experience when it comes to keeping your community safe and welcoming. To ensure we are able to deliver the features our users require, this bot is only available through a $5/month subscription."
|
|
||||||
);
|
|
||||||
embed.addFields(
|
|
||||||
{
|
|
||||||
name: "Version",
|
|
||||||
value: version ? `v${version}` : "unable to parse version",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Current Commit",
|
|
||||||
value: `[${commit.slice(
|
|
||||||
0,
|
|
||||||
7
|
|
||||||
)}](https://github.com/nhcarrigan/mod-bot/commit/${commit})`,
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Is this server subscribed?",
|
|
||||||
value: subscribed ? "Yes!" : "No :c",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Details",
|
|
||||||
value: `Currently protecting ${servers} servers and watching over ${members} users.`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const supportButton = new ButtonBuilder()
|
|
||||||
.setStyle(ButtonStyle.Link)
|
|
||||||
.setURL("https://chat.naomi.lgbt")
|
|
||||||
.setLabel("Join our Support Server");
|
|
||||||
const subscribeButton = new ButtonBuilder()
|
|
||||||
.setStyle(ButtonStyle.Link)
|
|
||||||
.setURL("https://docs.nhcarrigan.com/#/donate")
|
|
||||||
.setLabel("Subscribe for Access");
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
||||||
supportButton,
|
|
||||||
subscribeButton
|
|
||||||
);
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [embed],
|
|
||||||
components: [row]
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "help command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,198 +0,0 @@
|
|||||||
import {
|
|
||||||
GuildMember,
|
|
||||||
EmbedBuilder,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
ActionRowBuilder,
|
|
||||||
ComponentType,
|
|
||||||
ButtonStyle
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { getNextIndex, getPreviousIndex } from "../utils/getArrayIndex";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
|
|
||||||
export const history: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("history")
|
|
||||||
.setDescription("View a user's history.")
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to view the history for.")
|
|
||||||
.setRequired(true)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isModerator(member as GuildMember)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = interaction.options.getUser("user", true);
|
|
||||||
|
|
||||||
const cases = await bot.db.cases.findMany({
|
|
||||||
where: {
|
|
||||||
userId: target.id,
|
|
||||||
serverId: guild.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!cases.length) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "That user is squeaky clean!"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const caseNumbers = cases
|
|
||||||
.filter((c) => c.action !== "note")
|
|
||||||
.map((c) => `**#${c.number} - ${c.action}**`);
|
|
||||||
const noteNumbers = cases
|
|
||||||
.filter((c) => c.action === "note")
|
|
||||||
.map((c) => `**#${c.number} - ${c.action}**`);
|
|
||||||
``;
|
|
||||||
|
|
||||||
const historyEmbed = new EmbedBuilder();
|
|
||||||
historyEmbed.setTitle(`${target.tag}'s history`);
|
|
||||||
historyEmbed.addFields(
|
|
||||||
{
|
|
||||||
name: "Bans",
|
|
||||||
value: String(cases.filter((c) => c.action === "ban").length) || "0",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Unbans",
|
|
||||||
value:
|
|
||||||
String(cases.filter((c) => c.action === "unban").length) || "0",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Softbans",
|
|
||||||
value:
|
|
||||||
String(cases.filter((c) => c.action === "softban").length) || "0",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Kicks",
|
|
||||||
value: String(cases.filter((c) => c.action === "kick").length) || "0",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mutes",
|
|
||||||
value: String(cases.filter((c) => c.action === "mute").length) || "0",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Unmutes",
|
|
||||||
value:
|
|
||||||
String(cases.filter((c) => c.action === "unmute").length) || "0",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Warns",
|
|
||||||
value: String(cases.filter((c) => c.action === "warn").length) || "0",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Notes",
|
|
||||||
value: String(cases.filter((c) => c.action === "note").length) || "0",
|
|
||||||
inline: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const embeds = [historyEmbed];
|
|
||||||
|
|
||||||
if (caseNumbers.length) {
|
|
||||||
const manualEmbed = new EmbedBuilder()
|
|
||||||
.setTitle("Manual Cases")
|
|
||||||
.setDescription(caseNumbers.join(", "));
|
|
||||||
embeds.push(manualEmbed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noteNumbers.length) {
|
|
||||||
const noteEmbed = new EmbedBuilder()
|
|
||||||
.setTitle("Notes")
|
|
||||||
.setDescription(noteNumbers.join(", "));
|
|
||||||
embeds.push(noteEmbed);
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
const nextButton = new ButtonBuilder()
|
|
||||||
.setCustomId("next")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setLabel(
|
|
||||||
embeds[getNextIndex(embeds, index)]?.data.title || "Unknown embed."
|
|
||||||
)
|
|
||||||
.setEmoji("▶️");
|
|
||||||
const prevButton = new ButtonBuilder()
|
|
||||||
.setCustomId("prev")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setLabel(
|
|
||||||
embeds[getPreviousIndex(embeds, index)]?.data.title ||
|
|
||||||
"Unknown embed."
|
|
||||||
)
|
|
||||||
.setEmoji("◀️");
|
|
||||||
const initialRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
||||||
prevButton,
|
|
||||||
nextButton
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await interaction.editReply({
|
|
||||||
embeds: [embeds[index] as EmbedBuilder],
|
|
||||||
components: [initialRow]
|
|
||||||
});
|
|
||||||
|
|
||||||
const collector =
|
|
||||||
response.createMessageComponentCollector<ComponentType.Button>({
|
|
||||||
time: 1000 * 60 * 5
|
|
||||||
});
|
|
||||||
|
|
||||||
collector.on("collect", async (i) => {
|
|
||||||
await i.deferUpdate();
|
|
||||||
index =
|
|
||||||
i.customId === "next"
|
|
||||||
? getNextIndex(embeds, index)
|
|
||||||
: getPreviousIndex(embeds, index);
|
|
||||||
prevButton.setLabel(
|
|
||||||
embeds[getPreviousIndex(embeds, index)]?.data.title ||
|
|
||||||
"Unknown embed."
|
|
||||||
);
|
|
||||||
nextButton.setLabel(
|
|
||||||
embeds[getNextIndex(embeds, index)]?.data.title || "Unknown embed."
|
|
||||||
);
|
|
||||||
const newRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
||||||
prevButton,
|
|
||||||
nextButton
|
|
||||||
);
|
|
||||||
await i.editReply({
|
|
||||||
embeds: [embeds[index] as EmbedBuilder],
|
|
||||||
components: [newRow]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
collector.on("end", async () => {
|
|
||||||
await interaction.editReply({
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "history command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,145 +0,0 @@
|
|||||||
import {
|
|
||||||
GuildMember,
|
|
||||||
Message,
|
|
||||||
ActionRowBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
ButtonStyle,
|
|
||||||
ComponentType,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
import { processModAction } from "../utils/processModAction";
|
|
||||||
|
|
||||||
export const kick: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("kick")
|
|
||||||
.setDescription("Kick a user from the server.")
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to kick.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("reason")
|
|
||||||
.setDescription("The reason for kicking.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinLength(1)
|
|
||||||
.setMaxLength(400)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("evidence")
|
|
||||||
.setDescription(
|
|
||||||
"A link to the evidence for the kick. For multiple links, separate with a space."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(
|
|
||||||
PermissionFlagsBits.KickMembers
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reason = interaction.options.getString("reason", true);
|
|
||||||
const evidence =
|
|
||||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
|
||||||
const user = interaction.options.getUser("user", true);
|
|
||||||
const target =
|
|
||||||
guild.members.cache.get(user.id) ||
|
|
||||||
(await guild.members.fetch(user.id).catch(() => null));
|
|
||||||
|
|
||||||
if (!target) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `${user.tag} is not in this server and thus cannot be kicked.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isModerator(target)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You cannot kick a moderator."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const yes = new ButtonBuilder()
|
|
||||||
.setCustomId("confirm")
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setStyle(ButtonStyle.Success);
|
|
||||||
const no = new ButtonBuilder()
|
|
||||||
.setCustomId("cancel")
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setStyle(ButtonStyle.Danger);
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(yes, no);
|
|
||||||
const response = (await interaction.editReply({
|
|
||||||
content: `Are you sure you want to kick <@!${user.id}>?`,
|
|
||||||
components: [row]
|
|
||||||
})) as Message;
|
|
||||||
|
|
||||||
const collector =
|
|
||||||
response.createMessageComponentCollector<ComponentType.Button>({
|
|
||||||
filter: (click) => click.user.id === interaction.user.id,
|
|
||||||
time: 10000,
|
|
||||||
max: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
collector.on("end", async (clicks) => {
|
|
||||||
const choice = clicks.first()?.customId;
|
|
||||||
if (!clicks || clicks.size <= 0 || !choice) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "This command has timed out.",
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "confirm") {
|
|
||||||
await processModAction(
|
|
||||||
bot,
|
|
||||||
interaction,
|
|
||||||
guild,
|
|
||||||
user,
|
|
||||||
"kick",
|
|
||||||
reason,
|
|
||||||
evidence
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "cancel") {
|
|
||||||
interaction.editReply({
|
|
||||||
content: "Kick cancelled.",
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "kick command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,156 +0,0 @@
|
|||||||
import {
|
|
||||||
ActionRowBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
ButtonStyle,
|
|
||||||
ComponentType,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { generateLeaderboardImage } from "../modules/commands/generateProfileImage";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const leaderboard: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("leaderboard")
|
|
||||||
.setDescription("See the levels for this community.")
|
|
||||||
.setDMPermission(false),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
const levels = await bot.db.levels.findMany({
|
|
||||||
where: {
|
|
||||||
serverId: interaction.guild.id
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
points: "desc"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapped = levels.map((user, index) => ({
|
|
||||||
...user,
|
|
||||||
index: index + 1
|
|
||||||
}));
|
|
||||||
|
|
||||||
let page = 1;
|
|
||||||
const lastPage = Math.ceil(mapped.length / 10);
|
|
||||||
|
|
||||||
const pageBack = new ButtonBuilder()
|
|
||||||
.setCustomId("prev")
|
|
||||||
.setDisabled(true)
|
|
||||||
.setLabel("◀")
|
|
||||||
.setStyle(ButtonStyle.Primary);
|
|
||||||
const pageForward = new ButtonBuilder()
|
|
||||||
.setCustomId("next")
|
|
||||||
.setLabel("▶")
|
|
||||||
.setStyle(ButtonStyle.Primary);
|
|
||||||
|
|
||||||
if (page <= 1) {
|
|
||||||
pageBack.setDisabled(true);
|
|
||||||
} else {
|
|
||||||
pageBack.setDisabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page >= lastPage) {
|
|
||||||
pageForward.setDisabled(true);
|
|
||||||
} else {
|
|
||||||
pageForward.setDisabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachment = await generateLeaderboardImage(
|
|
||||||
bot,
|
|
||||||
mapped.slice(page * 10 - 10, page * 10)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!attachment) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Failed to load leaderboard image.",
|
|
||||||
files: [],
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sent = await interaction.editReply({
|
|
||||||
files: [attachment],
|
|
||||||
components: [
|
|
||||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
||||||
pageBack,
|
|
||||||
pageForward
|
|
||||||
)
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const clickyClick =
|
|
||||||
sent.createMessageComponentCollector<ComponentType.Button>({
|
|
||||||
time: 300000,
|
|
||||||
filter: (click) => click.user.id === interaction.user.id
|
|
||||||
});
|
|
||||||
|
|
||||||
clickyClick.on("collect", async (click) => {
|
|
||||||
await click.deferUpdate();
|
|
||||||
if (click.customId === "prev") {
|
|
||||||
page--;
|
|
||||||
}
|
|
||||||
if (click.customId === "next") {
|
|
||||||
page++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page <= 1) {
|
|
||||||
pageBack.setDisabled(true);
|
|
||||||
} else {
|
|
||||||
pageBack.setDisabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page >= lastPage) {
|
|
||||||
pageForward.setDisabled(true);
|
|
||||||
} else {
|
|
||||||
pageForward.setDisabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachment = await generateLeaderboardImage(
|
|
||||||
bot,
|
|
||||||
mapped.slice(page * 10 - 10, page * 10)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!attachment) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Failed to load leaderboard image.",
|
|
||||||
files: [],
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
files: [attachment],
|
|
||||||
components: [
|
|
||||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
||||||
pageBack,
|
|
||||||
pageForward
|
|
||||||
)
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
clickyClick.on("end", async () => {
|
|
||||||
pageBack.setDisabled(true);
|
|
||||||
pageForward.setDisabled(true);
|
|
||||||
await interaction.editReply({
|
|
||||||
components: [
|
|
||||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
||||||
pageBack,
|
|
||||||
pageForward
|
|
||||||
)
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "leaderboard subcommand", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,106 +0,0 @@
|
|||||||
import {
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
SlashCommandSubcommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const levelRoles: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("level-role")
|
|
||||||
.setDescription("Manage level roles.")
|
|
||||||
.setDMPermission(false)
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("create")
|
|
||||||
.setDescription("Create a new level role.")
|
|
||||||
.addRoleOption((o) =>
|
|
||||||
o
|
|
||||||
.setName("role")
|
|
||||||
.setDescription("The role to assign")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addIntegerOption((o) =>
|
|
||||||
o
|
|
||||||
.setName("level")
|
|
||||||
.setDescription("The level at which to assign the role.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinValue(1)
|
|
||||||
.setMaxValue(1000)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addSubcommand(
|
|
||||||
new SlashCommandSubcommandBuilder()
|
|
||||||
.setName("delete")
|
|
||||||
.setDescription("Delete a level role.")
|
|
||||||
.addRoleOption((o) =>
|
|
||||||
o
|
|
||||||
.setName("role")
|
|
||||||
.setDescription("The role to remove")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addIntegerOption((o) =>
|
|
||||||
o
|
|
||||||
.setName("level")
|
|
||||||
.setDescription("The level at which the role was being assigned")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinValue(1)
|
|
||||||
.setMaxValue(1000)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member } = interaction;
|
|
||||||
|
|
||||||
if (!member.permissions.has(PermissionFlagsBits.ManageRoles)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const role = interaction.options.getRole("role", true);
|
|
||||||
const level = interaction.options.getInteger("level", true);
|
|
||||||
const action = interaction.options.getSubcommand(true);
|
|
||||||
|
|
||||||
let success = false;
|
|
||||||
if (action === "create") {
|
|
||||||
success = !!(await bot.db.levelRoles
|
|
||||||
.create({
|
|
||||||
data: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
roleId: role.id,
|
|
||||||
level
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => null));
|
|
||||||
}
|
|
||||||
if (action === "delete") {
|
|
||||||
success = !!(await bot.db.levelRoles
|
|
||||||
.delete({
|
|
||||||
where: {
|
|
||||||
serverId_level_roleId: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
roleId: role.id,
|
|
||||||
level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => null));
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
content: success
|
|
||||||
? `Successfully ${action}ed your level ${level} ${role} assignment.`
|
|
||||||
: `Failed to ${action} your level ${level} ${role} assignment.`
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "level roles command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,98 +0,0 @@
|
|||||||
import {
|
|
||||||
ChannelType,
|
|
||||||
EmbedBuilder,
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
TextChannel
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { getConfig } from "../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const lockdown: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("lockdown")
|
|
||||||
.setDescription("Lock down a channel.")
|
|
||||||
.addChannelOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("channel")
|
|
||||||
.setDescription("The channel to lock down.")
|
|
||||||
.setRequired(true)
|
|
||||||
.addChannelTypes(ChannelType.GuildText)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You must be in a guild to use this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(
|
|
||||||
PermissionFlagsBits.ManageChannels
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel = interaction.options.getChannel(
|
|
||||||
"channel",
|
|
||||||
true
|
|
||||||
) as TextChannel;
|
|
||||||
|
|
||||||
if (!("send" in channel)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You must use this command to target a text based channel."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await channel.permissionOverwrites.edit(
|
|
||||||
guild.id,
|
|
||||||
{
|
|
||||||
SendMessages: false
|
|
||||||
},
|
|
||||||
{ reason: `Lockdown Performed by ${interaction.user.tag}` }
|
|
||||||
);
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
if (!config.modLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const logChannel =
|
|
||||||
guild.channels.cache.get(config.modLogChannel) ||
|
|
||||||
(await guild.channels.fetch(config.modLogChannel));
|
|
||||||
|
|
||||||
if (!logChannel || !("send" in logChannel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const embed = new EmbedBuilder();
|
|
||||||
embed.setTitle("Channel Locked Down");
|
|
||||||
embed.setDescription(
|
|
||||||
`The <#${channel.id}> channel has been locked down.`
|
|
||||||
);
|
|
||||||
embed.setAuthor({
|
|
||||||
name: interaction.user.tag,
|
|
||||||
iconURL: interaction.user.displayAvatarURL()
|
|
||||||
});
|
|
||||||
await logChannel.send({ embeds: [embed] });
|
|
||||||
|
|
||||||
await interaction.editReply({ content: "Channel has been locked down!" });
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "lockdown", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,49 +0,0 @@
|
|||||||
import {
|
|
||||||
ActionRowBuilder,
|
|
||||||
ModalBuilder,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
TextInputBuilder,
|
|
||||||
TextInputStyle
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const massBan: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("massban")
|
|
||||||
.setDescription("Ban a list of user IDs at once."),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
const textInput = new TextInputBuilder()
|
|
||||||
.setCustomId("mass-ban-ids")
|
|
||||||
.setLabel("Input your list of IDs separated by new lines")
|
|
||||||
.setMaxLength(4000)
|
|
||||||
.setStyle(TextInputStyle.Paragraph)
|
|
||||||
.setRequired(true);
|
|
||||||
const reasonInput = new TextInputBuilder()
|
|
||||||
.setCustomId("reason")
|
|
||||||
.setLabel("Reason for the mass ban?")
|
|
||||||
.setStyle(TextInputStyle.Short)
|
|
||||||
.setRequired(true);
|
|
||||||
const inputRow = new ActionRowBuilder<TextInputBuilder>().addComponents(
|
|
||||||
textInput
|
|
||||||
);
|
|
||||||
const reasonRow = new ActionRowBuilder<TextInputBuilder>().addComponents(
|
|
||||||
reasonInput
|
|
||||||
);
|
|
||||||
const modal = new ModalBuilder()
|
|
||||||
.setCustomId("mass-ban-modal")
|
|
||||||
.setTitle("Mass Ban")
|
|
||||||
.addComponents(inputRow, reasonRow);
|
|
||||||
|
|
||||||
await interaction.showModal(modal);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "mass ban", err);
|
|
||||||
await interaction.reply({
|
|
||||||
ephemeral: true,
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,122 +0,0 @@
|
|||||||
import {
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
import { processModAction } from "../utils/processModAction";
|
|
||||||
|
|
||||||
export const mute: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("mute")
|
|
||||||
.setDescription("Mute a member")
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to mute.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("duration")
|
|
||||||
.setDescription("The duration of the mute.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("duration-unit")
|
|
||||||
.setDescription("The unit for the duration value")
|
|
||||||
.setRequired(true)
|
|
||||||
.addChoices(
|
|
||||||
{ name: "Minutes", value: "minutes" },
|
|
||||||
{ name: "Hours", value: "hours" },
|
|
||||||
{ name: "Days", value: "days" },
|
|
||||||
{ name: "Weeks", value: "weeks" }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("reason")
|
|
||||||
.setDescription("The reason for the mute.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinLength(1)
|
|
||||||
.setMaxLength(400)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("evidence")
|
|
||||||
.setDescription(
|
|
||||||
"A link to the evidence for the mute. For multiple links, separate with a space."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(
|
|
||||||
PermissionFlagsBits.ModerateMembers
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = interaction.options.getUser("user", true);
|
|
||||||
const target =
|
|
||||||
guild.members.cache.get(user.id) ||
|
|
||||||
(await guild.members.fetch(user.id).catch(() => null));
|
|
||||||
|
|
||||||
if (!target) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "That member appears to have left the server."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isModerator(target)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You cannot mute a moderator."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = interaction.options.getInteger("duration", true);
|
|
||||||
const durationUnit = interaction.options.getString("duration-unit", true);
|
|
||||||
|
|
||||||
const reason = interaction.options.getString("reason", true);
|
|
||||||
const evidence =
|
|
||||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
|
||||||
|
|
||||||
await processModAction(
|
|
||||||
bot,
|
|
||||||
interaction,
|
|
||||||
guild,
|
|
||||||
user,
|
|
||||||
"mute",
|
|
||||||
reason,
|
|
||||||
evidence,
|
|
||||||
duration,
|
|
||||||
durationUnit
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "mute command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,67 +0,0 @@
|
|||||||
import { GuildMember, SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
import { processModAction } from "../utils/processModAction";
|
|
||||||
|
|
||||||
export const note: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("note")
|
|
||||||
.setDescription("Add a note to a member's record.")
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to add a note to.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("reason")
|
|
||||||
.setDescription("The reason for adding this note.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinLength(1)
|
|
||||||
.setMaxLength(400)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isModerator(member as GuildMember)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = interaction.options.getUser("user", true);
|
|
||||||
const reason = interaction.options.getString("reason", true);
|
|
||||||
|
|
||||||
const target =
|
|
||||||
guild.members.cache.get(user.id) ||
|
|
||||||
(await guild.members.fetch(user.id).catch(() => null));
|
|
||||||
|
|
||||||
if (target && isModerator(target)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You cannot add a note to a moderator."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await processModAction(bot, interaction, guild, user, "note", reason, []);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "note command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,50 +0,0 @@
|
|||||||
import { EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const ping: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("ping")
|
|
||||||
.setDescription("Check the response time of the bot."),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply();
|
|
||||||
const receivedInteraction = Date.now();
|
|
||||||
const { createdTimestamp } = interaction;
|
|
||||||
|
|
||||||
const discordLatency = receivedInteraction - createdTimestamp;
|
|
||||||
const websocketLatency = bot.ws.ping;
|
|
||||||
|
|
||||||
await bot.db.$runCommandRaw({ ping: 1 });
|
|
||||||
const databaseLatency = Date.now() - receivedInteraction;
|
|
||||||
|
|
||||||
const pingEmbed = new EmbedBuilder();
|
|
||||||
pingEmbed.setTitle("Pong!");
|
|
||||||
pingEmbed.setDescription("Here is my latency information!");
|
|
||||||
pingEmbed.addFields(
|
|
||||||
{
|
|
||||||
name: "Interaction Latency",
|
|
||||||
value: `${discordLatency}ms`,
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Websocket Latency",
|
|
||||||
value: `${websocketLatency}ms`,
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Database Latency",
|
|
||||||
value: `${databaseLatency}ms`,
|
|
||||||
inline: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await interaction.editReply({ embeds: [pingEmbed] });
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "ping command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,129 +0,0 @@
|
|||||||
import { SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import {
|
|
||||||
validateColour,
|
|
||||||
validateImage
|
|
||||||
} from "../modules/commands/profileValidation";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const profile: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("profile")
|
|
||||||
.setDescription("Edit your profile that appears in the leaderboard")
|
|
||||||
.setDMPermission(false)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("avatar")
|
|
||||||
.setDescription(
|
|
||||||
"The avatar to appear on your profile card must be a URL that points to an image."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("background-colour")
|
|
||||||
.setDescription(
|
|
||||||
"The semi-transparent background color for your profile card must be a 6-digit hex value."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("background-image")
|
|
||||||
.setDescription(
|
|
||||||
"The background image for your profile card must be a URL that points to an image."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("colour")
|
|
||||||
.setDescription(
|
|
||||||
"The color for the text on your profile card must be a 6-digit hex value."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (CamperChan, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
|
|
||||||
const responses = ["Your profile settings have been updated!"];
|
|
||||||
const opts = {
|
|
||||||
avatar: interaction.options.getString("avatar"),
|
|
||||||
backgroundColour: interaction.options.getString("background-colour"),
|
|
||||||
backgroundImage: interaction.options.getString("background-image"),
|
|
||||||
colour: interaction.options.getString("colour")
|
|
||||||
};
|
|
||||||
if (opts.avatar) {
|
|
||||||
const isValid = await validateImage(opts.avatar);
|
|
||||||
if (!isValid) {
|
|
||||||
responses.push(`${opts.avatar} is not a valid image URL.`);
|
|
||||||
opts.avatar = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (opts.backgroundImage) {
|
|
||||||
const isValid = await validateImage(opts.backgroundImage);
|
|
||||||
if (!isValid) {
|
|
||||||
responses.push(`${opts.backgroundImage} is not a valid image URL.`);
|
|
||||||
opts.backgroundImage = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (opts.colour) {
|
|
||||||
if (opts.colour.startsWith("#")) {
|
|
||||||
opts.colour = opts.colour.slice(1);
|
|
||||||
}
|
|
||||||
if (!validateColour(opts.colour)) {
|
|
||||||
opts.colour = null;
|
|
||||||
responses.push(
|
|
||||||
`${interaction.options.getString("colour")} is not a valid hex code. Please try again with a 6 character hex code (# is optional).`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (opts.backgroundColour) {
|
|
||||||
if (opts.backgroundColour.startsWith("#")) {
|
|
||||||
opts.backgroundColour = opts.backgroundColour.slice(1);
|
|
||||||
}
|
|
||||||
if (!validateColour(opts.backgroundColour)) {
|
|
||||||
opts.backgroundColour = null;
|
|
||||||
responses.push(
|
|
||||||
`${interaction.options.getString("background-colour")} is not a valid hex code. Please try again with a 6 character hex code (# is optional).`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = (
|
|
||||||
Object.entries(opts) as [keyof typeof opts, string][]
|
|
||||||
).reduce(
|
|
||||||
(acc, [key, val]) => {
|
|
||||||
if (val) {
|
|
||||||
acc[key] = val;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<keyof typeof opts, string>
|
|
||||||
);
|
|
||||||
|
|
||||||
await CamperChan.db.levels.upsert({
|
|
||||||
where: {
|
|
||||||
serverId_userId: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
userId: interaction.user.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
...query
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
userId: interaction.user.id,
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
username: interaction.user.username,
|
|
||||||
...query
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await interaction.editReply({ content: responses.join("\n") });
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(CamperChan, "user settings command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,71 +0,0 @@
|
|||||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { getConfig } from "../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const prune: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("prune")
|
|
||||||
.setDescription("Prune messages from THIS channel.")
|
|
||||||
.addNumberOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("amount")
|
|
||||||
.setDescription("The amount of messages to remove")
|
|
||||||
.setMinValue(1)
|
|
||||||
.setMaxValue(100)
|
|
||||||
.setRequired(true)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Could not find the member or guild."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!member.permissions.has(PermissionFlagsBits.ManageMessages)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to prune messages."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel = interaction.channel;
|
|
||||||
const amount = interaction.options.getNumber("amount", true);
|
|
||||||
|
|
||||||
if (!channel) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Please provide a text channel or thread."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const messages = await channel.messages.fetch({ limit: amount });
|
|
||||||
for (const message of messages.values()) {
|
|
||||||
await message.delete().catch(() => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.editReply({ content: "Complete." });
|
|
||||||
const config = await getConfig(bot, interaction.guild.id);
|
|
||||||
if (config.modLogChannel) {
|
|
||||||
const logChannel =
|
|
||||||
interaction.guild.channels.cache.get(config.modLogChannel) ||
|
|
||||||
(await interaction.guild.channels.fetch(config.modLogChannel));
|
|
||||||
|
|
||||||
if (logChannel && "send" in logChannel) {
|
|
||||||
await logChannel.send({
|
|
||||||
content: `Prune run by <@!${interaction.user.id}>. Deleted Messages: ${amount}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "prune interaction", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,52 +0,0 @@
|
|||||||
import { SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { generateProfileImage } from "../modules/commands/generateProfileImage";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const rank: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setDMPermission(false)
|
|
||||||
.setName("rank")
|
|
||||||
.setDescription("See your level rank in the community."),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply();
|
|
||||||
const { user } = interaction;
|
|
||||||
|
|
||||||
const target = user.id;
|
|
||||||
|
|
||||||
const record = await bot.db.levels.findUnique({
|
|
||||||
where: {
|
|
||||||
serverId_userId: {
|
|
||||||
userId: target,
|
|
||||||
serverId: interaction.guild.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!record) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Error loading your database record."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = await generateProfileImage(bot, record);
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error generating your profile. :c"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.editReply({ files: [file] });
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "rank command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,52 +0,0 @@
|
|||||||
import { SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const role: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setDMPermission(false)
|
|
||||||
.setName("role")
|
|
||||||
.setDescription("Give yourself a permitted role, or remove it.")
|
|
||||||
.addRoleOption((o) =>
|
|
||||||
o.setName("role").setDescription("The role to toggle.").setRequired(true)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const role = interaction.options.getRole("role", true);
|
|
||||||
const isPermitted = await bot.db.roles
|
|
||||||
.findUnique({
|
|
||||||
where: {
|
|
||||||
serverId_roleId: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
roleId: role.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
if (!isPermitted) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `The <@&${role.id}> role is not self-assignable.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (interaction.member.roles.cache.has(role.id)) {
|
|
||||||
await interaction.member.roles.remove(role.id);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `The <@&${role.id}> role has been removed.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await interaction.member.roles.add(role.id);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `The <@&${role.id}> role has been added.`
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "role command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,102 +0,0 @@
|
|||||||
import { PermissionFlagsBits, SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const secure: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("secure")
|
|
||||||
.setDescription(
|
|
||||||
"Toggles the feature to keep dms/invites locked or unlocked"
|
|
||||||
)
|
|
||||||
.setDMPermission(false)
|
|
||||||
.addBooleanOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("invites")
|
|
||||||
.setDescription("Keep invites locked down?")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addBooleanOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("dms")
|
|
||||||
.setDescription("Keep DMs locked down?")
|
|
||||||
.setRequired(true)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { guild, member } = interaction;
|
|
||||||
const lockInvites = interaction.options.getBoolean("invites", true);
|
|
||||||
const lockDms = interaction.options.getBoolean("dms", true);
|
|
||||||
|
|
||||||
if (
|
|
||||||
![
|
|
||||||
PermissionFlagsBits.Administrator,
|
|
||||||
PermissionFlagsBits.KickMembers,
|
|
||||||
PermissionFlagsBits.BanMembers,
|
|
||||||
PermissionFlagsBits.ManageGuild
|
|
||||||
].some((perm) => member.permissions.has(perm))
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to use this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const botMember = await guild.members
|
|
||||||
.fetch(bot.user?.id || "oopsie")
|
|
||||||
.catch(() => null);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!botMember ||
|
|
||||||
![
|
|
||||||
PermissionFlagsBits.Administrator,
|
|
||||||
PermissionFlagsBits.KickMembers,
|
|
||||||
PermissionFlagsBits.BanMembers,
|
|
||||||
PermissionFlagsBits.ManageGuild
|
|
||||||
].some((perm) => botMember.permissions.has(perm))
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "I do not have the correct permissions to do this."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
await bot.db.security.upsert({
|
|
||||||
where: { serverId: guild.id },
|
|
||||||
update: { lockDms, lockInvites },
|
|
||||||
create: { lockDms, lockInvites, serverId: guild.id }
|
|
||||||
});
|
|
||||||
|
|
||||||
await fetch(
|
|
||||||
`https://discord.com/api/v10/guilds/${guild.id}/incident-actions`,
|
|
||||||
{
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bot ${bot.env.token}`,
|
|
||||||
"content-type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
dms_disabled_until: lockDms ? date : null,
|
|
||||||
invites_disabled_until: lockInvites ? date : null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
).catch(
|
|
||||||
async (e) =>
|
|
||||||
await errorHandler(bot, `incident-actions for ${guild.id}`, e)
|
|
||||||
);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Security options have been updated.\nInvites are ${
|
|
||||||
lockInvites ? "disabled" : "enabled"
|
|
||||||
}.\nDMs are ${lockDms ? "disabled" : "enabled"}.`
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "secure command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,102 +0,0 @@
|
|||||||
import {
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
import { processModAction } from "../utils/processModAction";
|
|
||||||
|
|
||||||
export const softBan: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("softban")
|
|
||||||
.setDescription(
|
|
||||||
"Bans a user from the server, cleans up their messages, and removes the ban."
|
|
||||||
)
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to softban.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("reason")
|
|
||||||
.setDescription("The reason for softbanning.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinLength(1)
|
|
||||||
.setMaxLength(400)
|
|
||||||
)
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("prune")
|
|
||||||
.setDescription("Number of days to prune messages.")
|
|
||||||
.setMinValue(1)
|
|
||||||
.setMaxValue(7)
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("evidence")
|
|
||||||
.setDescription(
|
|
||||||
"A link to the evidence for the ban. For multiple links, separate with a space."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(PermissionFlagsBits.BanMembers)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reason = interaction.options.getString("reason", true);
|
|
||||||
const evidence =
|
|
||||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
|
||||||
const prune = interaction.options.getInteger("prune", true);
|
|
||||||
const user = interaction.options.getUser("user", true);
|
|
||||||
const target =
|
|
||||||
guild.members.cache.get(user.id) ||
|
|
||||||
(await guild.members.fetch(user.id).catch(() => null));
|
|
||||||
|
|
||||||
if (target && isModerator(target)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You cannot ban a moderator."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await processModAction(
|
|
||||||
bot,
|
|
||||||
interaction,
|
|
||||||
guild,
|
|
||||||
user,
|
|
||||||
"softban",
|
|
||||||
reason,
|
|
||||||
evidence,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
prune
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "softban command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,87 +0,0 @@
|
|||||||
import {
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { processModAction } from "../utils/processModAction";
|
|
||||||
|
|
||||||
export const unban: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("unban")
|
|
||||||
.setDescription("Unban a user from the server.")
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to unban.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("reason")
|
|
||||||
.setDescription("The reason for unbanning.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinLength(1)
|
|
||||||
.setMaxLength(400)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("evidence")
|
|
||||||
.setDescription(
|
|
||||||
"A link to the evidence for the unban. For multiple links, separate with a space."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(PermissionFlagsBits.BanMembers)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reason = interaction.options.getString("reason", true);
|
|
||||||
const evidence =
|
|
||||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
|
||||||
const user = interaction.options.getUser("user", true);
|
|
||||||
|
|
||||||
const isBanned = await guild.bans.fetch(user.id).catch(() => false);
|
|
||||||
|
|
||||||
if (!isBanned) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `ID ${user.id} is not banned.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await processModAction(
|
|
||||||
bot,
|
|
||||||
interaction,
|
|
||||||
guild,
|
|
||||||
user,
|
|
||||||
"unban",
|
|
||||||
reason,
|
|
||||||
evidence
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "unban command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,96 +0,0 @@
|
|||||||
import {
|
|
||||||
ChannelType,
|
|
||||||
EmbedBuilder,
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
TextChannel
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { getConfig } from "../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const unlock: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("unlock")
|
|
||||||
.setDescription("Remove lock down from a channel.")
|
|
||||||
.addChannelOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("channel")
|
|
||||||
.setDescription("The channel to unlock.")
|
|
||||||
.setRequired(true)
|
|
||||||
.addChannelTypes(ChannelType.GuildText)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You must be in a guild to use this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(
|
|
||||||
PermissionFlagsBits.ManageChannels
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel = interaction.options.getChannel(
|
|
||||||
"channel",
|
|
||||||
true
|
|
||||||
) as TextChannel;
|
|
||||||
|
|
||||||
if (!("send" in channel)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You must use this command to target a text based channel."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await channel.permissionOverwrites.edit(
|
|
||||||
guild.id,
|
|
||||||
{
|
|
||||||
SendMessages: null
|
|
||||||
},
|
|
||||||
{ reason: `Lockdown Removed by ${interaction.user.tag}` }
|
|
||||||
);
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
if (!config.modLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const logChannel =
|
|
||||||
guild.channels.cache.get(config.modLogChannel) ||
|
|
||||||
(await guild.channels.fetch(config.modLogChannel));
|
|
||||||
|
|
||||||
if (!logChannel || !("send" in logChannel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const embed = new EmbedBuilder();
|
|
||||||
embed.setTitle("Channel Unlocked");
|
|
||||||
embed.setDescription(`The <#${channel.id}> channel has been unlocked.`);
|
|
||||||
embed.setAuthor({
|
|
||||||
name: interaction.user.tag,
|
|
||||||
iconURL: interaction.user.displayAvatarURL()
|
|
||||||
});
|
|
||||||
await logChannel.send({ embeds: [embed] });
|
|
||||||
|
|
||||||
await interaction.editReply({ content: "Channel has been unlocked!" });
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "unlock command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,106 +0,0 @@
|
|||||||
import {
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
import { processModAction } from "../utils/processModAction";
|
|
||||||
|
|
||||||
export const unmute: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("unmute")
|
|
||||||
.setDescription("Unmute a member")
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to unmute.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("reason")
|
|
||||||
.setDescription("The reason for the unmute.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinLength(1)
|
|
||||||
.setMaxLength(400)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("evidence")
|
|
||||||
.setDescription(
|
|
||||||
"A link to the evidence for the unmute. For multiple links, separate with a space."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(
|
|
||||||
PermissionFlagsBits.ModerateMembers
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = interaction.options.getUser("user", true);
|
|
||||||
const target =
|
|
||||||
guild.members.cache.get(user.id) ||
|
|
||||||
(await guild.members.fetch(user.id).catch(() => null));
|
|
||||||
|
|
||||||
if (!target) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "That member appears to have left the server."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!target.isCommunicationDisabled()) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "That member is not muted."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isModerator(target)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content:
|
|
||||||
"A moderator should never be muted. How on earth did you achieve this???"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const reason = interaction.options.getString("reason", true);
|
|
||||||
const evidence =
|
|
||||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
|
||||||
|
|
||||||
await processModAction(
|
|
||||||
bot,
|
|
||||||
interaction,
|
|
||||||
guild,
|
|
||||||
user,
|
|
||||||
"unmute",
|
|
||||||
reason,
|
|
||||||
evidence
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "unmute command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,99 +0,0 @@
|
|||||||
import {
|
|
||||||
GuildMember,
|
|
||||||
PermissionFlagsBits,
|
|
||||||
SlashCommandBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "../interfaces/Command";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { isModerator } from "../utils/isModerator";
|
|
||||||
import { processModAction } from "../utils/processModAction";
|
|
||||||
|
|
||||||
export const warn: Command = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("warn")
|
|
||||||
.setDescription("Warn a member")
|
|
||||||
.addUserOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("user")
|
|
||||||
.setDescription("The user to warn.")
|
|
||||||
.setRequired(true)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("reason")
|
|
||||||
.setDescription("The reason for the warning.")
|
|
||||||
.setRequired(true)
|
|
||||||
.setMinLength(1)
|
|
||||||
.setMaxLength(400)
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("evidence")
|
|
||||||
.setDescription(
|
|
||||||
"A link to the evidence for the warning. For multiple links, separate with a space."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { member, guild } = interaction;
|
|
||||||
|
|
||||||
if (!member || !guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "There was an error loading the guild and member data."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(member as GuildMember).permissions.has(
|
|
||||||
PermissionFlagsBits.KickMembers
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You do not have permission to run this command."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = interaction.options.getUser("user", true);
|
|
||||||
const target =
|
|
||||||
guild.members.cache.get(user.id) ||
|
|
||||||
(await guild.members.fetch(user.id).catch(() => null));
|
|
||||||
|
|
||||||
if (!target) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "That member appears to have left the server."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isModerator(target)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You cannot warn a moderator."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reason = interaction.options.getString("reason", true);
|
|
||||||
const evidence =
|
|
||||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
|
||||||
|
|
||||||
await processModAction(
|
|
||||||
bot,
|
|
||||||
interaction,
|
|
||||||
guild,
|
|
||||||
user,
|
|
||||||
"warn",
|
|
||||||
reason,
|
|
||||||
evidence
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "warn command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
import { configs } from "@prisma/client";
|
|
||||||
|
|
||||||
export const defaultConfig: Omit<configs, "id"> = {
|
|
||||||
serverId: "",
|
|
||||||
inviteLink: "",
|
|
||||||
banAppealLink: "",
|
|
||||||
modLogChannel: "",
|
|
||||||
eventLogChannel: "",
|
|
||||||
messageReportChannel: "",
|
|
||||||
birthdayChannel: "",
|
|
||||||
joinRole: ""
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Action } from "../interfaces/Action";
|
|
||||||
|
|
||||||
export const EmbedColours: { [K in Action]: number } = {
|
|
||||||
ban: 0xfa000c,
|
|
||||||
softban: 0xfa9900,
|
|
||||||
kick: 0xffee00,
|
|
||||||
warn: 0x2600ff,
|
|
||||||
mute: 0xd900ff,
|
|
||||||
unmute: 0x00ff22,
|
|
||||||
unban: 0x00ff22,
|
|
||||||
note: 0x000001
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
export const IgnoredActors = [
|
|
||||||
"renovate[bot]",
|
|
||||||
"codeclimate[bot]",
|
|
||||||
"dependabot[bot]",
|
|
||||||
"lgtm-com[bot]",
|
|
||||||
"deepsource-autofix[bot]",
|
|
||||||
"sonarcloud[bot]",
|
|
||||||
"melody-iuvo"
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ThankYou = `## Thank You
|
|
||||||
|
|
||||||
Thank you for your contribution to our project. We have reviewed your pull request and are happy to accept these changes.
|
|
||||||
|
|
||||||
Please continue to watch for issues labelled \`help wanted\`, as these will be additional opportunities to contribute.
|
|
||||||
|
|
||||||
You can also see all open issues [through our contributor tool](https://contribute.naomi.lgbt). Also, feel free to join our [Discord server](https://chat.naomi.lgbt) to chat with us and get notified when new issues are available!`;
|
|
@ -1,10 +0,0 @@
|
|||||||
import { GatewayIntentBits } from "discord.js";
|
|
||||||
|
|
||||||
export const IntentOptions = [
|
|
||||||
GatewayIntentBits.Guilds,
|
|
||||||
GatewayIntentBits.GuildMembers,
|
|
||||||
GatewayIntentBits.GuildModeration,
|
|
||||||
GatewayIntentBits.GuildMessages,
|
|
||||||
GatewayIntentBits.MessageContent,
|
|
||||||
GatewayIntentBits.GuildMessageReactions
|
|
||||||
];
|
|
@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* This config is an automatically-generated scale for mapping experience
|
|
||||||
* point values to level values.
|
|
||||||
*/
|
|
||||||
const levelScale: number[] = [];
|
|
||||||
|
|
||||||
let j = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i <= 1000; i++) {
|
|
||||||
j += i * 1000;
|
|
||||||
levelScale[i] = j;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default levelScale;
|
|
@ -1,15 +0,0 @@
|
|||||||
import { configs } from "@prisma/client";
|
|
||||||
|
|
||||||
export const logChannelChoices: { name: string; value: keyof configs }[] = [
|
|
||||||
{ name: "Moderation Action Log Channel", value: "modLogChannel" },
|
|
||||||
{ name: "Private Event Log Channel", value: "eventLogChannel" },
|
|
||||||
{ name: "Message Reporting Channel", value: "messageReportChannel" }
|
|
||||||
];
|
|
||||||
|
|
||||||
export const logChannelChoicesMap: {
|
|
||||||
[key: string]: string;
|
|
||||||
} = {
|
|
||||||
modLogChannel: "moderation actions",
|
|
||||||
eventLogChannel: "gateway events",
|
|
||||||
messageReportChannel: "message reports"
|
|
||||||
};
|
|
@ -1,8 +0,0 @@
|
|||||||
import { GuildPremiumTier } from "discord.js";
|
|
||||||
|
|
||||||
export const ServerUploadLimits: { [tier in GuildPremiumTier]: number } = {
|
|
||||||
0: 8000000,
|
|
||||||
1: 8000000,
|
|
||||||
2: 50000000,
|
|
||||||
3: 100000000
|
|
||||||
};
|
|
@ -1,126 +0,0 @@
|
|||||||
import {
|
|
||||||
Message,
|
|
||||||
ActionRowBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
EmbedBuilder,
|
|
||||||
ButtonStyle,
|
|
||||||
TextInputBuilder,
|
|
||||||
TextInputStyle,
|
|
||||||
ModalBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { EmbedColours } from "../config/EmbedColours";
|
|
||||||
import { Context } from "../interfaces/Context";
|
|
||||||
import { getConfig } from "../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
export const report: Context = {
|
|
||||||
data: {
|
|
||||||
name: "report",
|
|
||||||
type: 3
|
|
||||||
},
|
|
||||||
run: async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
if (!interaction.isMessageContextMenuCommand()) {
|
|
||||||
await interaction.reply({
|
|
||||||
content:
|
|
||||||
"This command is improperly configured. Please contact Naomi.",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const guild = interaction.guild;
|
|
||||||
const message = interaction.options.getMessage(
|
|
||||||
"message",
|
|
||||||
true
|
|
||||||
) as Message;
|
|
||||||
|
|
||||||
if (!guild || !message) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "Could not find the guild record...",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
|
|
||||||
if (!config.messageReportChannel) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "Reporting has not been set up for this server.",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
guild.channels.cache.get(config.messageReportChannel) ||
|
|
||||||
(await guild.channels.fetch(config.messageReportChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "Reporting channel not found.",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle("Message Reported")
|
|
||||||
.setDescription(message.content)
|
|
||||||
.setColor(EmbedColours.ban)
|
|
||||||
.addFields(
|
|
||||||
{
|
|
||||||
name: "Author",
|
|
||||||
value: `<@${message.author.id}>`,
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Reporter",
|
|
||||||
value: `<@${interaction.user.id}>`,
|
|
||||||
inline: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const link = new ButtonBuilder()
|
|
||||||
.setLabel("Message Link")
|
|
||||||
.setStyle(ButtonStyle.Link)
|
|
||||||
.setURL(message.url);
|
|
||||||
const button = new ButtonBuilder()
|
|
||||||
.setLabel("Acknowledge")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setCustomId(`ack-${message.id}`);
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents([
|
|
||||||
link,
|
|
||||||
button
|
|
||||||
]);
|
|
||||||
|
|
||||||
const reportLog = await channel.send({
|
|
||||||
embeds: [embed],
|
|
||||||
components: [row]
|
|
||||||
});
|
|
||||||
|
|
||||||
const reportModal = new ModalBuilder()
|
|
||||||
.setCustomId(`rep-${reportLog.id}`)
|
|
||||||
.setTitle("Report Message");
|
|
||||||
const reasonInput = new TextInputBuilder()
|
|
||||||
.setCustomId("reason")
|
|
||||||
.setLabel("Why are you reporting this message?")
|
|
||||||
.setStyle(TextInputStyle.Paragraph)
|
|
||||||
.setRequired(true);
|
|
||||||
const actionRow = new ActionRowBuilder<TextInputBuilder>().addComponents(
|
|
||||||
reasonInput
|
|
||||||
);
|
|
||||||
reportModal.addComponents(actionRow);
|
|
||||||
|
|
||||||
await interaction.showModal(reportModal);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "report context command", err);
|
|
||||||
await interaction.reply({
|
|
||||||
ephemeral: true,
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
import { sendDebugMessage } from "../utils/sendDebugMessage";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to the database.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
*/
|
|
||||||
export const connectDatabase = async (bot: ExtendedClient) => {
|
|
||||||
try {
|
|
||||||
bot.db = new PrismaClient();
|
|
||||||
await bot.db.$connect();
|
|
||||||
await sendDebugMessage(bot, "Connected to database.");
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "connect database", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,129 +0,0 @@
|
|||||||
import { ExtendedClient } from "../interfaces/ExtendedClient";
|
|
||||||
import { checkEntitledGuild } from "../utils/checkEntitledGuild";
|
|
||||||
|
|
||||||
import { onDisconnect } from "./client/onDisconnect";
|
|
||||||
import { onReady } from "./client/onReady";
|
|
||||||
import { onAuditLogEntry } from "./guild/onAuditLogEntry";
|
|
||||||
import { onGuildCreate } from "./guild/onGuildCreate";
|
|
||||||
import { onGuildDelete } from "./guild/onGuildDelete";
|
|
||||||
import { onInteraction } from "./interaction/onInteraction";
|
|
||||||
import { onMemberAdd } from "./member/onMemberAdd";
|
|
||||||
import { onMemberRemove } from "./member/onMemberRemove";
|
|
||||||
import { onMemberUpdate } from "./member/onMemberUpdate";
|
|
||||||
import { onMessage } from "./message/onMessage";
|
|
||||||
import { onMessageDelete } from "./message/onMessageDelete";
|
|
||||||
import { onMessageEdit } from "./message/onMessageEdit";
|
|
||||||
import { onThreadCreate } from "./thread/onThreadCreate";
|
|
||||||
import { onThreadDelete } from "./thread/onThreadDelete";
|
|
||||||
import { onThreadUpdate } from "./thread/onThreadUpdate";
|
|
||||||
import { onVoiceUpdate } from "./voice/onVoiceUpdate";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module to mount the Discord event listeners.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
*/
|
|
||||||
export const handleEvents = (bot: ExtendedClient) => {
|
|
||||||
/* Client Events */
|
|
||||||
bot.on("ready", async () => await onReady(bot));
|
|
||||||
bot.on("disconnect", () => onDisconnect(bot));
|
|
||||||
|
|
||||||
/* Message Events */
|
|
||||||
bot.on("messageCreate", async (message) => {
|
|
||||||
if (!message.guild || !(await checkEntitledGuild(bot, message.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onMessage(bot, message);
|
|
||||||
});
|
|
||||||
bot.on("messageDelete", async (message) => {
|
|
||||||
if (!message.guild || !(await checkEntitledGuild(bot, message.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onMessageDelete(bot, message);
|
|
||||||
});
|
|
||||||
bot.on("messageUpdate", async (oldMessage, newMessage) => {
|
|
||||||
if (
|
|
||||||
!newMessage.guild ||
|
|
||||||
!(await checkEntitledGuild(bot, newMessage.guild))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onMessageEdit(bot, oldMessage, newMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Interaction Events */
|
|
||||||
bot.on(
|
|
||||||
"interactionCreate",
|
|
||||||
async (interaction) => await onInteraction(bot, interaction)
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Thread Events */
|
|
||||||
bot.on("threadCreate", async (thread) => {
|
|
||||||
if (!thread.guild || !(await checkEntitledGuild(bot, thread.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onThreadCreate(bot, thread);
|
|
||||||
});
|
|
||||||
bot.on("threadDelete", async (thread) => {
|
|
||||||
if (!thread.guild || !(await checkEntitledGuild(bot, thread.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onThreadDelete(bot, thread);
|
|
||||||
});
|
|
||||||
bot.on("threadUpdate", async (oldThread, newThread) => {
|
|
||||||
if (!newThread.guild || !(await checkEntitledGuild(bot, newThread.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onThreadUpdate(bot, oldThread, newThread);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Voice Events */
|
|
||||||
bot.on("voiceStateUpdate", async (oldVoice, newVoice) => {
|
|
||||||
if (!newVoice.guild || !(await checkEntitledGuild(bot, newVoice.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onVoiceUpdate(bot, oldVoice, newVoice);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Member Events */
|
|
||||||
bot.on("guildMemberAdd", async (member) => {
|
|
||||||
if (!member.guild || !(await checkEntitledGuild(bot, member.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onMemberAdd(bot, member);
|
|
||||||
});
|
|
||||||
bot.on("guildMemberRemove", async (member) => {
|
|
||||||
if (!member.guild || !(await checkEntitledGuild(bot, member.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onMemberRemove(bot, member);
|
|
||||||
});
|
|
||||||
bot.on("guildMemberUpdate", async (oldMember, newMember) => {
|
|
||||||
if (!newMember.guild || !(await checkEntitledGuild(bot, newMember.guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onMemberUpdate(bot, oldMember, newMember);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Guild Events */
|
|
||||||
bot.on("guildAuditLogEntryCreate", async (log, guild) => {
|
|
||||||
if (!guild || !(await checkEntitledGuild(bot, guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onAuditLogEntry(bot, log, guild);
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.on("guildCreate", async (guild) => {
|
|
||||||
if (!guild || !(await checkEntitledGuild(bot, guild))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onGuildCreate(bot, guild);
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.on("guildDelete", async (guild) => {
|
|
||||||
if (!guild) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await onGuildDelete(bot, guild);
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,16 +0,0 @@
|
|||||||
import { EmbedBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a message to the debug hook when the bot disconnects.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
*/
|
|
||||||
export const onDisconnect = async (bot: ExtendedClient) => {
|
|
||||||
const disconnectEmbed = new EmbedBuilder();
|
|
||||||
disconnectEmbed.setTitle("Disconnected");
|
|
||||||
disconnectEmbed.setDescription("I have been disconnected from Discord.");
|
|
||||||
disconnectEmbed.setTimestamp();
|
|
||||||
await bot.env.debugHook.send({ embeds: [disconnectEmbed] });
|
|
||||||
};
|
|
@ -1,29 +0,0 @@
|
|||||||
import { scheduleJob } from "node-schedule";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { maintainSecurity } from "../../modules/maintainSecurity";
|
|
||||||
import { postBirthdays } from "../../modules/postBirthdays";
|
|
||||||
import { Prometheus } from "../../modules/prometheus";
|
|
||||||
import { registerCommands } from "../../utils/registerCommands";
|
|
||||||
import { sendDebugMessage } from "../../utils/sendDebugMessage";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the `ready` from Discord.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
*/
|
|
||||||
export const onReady = async (bot: ExtendedClient) => {
|
|
||||||
await sendDebugMessage(bot, `Logged in as ${bot.user?.tag}`);
|
|
||||||
await registerCommands(bot);
|
|
||||||
bot.analytics = new Prometheus(bot);
|
|
||||||
await bot.analytics.updateEntitlements(bot);
|
|
||||||
|
|
||||||
// Daily at 9am PST
|
|
||||||
scheduleJob("birthdays", "0 9 * * *", async () => await postBirthdays(bot));
|
|
||||||
// Daily at midnight and noon
|
|
||||||
scheduleJob(
|
|
||||||
"birthdays",
|
|
||||||
"0 0,12 * * *",
|
|
||||||
async () => await maintainSecurity(bot)
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,78 +0,0 @@
|
|||||||
import { AuditLogEvent, Guild, GuildAuditLogsEntry, User } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getModActionFromAuditLog } from "../../modules/events/getModActionFromAuditLog";
|
|
||||||
import { addCase } from "../../utils/addCase";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
import { sendLogMessage } from "../../utils/sendLogMessage";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles properly logging a manual mod action based on audit logs.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {GuildAuditLogsEntry} log The audit log payload from Discord.
|
|
||||||
* @param {Guild} guild The guild payload from Discord.
|
|
||||||
*/
|
|
||||||
export const onAuditLogEntry = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
log: GuildAuditLogsEntry,
|
|
||||||
guild: Guild
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const { action, changes, executorId, targetId, target, reason } = log;
|
|
||||||
if (executorId === bot.user?.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// if not a mod action we don't care.
|
|
||||||
if (
|
|
||||||
![
|
|
||||||
AuditLogEvent.MemberBanAdd,
|
|
||||||
AuditLogEvent.MemberBanRemove,
|
|
||||||
AuditLogEvent.MemberKick,
|
|
||||||
AuditLogEvent.MemberUpdate
|
|
||||||
].includes(action) ||
|
|
||||||
(action === AuditLogEvent.MemberUpdate &&
|
|
||||||
!changes.find(
|
|
||||||
(change) => change.key === "communication_disabled_until"
|
|
||||||
)) ||
|
|
||||||
!targetId ||
|
|
||||||
!(target instanceof User) ||
|
|
||||||
!executorId
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modAction = getModActionFromAuditLog(log);
|
|
||||||
|
|
||||||
if (!modAction) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reasonString = `This was a manual action pulled from the audit log. Please use the bot for accurate reporting.\n\nReason: ${
|
|
||||||
reason || "Unable to parse reason."
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const caseNum = await addCase(
|
|
||||||
bot,
|
|
||||||
guild.id,
|
|
||||||
target.id,
|
|
||||||
reasonString,
|
|
||||||
modAction,
|
|
||||||
executorId,
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
await sendLogMessage(
|
|
||||||
bot,
|
|
||||||
guild,
|
|
||||||
target,
|
|
||||||
modAction,
|
|
||||||
reasonString,
|
|
||||||
executorId,
|
|
||||||
[],
|
|
||||||
false,
|
|
||||||
caseNum
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on audit log entry", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
import { Guild } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {Guild} guild The newly joined Discord guild.
|
|
||||||
*/
|
|
||||||
export const onGuildCreate = async function (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
guild: Guild
|
|
||||||
) {
|
|
||||||
const owner = await guild.fetchOwner();
|
|
||||||
|
|
||||||
await bot.env.debugHook.send({
|
|
||||||
content: `JOINED GUILD: ${guild.name} (${guild.id}) - owned by ${owner?.displayName} (${owner.id})`
|
|
||||||
});
|
|
||||||
bot.analytics.updateGuilds(bot);
|
|
||||||
await bot.analytics.updateEntitlements(bot);
|
|
||||||
};
|
|
@ -1,44 +0,0 @@
|
|||||||
import { Guild } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {Guild} guild The newly left Discord guild.
|
|
||||||
*/
|
|
||||||
export const onGuildDelete = async function (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
guild: Guild
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
await bot.env.debugHook.send({
|
|
||||||
content: `LEFT GUILD: ${guild.name} (${guild.id}) `
|
|
||||||
});
|
|
||||||
await bot.db.cases
|
|
||||||
.deleteMany({ where: { serverId: guild.id } })
|
|
||||||
.catch(() => null);
|
|
||||||
await bot.db.levelRoles
|
|
||||||
.deleteMany({ where: { serverId: guild.id } })
|
|
||||||
.catch(() => null);
|
|
||||||
await bot.db.levels
|
|
||||||
.deleteMany({ where: { serverId: guild.id } })
|
|
||||||
.catch(() => null);
|
|
||||||
await bot.db.configs
|
|
||||||
.deleteMany({ where: { serverId: guild.id } })
|
|
||||||
.catch(() => null);
|
|
||||||
await bot.db.roles
|
|
||||||
.deleteMany({ where: { serverId: guild.id } })
|
|
||||||
.catch(() => null);
|
|
||||||
await bot.db.birthdays
|
|
||||||
.deleteMany({ where: { serverId: guild.id } })
|
|
||||||
.catch(() => null);
|
|
||||||
await bot.db.security
|
|
||||||
.deleteMany({ where: { serverId: guild.id } })
|
|
||||||
.catch(() => null);
|
|
||||||
bot.analytics.updateGuilds(bot);
|
|
||||||
await bot.analytics.updateEntitlements(bot);
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on guild delete", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,72 +0,0 @@
|
|||||||
import { Interaction } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { handleCopyIdButton } from "../../modules/buttons/handleCopyIdButton";
|
|
||||||
import { handleReportAcknowledgeButton } from "../../modules/buttons/handleReportAcknowledgeButton";
|
|
||||||
import { handleChatInputCommand } from "../../modules/interactions/handleChatInputCommand";
|
|
||||||
import { handleContextMenuCommand } from "../../modules/interactions/handleContextMenuCommand";
|
|
||||||
import { handleMassBanModal } from "../../modules/modals/handleMassBanModal";
|
|
||||||
import { handleMessageReportModal } from "../../modules/modals/handleMessageReportModal";
|
|
||||||
import { checkEntitledGuild } from "../../utils/checkEntitledGuild";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles interactions.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {Interaction} interaction The interaction payload from Discord.
|
|
||||||
*/
|
|
||||||
export const onInteraction = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: Interaction
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
if (interaction.isAutocomplete()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!interaction.guild ||
|
|
||||||
(!(await checkEntitledGuild(bot, interaction.guild)) &&
|
|
||||||
(!interaction.isChatInputCommand() ||
|
|
||||||
interaction.commandName !== "help"))
|
|
||||||
) {
|
|
||||||
await interaction.reply(
|
|
||||||
"Your guild does not appear to be subscribed to use our bot. Please reach out to us in our [support server](<https://chat.naomi.lgbt>) if you would like to sign up."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bot.analytics.commandUsed();
|
|
||||||
if (interaction.isChatInputCommand()) {
|
|
||||||
handleChatInputCommand(bot, interaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.isContextMenuCommand()) {
|
|
||||||
handleContextMenuCommand(bot, interaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.isButton()) {
|
|
||||||
if (interaction.customId.startsWith("ack")) {
|
|
||||||
await handleReportAcknowledgeButton(bot, interaction);
|
|
||||||
}
|
|
||||||
if (interaction.customId.startsWith("copyid")) {
|
|
||||||
await handleCopyIdButton(bot, interaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.isModalSubmit()) {
|
|
||||||
if (interaction.customId === "mass-ban-modal") {
|
|
||||||
await handleMassBanModal(bot, interaction);
|
|
||||||
}
|
|
||||||
if (interaction.customId.startsWith("rep")) {
|
|
||||||
await handleMessageReportModal(bot, interaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "on interaction", err);
|
|
||||||
if (!interaction.isAutocomplete()) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,43 +0,0 @@
|
|||||||
import { GuildMember } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a log message to the configured log channel when a member
|
|
||||||
* joins the server.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {GuildMember} member The user's Discord instance.
|
|
||||||
*/
|
|
||||||
export const onMemberAdd = async (bot: ExtendedClient, member: GuildMember) => {
|
|
||||||
try {
|
|
||||||
const { user, guild } = member;
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
|
|
||||||
if (config.joinRole) {
|
|
||||||
await member.roles.add(config.joinRole).catch(() => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.eventLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await channel.send({
|
|
||||||
content: `${user.tag} (${user.id}) has joined the server. Total Members: ${guild.memberCount}`
|
|
||||||
});
|
|
||||||
bot.analytics.updateUsers(bot);
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on member add", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,61 +0,0 @@
|
|||||||
import { GuildMember, PartialGuildMember } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a log message to the configured log channel when a member
|
|
||||||
* leaves the server.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {GuildMember} member The user's Discord instance.
|
|
||||||
*/
|
|
||||||
export const onMemberRemove = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
member: GuildMember | PartialGuildMember
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const { user, guild } = member;
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
|
|
||||||
await bot.db.birthdays
|
|
||||||
.delete({
|
|
||||||
where: {
|
|
||||||
serverId_userId: { serverId: guild.id, userId: user.id }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
await bot.db.levels
|
|
||||||
.delete({
|
|
||||||
where: {
|
|
||||||
serverId_userId: { serverId: guild.id, userId: user.id }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
|
|
||||||
if (!config.eventLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const joinStamp = member.joinedTimestamp
|
|
||||||
? `<t:${Math.floor(member.joinedTimestamp / 1000)}:F>`
|
|
||||||
: "unknown";
|
|
||||||
|
|
||||||
await channel.send({
|
|
||||||
content: `${user.tag} (${user.id}) has left the server (joined at ${joinStamp}). Total Members: ${guild.memberCount}`
|
|
||||||
});
|
|
||||||
bot.analytics.updateUsers(bot);
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on member remove", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,80 +0,0 @@
|
|||||||
import { GuildMember, PartialGuildMember } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a log message to the configured log channel when a member's
|
|
||||||
* data is updated.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {GuildMember} oldMember The user's old Discord instance.
|
|
||||||
* @param {GuildMember} newMember The user's new Discord instance.
|
|
||||||
*/
|
|
||||||
export const onMemberUpdate = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
oldMember: GuildMember | PartialGuildMember,
|
|
||||||
newMember: GuildMember
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const { user, guild } = newMember;
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
|
|
||||||
if (!config.eventLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldMember.user.tag !== newMember.user.tag) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${user.tag} (${user.id}) has changed their name from ${oldMember.user.tag} to ${newMember.user.tag}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldMember.nickname !== newMember.nickname) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${user.tag} (${user.id}) has changed their nickname from ${
|
|
||||||
oldMember.nickname || "**none**"
|
|
||||||
} to ${newMember.nickname || "**none**"}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const removedRoles = oldMember.roles.cache.filter(
|
|
||||||
(role) => !newMember.roles.cache.has(role.id)
|
|
||||||
);
|
|
||||||
const addedRoles = newMember.roles.cache.filter(
|
|
||||||
(role) => !oldMember.roles.cache.has(role.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (removedRoles.size > 0) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${user.tag} (${
|
|
||||||
user.id
|
|
||||||
}) has removed the following roles: ${removedRoles.map(
|
|
||||||
(role) => role.name
|
|
||||||
)}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addedRoles.size > 0) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${user.tag} (${
|
|
||||||
user.id
|
|
||||||
}) has added the following roles: ${addedRoles.map(
|
|
||||||
(role) => role.name
|
|
||||||
)}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on member update", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,141 +0,0 @@
|
|||||||
import { Message } from "discord.js";
|
|
||||||
|
|
||||||
import levelScale from "../../config/LevelScale";
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { calculateMuteDuration } from "../../modules/commands/calculateMuteDuration";
|
|
||||||
import { checkSpamDomain } from "../../modules/events/checkSpamDomain";
|
|
||||||
import { addCase } from "../../utils/addCase";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
import { sendLogMessage } from "../../utils/sendLogMessage";
|
|
||||||
import { sendModDm } from "../../utils/sendModDm";
|
|
||||||
import { triggerModRequest } from "../../utils/triggerModRequest";
|
|
||||||
|
|
||||||
const linkRegex = /https?:\/\/([a-zA-Z0-9_.-]{2,256}\.\w{2,24}\b)/g;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module to handle the messageCreate event from Discord.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {Message} message The message payload from Discord.
|
|
||||||
*/
|
|
||||||
export const onMessage = async (bot: ExtendedClient, message: Message) => {
|
|
||||||
try {
|
|
||||||
const { guild, member, author, system } = message;
|
|
||||||
if (!guild || !member || system || author.bot) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const links = message.content.match(linkRegex);
|
|
||||||
|
|
||||||
if (links) {
|
|
||||||
for (const link of links) {
|
|
||||||
if (await checkSpamDomain(bot, link.replace(/https?:\/\//, ""))) {
|
|
||||||
await message.delete().catch(() => null);
|
|
||||||
const notified = await sendModDm(
|
|
||||||
bot,
|
|
||||||
"mute",
|
|
||||||
author,
|
|
||||||
guild,
|
|
||||||
"Your account appears to be compromised."
|
|
||||||
);
|
|
||||||
const caseNum = await addCase(
|
|
||||||
bot,
|
|
||||||
guild.id,
|
|
||||||
author.id,
|
|
||||||
"Your account appears to be compromised",
|
|
||||||
"mute",
|
|
||||||
"Automoderator",
|
|
||||||
[link]
|
|
||||||
);
|
|
||||||
await sendLogMessage(
|
|
||||||
bot,
|
|
||||||
guild,
|
|
||||||
author,
|
|
||||||
"mute",
|
|
||||||
"Your account appears to be compromised",
|
|
||||||
"Automoderation",
|
|
||||||
[link],
|
|
||||||
notified,
|
|
||||||
caseNum
|
|
||||||
);
|
|
||||||
await triggerModRequest(bot, {
|
|
||||||
userId: author.id,
|
|
||||||
serverId: guild.id,
|
|
||||||
action: "mute",
|
|
||||||
reason: "Your account appears to be compromised",
|
|
||||||
moderator: "Automoderator",
|
|
||||||
duration: calculateMuteDuration(24, "hours"),
|
|
||||||
pruneDays: 0
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bonus = Math.floor(message.content.length / 10);
|
|
||||||
const pointsEarned = Math.floor(Math.random() * (20 + bonus)) + 5;
|
|
||||||
const user = await bot.db.levels.upsert({
|
|
||||||
where: {
|
|
||||||
serverId_userId: {
|
|
||||||
serverId: guild.id,
|
|
||||||
userId: author.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update: {},
|
|
||||||
create: {
|
|
||||||
serverId: guild.id,
|
|
||||||
userId: author.id,
|
|
||||||
username: author.username,
|
|
||||||
avatar: author.displayAvatarURL(),
|
|
||||||
points: 0,
|
|
||||||
level: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Date.now() - user.cooldown.getTime() < 60000 || user.level >= 1000) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
user.points += pointsEarned;
|
|
||||||
user.cooldown = new Date();
|
|
||||||
let levelUp = false;
|
|
||||||
|
|
||||||
while (user.points > (levelScale[user.level + 1] ?? Infinity)) {
|
|
||||||
user.level++;
|
|
||||||
levelUp = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await bot.db.levels.update({
|
|
||||||
where: {
|
|
||||||
serverId_userId: {
|
|
||||||
serverId: guild.id,
|
|
||||||
userId: author.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
points: user.points,
|
|
||||||
level: user.level,
|
|
||||||
username: author.username,
|
|
||||||
avatar: author.displayAvatarURL(),
|
|
||||||
cooldown: user.cooldown
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (levelUp) {
|
|
||||||
await message.reply(`Congrats! You're now level ${user.level}!!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const levelRoles = await bot.db.levelRoles.findMany({
|
|
||||||
where: {
|
|
||||||
serverId: guild.id,
|
|
||||||
level: {
|
|
||||||
lte: user.level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (const record of levelRoles) {
|
|
||||||
await member.roles.add(record.roleId).catch(() => null);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on message", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,73 +0,0 @@
|
|||||||
import { Message, PartialMessage } from "discord.js";
|
|
||||||
|
|
||||||
import { ServerUploadLimits } from "../../config/ServerUploadLimits";
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { customSubstring } from "../../utils/customSubstring";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a message delete event.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {Message} message The message that was deleted.
|
|
||||||
*/
|
|
||||||
export const onMessageDelete = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
message: Message | PartialMessage
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const { author, channel, content, guild, embeds, attachments, stickers } =
|
|
||||||
message;
|
|
||||||
|
|
||||||
if (!guild || author?.bot) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
|
|
||||||
if (!config.eventLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const logChannel =
|
|
||||||
guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!logChannel || !("send" in logChannel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedContent = content || "**No message content found.**";
|
|
||||||
const mappedAttachements = attachments
|
|
||||||
.map((el) => el)
|
|
||||||
.filter((el) => el.size <= ServerUploadLimits[guild.premiumTier]);
|
|
||||||
const mappedStickers = stickers
|
|
||||||
.map((el) => el)
|
|
||||||
.filter((el) => el.available);
|
|
||||||
|
|
||||||
let logContent = `${author?.tag} (${author?.id}) had a message (${message.id}) deleted in <#${channel.id}>:\n\n\`${deletedContent}\``;
|
|
||||||
|
|
||||||
if (message.reference && message.reference.messageId) {
|
|
||||||
logContent += `\n\n**This message was in reply to: https://discord.com/channels/${guild.id}/${message.reference.channelId}/${message.reference.messageId}**`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachments.size && mappedAttachements.length < attachments.size) {
|
|
||||||
logContent += `\n\n**${
|
|
||||||
attachments.size - mappedAttachements.length
|
|
||||||
} attachment(s) were too large to log.**`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await logChannel.send({
|
|
||||||
content: customSubstring(logContent, 2000),
|
|
||||||
files: mappedAttachements,
|
|
||||||
embeds,
|
|
||||||
stickers: mappedStickers,
|
|
||||||
allowedMentions: {
|
|
||||||
parse: []
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on message delete", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,64 +0,0 @@
|
|||||||
import { Message, PartialMessage } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { customSubstring } from "../../utils/customSubstring";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a message edit event.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {Message} oldMessage The old message payload.
|
|
||||||
* @param {Message} newMessage The new message payload.
|
|
||||||
*/
|
|
||||||
export const onMessageEdit = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
oldMessage: Message | PartialMessage,
|
|
||||||
newMessage: Message | PartialMessage
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const { author, channel, guild } = newMessage;
|
|
||||||
|
|
||||||
if (!guild || author?.bot) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!oldMessage.content ||
|
|
||||||
!newMessage.content ||
|
|
||||||
oldMessage.content === newMessage.content
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
|
|
||||||
if (!config.eventLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const logChannel =
|
|
||||||
guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!logChannel || !("send" in logChannel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await logChannel.send({
|
|
||||||
content: `${author?.tag} (${author?.id}) edited their message in in <#${
|
|
||||||
channel.id
|
|
||||||
}>:\n\n**Old Content:**\n\`${customSubstring(
|
|
||||||
oldMessage.content,
|
|
||||||
1750
|
|
||||||
)}\`\n\n**New content:**\n\`${customSubstring(
|
|
||||||
newMessage.content,
|
|
||||||
1750
|
|
||||||
)}\`\n\n${newMessage.url}`,
|
|
||||||
allowedMentions: { parse: [] }
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on message edit", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,42 +0,0 @@
|
|||||||
import { ThreadChannel } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the creation of a new thread.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ThreadChannel} thread The thread payload from Discord.
|
|
||||||
*/
|
|
||||||
export const onThreadCreate = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
thread: ThreadChannel
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
if (thread.joinable) {
|
|
||||||
await thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await getConfig(bot, thread.guild.id);
|
|
||||||
|
|
||||||
if (!config.eventLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
thread.guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await thread.guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await channel.send({
|
|
||||||
content: `${thread.name} has been created in <#${thread.parentId}>`
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on thread create", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,38 +0,0 @@
|
|||||||
import { ThreadChannel } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the deletion of a thread.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ThreadChannel} thread The thread payload from Discord.
|
|
||||||
*/
|
|
||||||
export const onThreadDelete = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
thread: ThreadChannel
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const config = await getConfig(bot, thread.guild.id);
|
|
||||||
|
|
||||||
if (!config.eventLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
thread.guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await thread.guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await channel.send({
|
|
||||||
content: `${thread.name} has been deleted from <#${thread.parentId}>`
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on thread create", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,54 +0,0 @@
|
|||||||
import { ThreadChannel } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a thread update.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ThreadChannel} oldThread The old thread payload.
|
|
||||||
* @param {ThreadChannel} newThread The new thread payload.
|
|
||||||
*/
|
|
||||||
export const onThreadUpdate = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
oldThread: ThreadChannel,
|
|
||||||
newThread: ThreadChannel
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const config = await getConfig(bot, newThread.guild.id);
|
|
||||||
|
|
||||||
if (!config.eventLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
oldThread.guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await oldThread.guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!oldThread.archived && newThread.archived) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newThread.name} has been archived <#${newThread.parentId}>`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldThread.archived && !newThread.archived) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newThread.name} has been unarchived <#${newThread.parentId}>`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldThread.name !== newThread.name) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${oldThread.name} has been renamed to ${newThread.name} in <#${newThread.parentId}>`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on thread update", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,82 +0,0 @@
|
|||||||
import { VoiceState } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { getConfig } from "../../modules/data/getConfig";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles voice state updates.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {VoiceState} oldVoice The old voice state.
|
|
||||||
* @param {VoiceState} newVoice The new voice state.
|
|
||||||
*/
|
|
||||||
export const onVoiceUpdate = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
oldVoice: VoiceState,
|
|
||||||
newVoice: VoiceState
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const config = await getConfig(bot, newVoice.guild.id);
|
|
||||||
|
|
||||||
if (!config.eventLogChannel || !newVoice.member) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
newVoice.guild.channels.cache.get(config.eventLogChannel) ||
|
|
||||||
(await newVoice.guild.channels.fetch(config.eventLogChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
oldVoice.channelId &&
|
|
||||||
newVoice.channelId &&
|
|
||||||
oldVoice.channelId !== newVoice.channelId
|
|
||||||
) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newVoice.member.user.tag} (${newVoice.member.id}) has moved from <#!${oldVoice.channelId}> to <#!${newVoice.channelId}>.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVoice.channelId && !newVoice.channelId) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newVoice.member.user.tag} (${newVoice.member.id}) has disconnected from <#!${oldVoice.channelId}>.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!oldVoice.channelId && newVoice.channelId) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newVoice.member.user.tag} (${newVoice.member.id}) has connected to <#!${newVoice.channelId}>.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVoice.mute && !newVoice.mute) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newVoice.member.user.tag} (${newVoice.member.id}) has been unmuted.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!oldVoice.mute && newVoice.mute) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newVoice.member.user.tag} (${newVoice.member.id}) has been muted.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVoice.deaf && !newVoice.deaf) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newVoice.member.user.tag} (${newVoice.member.id}) has been undeafened.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!oldVoice.deaf && newVoice.deaf) {
|
|
||||||
await channel.send({
|
|
||||||
content: `${newVoice.member.user.tag} (${newVoice.member.id}) has been deafened.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "on voice update", err);
|
|
||||||
}
|
|
||||||
};
|
|
24
src/index.ts
24
src/index.ts
@ -1,24 +0,0 @@
|
|||||||
import { Client } from "discord.js";
|
|
||||||
|
|
||||||
import { IntentOptions } from "./config/IntentOptions";
|
|
||||||
import { connectDatabase } from "./database/connectDatabase";
|
|
||||||
import { handleEvents } from "./events/_handleEvents";
|
|
||||||
import { ExtendedClient } from "./interfaces/ExtendedClient";
|
|
||||||
import { validateEnv } from "./modules/validateEnv";
|
|
||||||
import { serve } from "./server/serve";
|
|
||||||
import { loadCommands } from "./utils/loadCommands";
|
|
||||||
import { loadContexts } from "./utils/loadContexts";
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const bot = new Client({ intents: IntentOptions }) as ExtendedClient;
|
|
||||||
bot.env = validateEnv();
|
|
||||||
bot.configs = {};
|
|
||||||
bot.commands = await loadCommands(bot);
|
|
||||||
bot.contexts = await loadContexts(bot);
|
|
||||||
|
|
||||||
await connectDatabase(bot);
|
|
||||||
handleEvents(bot);
|
|
||||||
serve(bot);
|
|
||||||
|
|
||||||
await bot.login(bot.env.token);
|
|
||||||
})();
|
|
@ -1,30 +0,0 @@
|
|||||||
type PastAction =
|
|
||||||
| "warned"
|
|
||||||
| "kicked"
|
|
||||||
| "banned"
|
|
||||||
| "muted"
|
|
||||||
| "unmuted"
|
|
||||||
| "unbanned"
|
|
||||||
| "noted"
|
|
||||||
| "softbanned";
|
|
||||||
|
|
||||||
export type Action =
|
|
||||||
| "warn"
|
|
||||||
| "kick"
|
|
||||||
| "ban"
|
|
||||||
| "mute"
|
|
||||||
| "unmute"
|
|
||||||
| "unban"
|
|
||||||
| "note"
|
|
||||||
| "softban";
|
|
||||||
|
|
||||||
export const ActionToPastTense: { [key in Action]: PastAction } = {
|
|
||||||
warn: "warned",
|
|
||||||
kick: "kicked",
|
|
||||||
ban: "banned",
|
|
||||||
mute: "muted",
|
|
||||||
unmute: "unmuted",
|
|
||||||
unban: "unbanned",
|
|
||||||
note: "noted",
|
|
||||||
softban: "softbanned"
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
import { Action } from "./Action";
|
|
||||||
|
|
||||||
export interface ActionPayload {
|
|
||||||
userId: string;
|
|
||||||
serverId: string;
|
|
||||||
action: Action;
|
|
||||||
reason: string;
|
|
||||||
moderator: string;
|
|
||||||
duration?: number;
|
|
||||||
pruneDays?: number | undefined;
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
SlashCommandBuilder,
|
|
||||||
SlashCommandOptionsOnlyBuilder,
|
|
||||||
SlashCommandSubcommandsOnlyBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "./ExtendedClient";
|
|
||||||
import { GuildCommandInteraction } from "./Interactions";
|
|
||||||
|
|
||||||
export interface Command {
|
|
||||||
data:
|
|
||||||
| SlashCommandOptionsOnlyBuilder
|
|
||||||
| Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">
|
|
||||||
| SlashCommandSubcommandsOnlyBuilder;
|
|
||||||
run: (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: GuildCommandInteraction
|
|
||||||
) => Promise<void>;
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import { ExtendedClient } from "./ExtendedClient";
|
|
||||||
import { GuildCommandInteraction } from "./Interactions";
|
|
||||||
|
|
||||||
export type CommandHandler = (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: GuildCommandInteraction,
|
|
||||||
config: ExtendedClient["configs"][""]
|
|
||||||
) => Promise<void>;
|
|
@ -1,13 +0,0 @@
|
|||||||
import { ExtendedClient } from "./ExtendedClient";
|
|
||||||
import { GuildContextInteraction } from "./Interactions";
|
|
||||||
|
|
||||||
export interface Context {
|
|
||||||
data: {
|
|
||||||
name: string;
|
|
||||||
type: 2 | 3;
|
|
||||||
};
|
|
||||||
run: (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: GuildContextInteraction
|
|
||||||
) => Promise<void>;
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
import { PrismaClient, configs } from "@prisma/client";
|
|
||||||
import { Client, WebhookClient } from "discord.js";
|
|
||||||
|
|
||||||
import { Command } from "./Command";
|
|
||||||
import { Context } from "./Context";
|
|
||||||
|
|
||||||
import type { Prometheus } from "../modules/prometheus.js";
|
|
||||||
|
|
||||||
export interface ExtendedClient extends Client {
|
|
||||||
env: {
|
|
||||||
token: string;
|
|
||||||
debugHook: WebhookClient;
|
|
||||||
mongoUri: string;
|
|
||||||
devMode: boolean;
|
|
||||||
};
|
|
||||||
db: PrismaClient;
|
|
||||||
analytics: Prometheus;
|
|
||||||
commands: Command[];
|
|
||||||
contexts: Context[];
|
|
||||||
configs: { [serverId: string]: Omit<configs, "id"> };
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
/**
|
|
||||||
* The structure of the NESTED issue data from the GitHub Webhook.
|
|
||||||
*/
|
|
||||||
interface GithubIssuePayload {
|
|
||||||
id: number;
|
|
||||||
node_id: string;
|
|
||||||
url: string;
|
|
||||||
repository_url: string;
|
|
||||||
html_url: string;
|
|
||||||
number: number;
|
|
||||||
state: string;
|
|
||||||
state_reason: string | null;
|
|
||||||
title: string;
|
|
||||||
body: string;
|
|
||||||
user: GithubUserPayload;
|
|
||||||
created_at: string;
|
|
||||||
updated_at: string;
|
|
||||||
closed_by: GithubUserPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GithubPullRequestPayload {
|
|
||||||
html_url: string;
|
|
||||||
body: string;
|
|
||||||
number: number;
|
|
||||||
merged: boolean;
|
|
||||||
title: string;
|
|
||||||
user: GithubUserPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure of the repo data, sent on pretty much
|
|
||||||
* every GitHub Webhook payload.
|
|
||||||
*/
|
|
||||||
interface GithubRepoPayload {
|
|
||||||
id: number;
|
|
||||||
node_id: string;
|
|
||||||
name: string;
|
|
||||||
full_name: string;
|
|
||||||
owner: GithubUserPayload;
|
|
||||||
private: boolean;
|
|
||||||
html_url: string;
|
|
||||||
description: string;
|
|
||||||
fork: boolean;
|
|
||||||
url: string;
|
|
||||||
archive_url: string;
|
|
||||||
assignees_url: string;
|
|
||||||
blobs_url: string;
|
|
||||||
branches_url: string;
|
|
||||||
collaborators_url: string;
|
|
||||||
comments_url: string;
|
|
||||||
commits_url: string;
|
|
||||||
compare_url: string;
|
|
||||||
contents_url: string;
|
|
||||||
contributors_url: string;
|
|
||||||
deployments_url: string;
|
|
||||||
downloads_url: string;
|
|
||||||
events_url: string;
|
|
||||||
forks_url: string;
|
|
||||||
git_commits_url: string;
|
|
||||||
git_refs_url: string;
|
|
||||||
git_tags_url: string;
|
|
||||||
git_url: string;
|
|
||||||
issue_comment_url: string;
|
|
||||||
issue_events_url: string;
|
|
||||||
issues_url: string;
|
|
||||||
keys_url: string;
|
|
||||||
labels_url: string;
|
|
||||||
languages_url: string;
|
|
||||||
merges_url: string;
|
|
||||||
milestones_url: string;
|
|
||||||
notifications_url: string;
|
|
||||||
pulls_url: string;
|
|
||||||
releases_url: string;
|
|
||||||
ssh_url: string;
|
|
||||||
stargazers_url: string;
|
|
||||||
statuses_url: string;
|
|
||||||
subscribers_url: string;
|
|
||||||
subscription_url: string;
|
|
||||||
tags_url: string;
|
|
||||||
teams_url: string;
|
|
||||||
trees_url: string;
|
|
||||||
clone_url: string;
|
|
||||||
mirror_url: string;
|
|
||||||
hooks_url: string;
|
|
||||||
svn_url: string;
|
|
||||||
homepage: string;
|
|
||||||
language: string | null;
|
|
||||||
forks: number;
|
|
||||||
forks_count: number;
|
|
||||||
stargazers_count: number;
|
|
||||||
watchers_count: number;
|
|
||||||
watchers: number;
|
|
||||||
size: number;
|
|
||||||
default_branch: string;
|
|
||||||
open_issues_count: number;
|
|
||||||
open_issues: number;
|
|
||||||
created_at: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GithubUserPayload {
|
|
||||||
login: string;
|
|
||||||
id: number;
|
|
||||||
node_id: string;
|
|
||||||
avatar_url: string;
|
|
||||||
gravatar_id: string;
|
|
||||||
url: string;
|
|
||||||
html_url: string;
|
|
||||||
followers_url: string;
|
|
||||||
following_url: string;
|
|
||||||
gists_url: string;
|
|
||||||
starred_url: string;
|
|
||||||
subscriptions_url: string;
|
|
||||||
organizations_url: string;
|
|
||||||
repos_url: string;
|
|
||||||
events_url: string;
|
|
||||||
received_events_url: string;
|
|
||||||
type: string;
|
|
||||||
site_admin: boolean;
|
|
||||||
name: string;
|
|
||||||
company: string;
|
|
||||||
blog: string;
|
|
||||||
location: string;
|
|
||||||
email: string;
|
|
||||||
hireable: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The structure of the comment data from the Github Webhook.
|
|
||||||
*/
|
|
||||||
export interface GithubCommentPayload {
|
|
||||||
action: string;
|
|
||||||
issue: GithubIssuePayload;
|
|
||||||
comment: {
|
|
||||||
html_url: string;
|
|
||||||
body: string;
|
|
||||||
user: GithubUserPayload;
|
|
||||||
};
|
|
||||||
repository: GithubRepoPayload;
|
|
||||||
sender: GithubUserPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GithubForkPayload {
|
|
||||||
forkee: GithubRepoPayload;
|
|
||||||
repository: GithubRepoPayload;
|
|
||||||
sender: GithubUserPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The structure of the top level issue data from the GitHub webhook.
|
|
||||||
*/
|
|
||||||
export interface GithubIssuesPayload {
|
|
||||||
action: string;
|
|
||||||
issue: GithubIssuePayload;
|
|
||||||
repository: GithubRepoPayload;
|
|
||||||
sender: GithubUserPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The structure of the ping payload when a new GitHub webhook
|
|
||||||
* is initialised.
|
|
||||||
*/
|
|
||||||
export interface GithubPingPayload {
|
|
||||||
zen: string;
|
|
||||||
hook_id: string;
|
|
||||||
hook: Record<string, unknown>;
|
|
||||||
repository: GithubRepoPayload;
|
|
||||||
organization: Record<string, unknown>;
|
|
||||||
sender: GithubUserPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure of the pull request data from the GitHub Webhook.
|
|
||||||
*/
|
|
||||||
export interface GithubPullPayload {
|
|
||||||
action: string;
|
|
||||||
number: number;
|
|
||||||
pull_request: GithubPullRequestPayload;
|
|
||||||
repository: GithubRepoPayload;
|
|
||||||
sender: GithubUserPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure of the star data sent from the GitHub Webhook.
|
|
||||||
*/
|
|
||||||
export interface GithubStarPayload {
|
|
||||||
action: "created" | "deleted";
|
|
||||||
starred_at: string;
|
|
||||||
repository: GithubRepoPayload;
|
|
||||||
sender: GithubUserPayload;
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import {
|
|
||||||
ChatInputCommandInteraction,
|
|
||||||
ContextMenuCommandInteraction,
|
|
||||||
Guild,
|
|
||||||
GuildMember
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
export interface GuildCommandInteraction extends ChatInputCommandInteraction {
|
|
||||||
guild: Guild;
|
|
||||||
member: GuildMember;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GuildContextInteraction extends ContextMenuCommandInteraction {
|
|
||||||
guild: Guild;
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import { ButtonInteraction } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the logic for the acknowledge button on message reports.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ButtonInteraction} interaction The interaction payload from Discord.
|
|
||||||
*/
|
|
||||||
export const handleCopyIdButton = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: ButtonInteraction
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const id = interaction.customId.split("-")[1];
|
|
||||||
await interaction.editReply({
|
|
||||||
content: id || "Unable to parse ID."
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "handle copy id button", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,43 +0,0 @@
|
|||||||
import { ButtonInteraction } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the logic for the acknowledge button on message reports.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ButtonInteraction} interaction The interaction payload from Discord.
|
|
||||||
*/
|
|
||||||
export const handleReportAcknowledgeButton = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: ButtonInteraction
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferUpdate();
|
|
||||||
const message = interaction.message;
|
|
||||||
const embed = message.embeds[0];
|
|
||||||
await interaction.editReply({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: embed?.title || "wtf",
|
|
||||||
description: embed?.description || "wtf",
|
|
||||||
fields: [
|
|
||||||
...(embed?.fields ?? []),
|
|
||||||
{
|
|
||||||
name: "Acknowledged by",
|
|
||||||
value: `<@${interaction.user.id}>`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
color: 0x00ff00
|
|
||||||
}
|
|
||||||
],
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "handle report acknowledge button", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Parses a value/unit pair into a number of milliseconds. For example,
|
|
||||||
* (1, "seconds") would return one second in milliseconds.
|
|
||||||
*
|
|
||||||
* @param {number} value The number of "unit" to convert to milliseconds.
|
|
||||||
* @param {string} unit The unit of time to convert to milliseconds.
|
|
||||||
* @returns {number} The number of milliseconds.
|
|
||||||
*/
|
|
||||||
export const calculateMuteDuration = (value: number, unit: string) => {
|
|
||||||
switch (unit) {
|
|
||||||
case "seconds":
|
|
||||||
return value * 1000;
|
|
||||||
case "minutes":
|
|
||||||
return value * 60000;
|
|
||||||
case "hours":
|
|
||||||
return value * 3600000;
|
|
||||||
case "days":
|
|
||||||
return value * 86400000;
|
|
||||||
case "weeks":
|
|
||||||
return value * 604800000;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,213 +0,0 @@
|
|||||||
import { levels } from "@prisma/client";
|
|
||||||
import { AttachmentBuilder } from "discord.js";
|
|
||||||
import nodeHtmlToImage from "node-html-to-image";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an image from the user's profile settings, converts it into a Discord
|
|
||||||
* attachment, and returns it.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} CamperChan The CamperChan's Discord instance.
|
|
||||||
* @param {levels} record The user's record from the database.
|
|
||||||
* @returns {AttachmentBuilder} The attachment, or null on error.
|
|
||||||
*/
|
|
||||||
export const generateProfileImage = async (
|
|
||||||
CamperChan: ExtendedClient,
|
|
||||||
record: levels
|
|
||||||
): Promise<AttachmentBuilder | null> => {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
avatar,
|
|
||||||
backgroundColour,
|
|
||||||
backgroundImage,
|
|
||||||
colour,
|
|
||||||
username,
|
|
||||||
level,
|
|
||||||
points
|
|
||||||
} = record;
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Roboto";
|
|
||||||
src: url("https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700");
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background: url(${backgroundImage}) no-repeat center center fixed;
|
|
||||||
background-size: cover;
|
|
||||||
width: 1920px;
|
|
||||||
height: 1080px;
|
|
||||||
text-align: center;
|
|
||||||
font-family: "Roboto", Courier, monospace;
|
|
||||||
font-size: 75px;
|
|
||||||
padding: 2.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #${backgroundColour}bf;
|
|
||||||
color: #${colour};
|
|
||||||
padding: 2.5%;
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 250px;
|
|
||||||
height: 250px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 125px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 250px auto;
|
|
||||||
justify-items: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<div class="header">
|
|
||||||
<img class="avatar" src=${avatar || "https://cdn.freecodecamp.org/platform/universal/fcc_puck_500.jpg"}></img>
|
|
||||||
<div>
|
|
||||||
<h1>${username}</h1>
|
|
||||||
<p>Level ${level} (${points.toLocaleString("en-GB")}xp)</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
`;
|
|
||||||
const alt = `${username} is at level ${level} with ${points.toLocaleString("en-GB")} experience points.`;
|
|
||||||
|
|
||||||
const image = await nodeHtmlToImage({
|
|
||||||
html,
|
|
||||||
selector: "body",
|
|
||||||
transparent: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!(image instanceof Buffer)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, {
|
|
||||||
name: `${username}.png`,
|
|
||||||
description: alt
|
|
||||||
});
|
|
||||||
|
|
||||||
return attachment;
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(CamperChan, "generate profile image module", err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the image for the leaderboard.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} CamperChan The CamperChan's Discord instance.
|
|
||||||
* @param {levels} levels The user's record from the database.
|
|
||||||
* @returns {AttachmentBuilder} The attachment, or null on error.
|
|
||||||
*/
|
|
||||||
export const generateLeaderboardImage = async (
|
|
||||||
CamperChan: ExtendedClient,
|
|
||||||
levels: (levels & { index: number })[]
|
|
||||||
): Promise<AttachmentBuilder | null> => {
|
|
||||||
try {
|
|
||||||
const html = `
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Roboto";
|
|
||||||
src: url("https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700");
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background: transparent;
|
|
||||||
height: 4100px;
|
|
||||||
width: 2510px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 250px;
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 100px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
width: 2500px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 250px 2250px;
|
|
||||||
height: 400px;
|
|
||||||
margin: 5px 10px;
|
|
||||||
justify-items: left;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<body>
|
|
||||||
${levels.map(
|
|
||||||
(l) =>
|
|
||||||
`<div class="row" style="background-color: #${l.backgroundColour || "0a0a23"}bf;color: #${l.colour || "d0d0d5"};padding: 2.5%;border-radius: 100px;">
|
|
||||||
<img style="border-radius: 50%;" src=${l.avatar || "https://cdn.freecodecamp.org/platform/universal/fcc_puck_500.jpg"}></img>
|
|
||||||
<div style="text-align: left;padding-left:100px;">
|
|
||||||
<h1>#${l.index}. ${l.username}</h1>
|
|
||||||
<p>Level ${l.level} (${l.points}xp)</p>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
)}
|
|
||||||
</body>
|
|
||||||
`;
|
|
||||||
const alt = levels
|
|
||||||
.map(
|
|
||||||
(l) =>
|
|
||||||
`${l.username} is rank ${l.index} at ${l.level} with ${l.points.toLocaleString("en-GB")} experience points.`
|
|
||||||
)
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
const image = await nodeHtmlToImage({
|
|
||||||
html,
|
|
||||||
selector: "body",
|
|
||||||
transparent: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!(image instanceof Buffer)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachment = new AttachmentBuilder(image, {
|
|
||||||
name: `leaderboard-${levels[0]?.index}.png`,
|
|
||||||
description: alt
|
|
||||||
});
|
|
||||||
|
|
||||||
return attachment;
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(CamperChan, "generate leaderboard image module", err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Checks if a string matches a 6 character hex code.
|
|
||||||
*
|
|
||||||
* @param {string} colour The colour code to validate.
|
|
||||||
* @returns {boolean} If the string is in the correct format.
|
|
||||||
*/
|
|
||||||
export const validateColour = (colour: string): boolean => {
|
|
||||||
return /[\da-f]{6}/gi.test(colour);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a url points to a valid image.
|
|
||||||
*
|
|
||||||
* @param {string} url The URL to validate.
|
|
||||||
* @returns {boolean} If the URL provides a 2XX response, and if the response content type
|
|
||||||
* is an image.
|
|
||||||
*/
|
|
||||||
export const validateImage = async (url: string): Promise<boolean> => {
|
|
||||||
const validImage = await fetch(url, {
|
|
||||||
method: "HEAD"
|
|
||||||
}).catch(() => null);
|
|
||||||
|
|
||||||
if (!validImage) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validImage.headers.get("content-type")?.startsWith("image/")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
@ -1,39 +0,0 @@
|
|||||||
import { configs } from "@prisma/client";
|
|
||||||
|
|
||||||
import { defaultConfig } from "../../config/DefaultConfig";
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module to get the server config from the cache, database, or default.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {string} serverId The ID of the server to get the config for.
|
|
||||||
* @returns {ExtendedClient["config"]} The server config.
|
|
||||||
*/
|
|
||||||
export const getConfig = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
serverId: string
|
|
||||||
): Promise<Omit<configs, "id">> => {
|
|
||||||
try {
|
|
||||||
const exists = bot.configs[serverId];
|
|
||||||
if (exists) {
|
|
||||||
return exists;
|
|
||||||
}
|
|
||||||
const record = await bot.db.configs.upsert({
|
|
||||||
where: {
|
|
||||||
serverId
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
...defaultConfig,
|
|
||||||
serverId
|
|
||||||
},
|
|
||||||
update: {}
|
|
||||||
});
|
|
||||||
bot.configs[serverId] = record;
|
|
||||||
return record;
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "get config", err);
|
|
||||||
return defaultConfig;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,40 +0,0 @@
|
|||||||
import { defaultConfig } from "../../config/DefaultConfig";
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module to update a config.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {string} serverId The ID of the server to update.
|
|
||||||
* @param {keyof ExtendedClient["config"]} setting The setting to update.
|
|
||||||
* @param {number | string} value The value to update the setting to.
|
|
||||||
* @returns {boolean} True on successful update.
|
|
||||||
*/
|
|
||||||
export const setConfig = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
serverId: string,
|
|
||||||
setting: keyof ExtendedClient["configs"][""],
|
|
||||||
value: number | string
|
|
||||||
): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const configData = await bot.db.configs.upsert({
|
|
||||||
where: {
|
|
||||||
serverId
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
...defaultConfig,
|
|
||||||
serverId,
|
|
||||||
[setting]: value
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
[setting]: value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
bot.configs[serverId] = configData;
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "set config", err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,46 +0,0 @@
|
|||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a domain is a known source of Discord scams.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {string} domain The domain to validate. DO NOT include the protocol or path.
|
|
||||||
* @returns {boolean} True if the domain is known as a scam.
|
|
||||||
*/
|
|
||||||
export const checkSpamDomain = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
domain: string
|
|
||||||
): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const walshyReq = await fetch("https://bad-domains.walshy.dev/check", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
accept: "application/json",
|
|
||||||
"X-Identity": "Naomi's mod bot - built by naomi_lgbt"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ domain })
|
|
||||||
});
|
|
||||||
const walshyRes = (await walshyReq.json()) as { badDomain: boolean };
|
|
||||||
if (walshyRes.badDomain) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const yachtsReq = await fetch(
|
|
||||||
`https://phish.sinking.yachts/v2/check/${encodeURI(domain)}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
accept: "application/json",
|
|
||||||
"X-Identity": "Naomi's mod bot - built by naomi_lgbt"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const yachtsRes = (await yachtsReq.json()) as boolean;
|
|
||||||
if (yachtsRes) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "load spam domains", err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,35 +0,0 @@
|
|||||||
import { AuditLogEvent, GuildAuditLogsEntry } from "discord.js";
|
|
||||||
|
|
||||||
import { Action } from "../../interfaces/Action";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module to parse the audit log entry and return the moderation action.
|
|
||||||
*
|
|
||||||
* @param {GuildAuditLogsEntry} log The audit log entry payload.
|
|
||||||
* @returns {Action | null} The mod action string, or null if not found.
|
|
||||||
*/
|
|
||||||
export const getModActionFromAuditLog = (
|
|
||||||
log: GuildAuditLogsEntry
|
|
||||||
): Action | null => {
|
|
||||||
const muteChange = log.changes.find(
|
|
||||||
(change) => change.key === "communication_disabled_until"
|
|
||||||
);
|
|
||||||
switch (log.action) {
|
|
||||||
case AuditLogEvent.MemberBanAdd:
|
|
||||||
return "ban";
|
|
||||||
case AuditLogEvent.MemberBanRemove:
|
|
||||||
return "unban";
|
|
||||||
case AuditLogEvent.MemberKick:
|
|
||||||
return "kick";
|
|
||||||
case AuditLogEvent.MemberUpdate:
|
|
||||||
if (!muteChange) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (muteChange.new) {
|
|
||||||
return "mute";
|
|
||||||
}
|
|
||||||
return "unmute";
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,55 +0,0 @@
|
|||||||
import { ChatInputCommandInteraction } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
import { isModerator } from "../../utils/isModerator";
|
|
||||||
import { isGuildCommandInteraction } from "../validateGuildCommands";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the logic for running slash commands.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ChatInputCommandInteraction} interaction The interaction payload from Discord.
|
|
||||||
*/
|
|
||||||
export const handleChatInputCommand = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: ChatInputCommandInteraction
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
if (!isGuildCommandInteraction(interaction)) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "You can only use commands in a server.",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const command = bot.commands.find(
|
|
||||||
(c) => c.data.name === interaction.commandName
|
|
||||||
);
|
|
||||||
if (!command) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "That's not a valid command. Please contact Naomi.",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(!interaction.member || !isModerator(interaction.member)) &&
|
|
||||||
!["leaderboard", "rank", "profile", "role", "help", "ping"].includes(
|
|
||||||
interaction.commandName
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "You must be a moderator to use bot commands.",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await command.run(bot, interaction);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "handle chat input command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,42 +0,0 @@
|
|||||||
import { ContextMenuCommandInteraction } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
import { isGuildContextInteraction } from "../validateGuildCommands";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the logic for running context commands.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ContextMenuCommandInteraction} interaction The interaction payload from Discord.
|
|
||||||
*/
|
|
||||||
export const handleContextMenuCommand = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: ContextMenuCommandInteraction
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
if (!isGuildContextInteraction(interaction)) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "You can only use this in a server.",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const context = bot.contexts.find(
|
|
||||||
(c) => c.data.name === interaction.commandName
|
|
||||||
);
|
|
||||||
if (!context) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "That's not a valid context. Please contact Naomi.",
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await context.run(bot, interaction);
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "handle context menu command", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,47 +0,0 @@
|
|||||||
import { ExtendedClient } from "../interfaces/ExtendedClient";
|
|
||||||
import { checkEntitledGuild } from "../utils/checkEntitledGuild";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loops through the list of configured servers to lock security for, and processes the
|
|
||||||
* API calls.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
*/
|
|
||||||
export const maintainSecurity = async (bot: ExtendedClient) => {
|
|
||||||
try {
|
|
||||||
const records = await bot.db.security.findMany();
|
|
||||||
const date = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
|
|
||||||
for (const record of records) {
|
|
||||||
const guild =
|
|
||||||
bot.guilds.cache.get(record.serverId) ||
|
|
||||||
(await bot.guilds.fetch(record.serverId).catch(() => null));
|
|
||||||
if (!guild) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const isEntitled = await checkEntitledGuild(bot, guild);
|
|
||||||
if (!isEntitled) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await fetch(
|
|
||||||
`https://discord.com/api/v10/guilds/${record.serverId}/incident-actions`,
|
|
||||||
{
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bot ${bot.env.token}`,
|
|
||||||
"content-type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
dms_disabled_until: record.lockDms ? date : null,
|
|
||||||
invites_disabled_until: record.lockInvites ? date : null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
).catch(
|
|
||||||
async (e) =>
|
|
||||||
await errorHandler(bot, `incident-actions for ${record.serverId}`, e)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "maintain security", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,127 +0,0 @@
|
|||||||
import {
|
|
||||||
ActionRowBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
ButtonStyle,
|
|
||||||
ComponentType,
|
|
||||||
EmbedBuilder,
|
|
||||||
Message,
|
|
||||||
ModalSubmitInteraction
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
import { getConfig } from "../data/getConfig";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the submission of the mass ban form.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ModalSubmitInteraction} interaction The interaction payload from Discord.
|
|
||||||
*/
|
|
||||||
export const handleMassBanModal = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: ModalSubmitInteraction
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
const { guild } = interaction;
|
|
||||||
if (!guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "This command can only be used in a guild."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawBanList = interaction.fields.getTextInputValue("mass-ban-ids");
|
|
||||||
const banList = rawBanList.trim().split(/\b/g);
|
|
||||||
|
|
||||||
const reason = interaction.fields.getTextInputValue("reason");
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder();
|
|
||||||
embed.setTitle("Confirm Mass Ban of Following IDs:");
|
|
||||||
embed.setDescription(banList.join("\n"));
|
|
||||||
embed.addFields({
|
|
||||||
name: "Reason",
|
|
||||||
value: reason
|
|
||||||
});
|
|
||||||
|
|
||||||
const yes = new ButtonBuilder()
|
|
||||||
.setCustomId("confirm")
|
|
||||||
.setLabel("Confirm")
|
|
||||||
.setStyle(ButtonStyle.Success);
|
|
||||||
const no = new ButtonBuilder()
|
|
||||||
.setCustomId("cancel")
|
|
||||||
.setLabel("Cancel")
|
|
||||||
.setStyle(ButtonStyle.Danger);
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(yes, no);
|
|
||||||
const response = (await interaction.editReply({
|
|
||||||
embeds: [embed],
|
|
||||||
components: [row]
|
|
||||||
})) as Message;
|
|
||||||
|
|
||||||
const collector =
|
|
||||||
response.createMessageComponentCollector<ComponentType.Button>({
|
|
||||||
filter: (click) => click.user.id === interaction.user.id,
|
|
||||||
time: 10000,
|
|
||||||
max: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
collector.on("end", async (clicks) => {
|
|
||||||
const choice = clicks.first()?.customId;
|
|
||||||
if (!clicks || clicks.size <= 0 || !choice) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "This command has timed out.",
|
|
||||||
embeds: [],
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "confirm") {
|
|
||||||
for (const id of banList) {
|
|
||||||
await guild.bans.create(id, {
|
|
||||||
reason: `Massban by ${interaction.user.tag} for: ${reason}`,
|
|
||||||
deleteMessageSeconds: 86400
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await getConfig(bot, guild.id);
|
|
||||||
if (!config.modLogChannel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const channel =
|
|
||||||
guild.channels.cache.get(config.modLogChannel) ||
|
|
||||||
(await guild.channels.fetch(config.modLogChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
embed.setTitle("Mass Ban:");
|
|
||||||
embed.setAuthor({
|
|
||||||
name: interaction.user.tag,
|
|
||||||
iconURL: interaction.user.displayAvatarURL()
|
|
||||||
});
|
|
||||||
await channel.send({ embeds: [embed] });
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Mass ban complete.",
|
|
||||||
embeds: [],
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "cancel") {
|
|
||||||
interaction.editReply({
|
|
||||||
content: "Mass ban cancelled.",
|
|
||||||
embeds: [],
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "handle mass ban modal", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,82 +0,0 @@
|
|||||||
import { ModalSubmitInteraction } from "discord.js";
|
|
||||||
|
|
||||||
import { ExtendedClient } from "../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../utils/errorHandler";
|
|
||||||
import { getConfig } from "../data/getConfig";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the submission of the message report form.
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
* @param {ModalSubmitInteraction} interaction The interaction payload from Discord.
|
|
||||||
*/
|
|
||||||
export const handleMessageReportModal = async (
|
|
||||||
bot: ExtendedClient,
|
|
||||||
interaction: ModalSubmitInteraction
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
|
||||||
if (!interaction.guild) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "This command can only be used in a guild."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const reportLogId = interaction.customId.split("-")[1] ?? "oops";
|
|
||||||
const config = await getConfig(bot, interaction.guild.id);
|
|
||||||
|
|
||||||
if (!config.messageReportChannel) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Reporting has not been set up for this server."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel =
|
|
||||||
interaction.guild.channels.cache.get(config.messageReportChannel) ||
|
|
||||||
(await interaction.guild.channels.fetch(config.messageReportChannel));
|
|
||||||
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Reporting channel not found."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportLog = await channel.messages
|
|
||||||
.fetch(reportLogId)
|
|
||||||
.catch(() => null);
|
|
||||||
if (!reportLog) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Could not find the report log."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = reportLog.embeds[0];
|
|
||||||
await reportLog.edit({
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: embed?.title || "wtf",
|
|
||||||
description: embed?.description || "wtf",
|
|
||||||
fields: [
|
|
||||||
...(embed?.fields ?? []),
|
|
||||||
{
|
|
||||||
name: "Reason",
|
|
||||||
value: interaction.fields.getTextInputValue("reason")
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Your report has been submitted."
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "handle message report modal", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,53 +0,0 @@
|
|||||||
import { ExtendedClient } from "../interfaces/ExtendedClient";
|
|
||||||
import { checkEntitledGuild } from "../utils/checkEntitledGuild";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the configs from the database, then for each config that
|
|
||||||
* has a birthday channel set, fetch birthdays. If any are from today,
|
|
||||||
* post!
|
|
||||||
*
|
|
||||||
* @param {ExtendedClient} bot The bot's Discord instance.
|
|
||||||
*/
|
|
||||||
export const postBirthdays = async (bot: ExtendedClient) => {
|
|
||||||
try {
|
|
||||||
const configs = await bot.db.configs.findMany();
|
|
||||||
const withChannel = configs.filter((c) => c.birthdayChannel);
|
|
||||||
for (const record of withChannel) {
|
|
||||||
const guild =
|
|
||||||
bot.guilds.cache.get(record.serverId) ||
|
|
||||||
(await bot.guilds.fetch(record.serverId).catch(() => null));
|
|
||||||
if (!guild) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const isEntitled = await checkEntitledGuild(bot, guild);
|
|
||||||
if (!isEntitled) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const channel =
|
|
||||||
guild.channels.cache.get(record.birthdayChannel) ||
|
|
||||||
(await bot.guilds.fetch(record.birthdayChannel).catch(() => null));
|
|
||||||
if (!channel || !("send" in channel)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasBirthdaySet = await bot.db.birthdays.findMany({
|
|
||||||
where: { serverId: guild.id }
|
|
||||||
});
|
|
||||||
const today = new Date();
|
|
||||||
const todayIn2000 = new Date(
|
|
||||||
`2000-${today.getMonth() + 1}-${today.getDate()}`
|
|
||||||
);
|
|
||||||
const isBirthdayToday = hasBirthdaySet.filter(
|
|
||||||
(r) => r.birthday === todayIn2000
|
|
||||||
);
|
|
||||||
if (!isBirthdayToday.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const names = isBirthdayToday.map((r) => `<@${r.userId}>`).join(", ");
|
|
||||||
await channel.send(`Happy birthday to these lovely people~!\n${names}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler(bot, "post birthdays", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,71 +0,0 @@
|
|||||||
import client, { Counter, Gauge } from "prom-client";
|
|
||||||
import type { ExtendedClient } from "../interfaces/ExtendedClient";
|
|
||||||
import { checkEntitledGuild } from "../utils/checkEntitledGuild";
|
|
||||||
|
|
||||||
export class Prometheus {
|
|
||||||
private client = client;
|
|
||||||
private guilds: Gauge;
|
|
||||||
private entitled: Gauge;
|
|
||||||
private commands: Counter;
|
|
||||||
private users: Gauge;
|
|
||||||
private errors: Counter;
|
|
||||||
|
|
||||||
constructor(bot: ExtendedClient) {
|
|
||||||
this.guilds = new Gauge({
|
|
||||||
name: "guilds",
|
|
||||||
help: "The number of guilds the bot is in."
|
|
||||||
});
|
|
||||||
this.entitled = new Gauge({
|
|
||||||
name: "entitled",
|
|
||||||
help: "The number of guilds the bot is in."
|
|
||||||
});
|
|
||||||
this.updateGuilds(bot);
|
|
||||||
this.commands = new Counter({
|
|
||||||
name: "commands",
|
|
||||||
help: "The number of commands that have been used since last boot."
|
|
||||||
});
|
|
||||||
this.users = new Gauge({
|
|
||||||
name: "users",
|
|
||||||
help: "The number of users the bot knows."
|
|
||||||
});
|
|
||||||
this.users.set(
|
|
||||||
bot.guilds.cache.reduce(
|
|
||||||
(members, guild) => members + guild.memberCount,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.errors = new Counter({
|
|
||||||
name: "errors",
|
|
||||||
help: "The number of errors handled by the process."
|
|
||||||
});
|
|
||||||
this.client.collectDefaultMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
public commandUsed() {
|
|
||||||
this.commands.inc();
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateGuilds(bot: ExtendedClient) {
|
|
||||||
this.guilds.set(bot.guilds.cache.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateUsers(bot: ExtendedClient) {
|
|
||||||
this.users.set(
|
|
||||||
bot.guilds.cache.reduce(
|
|
||||||
(members, guild) => members + guild.memberCount,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateEntitlements(bot: ExtendedClient) {
|
|
||||||
const entitled = bot.guilds.cache.filter(
|
|
||||||
async (g) => await checkEntitledGuild(bot, g)
|
|
||||||
);
|
|
||||||
this.entitled.set(entitled.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public errorHandled() {
|
|
||||||
this.errors.inc();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import { CommandHandler } from "../../../interfaces/CommandHandler";
|
|
||||||
import { errorHandler } from "../../../utils/errorHandler";
|
|
||||||
import { setConfig } from "../../data/setConfig";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the ban appeal link for the server.
|
|
||||||
*/
|
|
||||||
export const handleAppealLink: CommandHandler = async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
const link = interaction.options.getString("link", true);
|
|
||||||
|
|
||||||
const success = await setConfig(
|
|
||||||
bot,
|
|
||||||
interaction.guild.id,
|
|
||||||
"banAppealLink",
|
|
||||||
link
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Members who are banned can appeal at <${link}>.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Failed to set the settings."
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "automod logging subcommand", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,52 +0,0 @@
|
|||||||
import { PermissionFlagsBits } from "discord.js";
|
|
||||||
|
|
||||||
import { CommandHandler } from "../../../interfaces/CommandHandler";
|
|
||||||
import { errorHandler } from "../../../utils/errorHandler";
|
|
||||||
import { setConfig } from "../../data/setConfig";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the birthday channel for the server.
|
|
||||||
*/
|
|
||||||
export const handleBirthdayChannel: CommandHandler = async (
|
|
||||||
bot,
|
|
||||||
interaction
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const channel = interaction.options.getChannel("channel", true);
|
|
||||||
if (!("send" in channel)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "You must specify a text channel!"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const me = await interaction.guild.members.fetchMe();
|
|
||||||
if (!me.permissionsIn(channel).has(PermissionFlagsBits.SendMessages)) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "I can't send messages there. :c"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const success = await setConfig(
|
|
||||||
bot,
|
|
||||||
interaction.guild.id,
|
|
||||||
"birthdayChannel",
|
|
||||||
channel.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Birthdays will be posted in ${channel.toString()}. Members can set their birthdays with the \`/birthday\` command.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Failed to set the settings."
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "automod logging subcommand", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,34 +0,0 @@
|
|||||||
import { CommandHandler } from "../../../interfaces/CommandHandler";
|
|
||||||
import { errorHandler } from "../../../utils/errorHandler";
|
|
||||||
import { setConfig } from "../../data/setConfig";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the invite link for the server.
|
|
||||||
*/
|
|
||||||
export const handleInviteLink: CommandHandler = async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
const link = interaction.options.getString("link", true);
|
|
||||||
|
|
||||||
const success = await setConfig(
|
|
||||||
bot,
|
|
||||||
interaction.guild.id,
|
|
||||||
"inviteLink",
|
|
||||||
link
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Members who are kicked will be invited back with <${link}>.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Failed to set the settings."
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "automod logging subcommand", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,34 +0,0 @@
|
|||||||
import { CommandHandler } from "../../../interfaces/CommandHandler";
|
|
||||||
import { errorHandler } from "../../../utils/errorHandler";
|
|
||||||
import { setConfig } from "../../data/setConfig";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the role to be assigned when a member joins.
|
|
||||||
*/
|
|
||||||
export const handleJoinRole: CommandHandler = async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
const role = interaction.options.getRole("role", true);
|
|
||||||
|
|
||||||
const success = await setConfig(
|
|
||||||
bot,
|
|
||||||
interaction.guild.id,
|
|
||||||
"joinRole",
|
|
||||||
role.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Members will be given ${role.toString()} when they join..`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Failed to set the settings."
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "config join-role subcommand", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,141 +0,0 @@
|
|||||||
import {
|
|
||||||
ActionRowBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
ButtonStyle,
|
|
||||||
ComponentType,
|
|
||||||
EmbedBuilder
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { CommandHandler } from "../../../interfaces/CommandHandler";
|
|
||||||
import { errorHandler } from "../../../utils/errorHandler";
|
|
||||||
import { getNextIndex, getPreviousIndex } from "../../../utils/getArrayIndex";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the automod settings for the given guild.
|
|
||||||
*/
|
|
||||||
export const handleList: CommandHandler = async (bot, interaction, config) => {
|
|
||||||
try {
|
|
||||||
const embed = new EmbedBuilder();
|
|
||||||
|
|
||||||
embed.setTitle("Automod Settings");
|
|
||||||
embed.addFields([
|
|
||||||
{
|
|
||||||
name: "Moderation Log Channel",
|
|
||||||
value: config.modLogChannel ? `<#${config.modLogChannel}>` : "Not set.",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Event Log Channel",
|
|
||||||
value: config.eventLogChannel
|
|
||||||
? `<#${config.eventLogChannel}>`
|
|
||||||
: "Not set.",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Message Report Channel",
|
|
||||||
value: config.messageReportChannel
|
|
||||||
? `<#${config.messageReportChannel}>`
|
|
||||||
: "Not set.",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invite Link",
|
|
||||||
value: config.inviteLink || "None",
|
|
||||||
inline: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Ban Appeal Link",
|
|
||||||
value: config.banAppealLink || "None",
|
|
||||||
inline: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const roles = await bot.db.levelRoles.findMany({
|
|
||||||
where: { serverId: interaction.guild.id }
|
|
||||||
});
|
|
||||||
|
|
||||||
const levelRoles = new EmbedBuilder();
|
|
||||||
levelRoles.setTitle("Level Roles");
|
|
||||||
levelRoles.setDescription(
|
|
||||||
roles
|
|
||||||
.map((r) => `- <@&${r.roleId}> is assigned at level ${r.level}`)
|
|
||||||
.join("\n") || "No roles are currently set."
|
|
||||||
);
|
|
||||||
|
|
||||||
const assignRoles = await bot.db.roles.findMany({
|
|
||||||
where: {
|
|
||||||
serverId: interaction.guild.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const assignRolesEmbed = new EmbedBuilder();
|
|
||||||
assignRolesEmbed.setTitle("Self-Assignable Roles");
|
|
||||||
assignRolesEmbed.setDescription(
|
|
||||||
assignRoles.map((r) => `<@&${r.roleId}>`).join(" ") ||
|
|
||||||
"No roles are currently set."
|
|
||||||
);
|
|
||||||
const embeds = [embed, levelRoles, assignRolesEmbed];
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
const nextButton = new ButtonBuilder()
|
|
||||||
.setCustomId("next")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setLabel(
|
|
||||||
embeds[getNextIndex(embeds, index)]?.data.title || "Unknown embed."
|
|
||||||
)
|
|
||||||
.setEmoji("▶️");
|
|
||||||
const prevButton = new ButtonBuilder()
|
|
||||||
.setCustomId("prev")
|
|
||||||
.setStyle(ButtonStyle.Primary)
|
|
||||||
.setLabel(
|
|
||||||
embeds[getPreviousIndex(embeds, index)]?.data.title || "Unknown embed."
|
|
||||||
)
|
|
||||||
.setEmoji("◀️");
|
|
||||||
const initialRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
||||||
prevButton,
|
|
||||||
nextButton
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await interaction.editReply({
|
|
||||||
embeds: [embeds[index] as EmbedBuilder],
|
|
||||||
components: [initialRow]
|
|
||||||
});
|
|
||||||
|
|
||||||
const collector =
|
|
||||||
response.createMessageComponentCollector<ComponentType.Button>({
|
|
||||||
time: 1000 * 60 * 5
|
|
||||||
});
|
|
||||||
|
|
||||||
collector.on("collect", async (i) => {
|
|
||||||
await i.deferUpdate();
|
|
||||||
index =
|
|
||||||
i.customId === "next"
|
|
||||||
? getNextIndex(embeds, index)
|
|
||||||
: getPreviousIndex(embeds, index);
|
|
||||||
prevButton.setLabel(
|
|
||||||
embeds[getPreviousIndex(embeds, index)]?.data.title || "Unknown embed."
|
|
||||||
);
|
|
||||||
nextButton.setLabel(
|
|
||||||
embeds[getNextIndex(embeds, index)]?.data.title || "Unknown embed."
|
|
||||||
);
|
|
||||||
const newRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
||||||
prevButton,
|
|
||||||
nextButton
|
|
||||||
);
|
|
||||||
await i.editReply({
|
|
||||||
embeds: [embeds[index] as EmbedBuilder],
|
|
||||||
components: [newRow]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
collector.on("end", async () => {
|
|
||||||
await interaction.editReply({
|
|
||||||
components: []
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "automod list subcommand", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,40 +0,0 @@
|
|||||||
import { logChannelChoicesMap } from "../../../config/LogChannelChoices";
|
|
||||||
import { CommandHandler } from "../../../interfaces/CommandHandler";
|
|
||||||
import { ExtendedClient } from "../../../interfaces/ExtendedClient";
|
|
||||||
import { errorHandler } from "../../../utils/errorHandler";
|
|
||||||
import { setConfig } from "../../data/setConfig";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the logging channel for the server.
|
|
||||||
*/
|
|
||||||
export const handleLogging: CommandHandler = async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
const logType = interaction.options.getString(
|
|
||||||
"log-type",
|
|
||||||
true
|
|
||||||
) as keyof ExtendedClient["configs"][""];
|
|
||||||
const channel = interaction.options.getChannel("channel", true);
|
|
||||||
|
|
||||||
const success = await setConfig(
|
|
||||||
bot,
|
|
||||||
interaction.guild.id,
|
|
||||||
logType,
|
|
||||||
channel.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Your server will log ${logChannelChoicesMap[logType]} in <#${channel.id}>.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await interaction.editReply({
|
|
||||||
content: "Failed to set the settings."
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "automod logging subcommand", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,47 +0,0 @@
|
|||||||
import { CommandHandler } from "../../../interfaces/CommandHandler";
|
|
||||||
import { errorHandler } from "../../../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles a role to be self-assignable or not.
|
|
||||||
*/
|
|
||||||
export const handleRole: CommandHandler = async (bot, interaction) => {
|
|
||||||
try {
|
|
||||||
const role = interaction.options.getRole("role", true);
|
|
||||||
const exists = await bot.db.roles.findUnique({
|
|
||||||
where: {
|
|
||||||
serverId_roleId: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
roleId: role.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (exists) {
|
|
||||||
await bot.db.roles.delete({
|
|
||||||
where: {
|
|
||||||
serverId_roleId: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
roleId: role.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Your <@&${role.id}> role is no longer self-assignable.`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await bot.db.roles.create({
|
|
||||||
data: {
|
|
||||||
serverId: interaction.guild.id,
|
|
||||||
roleId: role.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Your <@&${role.id}> role is now self-assignable.`
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
const id = await errorHandler(bot, "automod logging subcommand", err);
|
|
||||||
await interaction.editReply({
|
|
||||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user