Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GraphicsProgramming/wheatley
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: TCCPP/wheatley
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Able to merge. These branches can be automatically merged.
Loading
Showing with 5,620 additions and 3,316 deletions.
  1. +2 −1 .gitignore
  2. +0 −8 .gitmodules
  3. +18 −4 Makefile
  4. +35 −59 README.md
  5. +17 −0 config.default.json
  6. +38 −0 dev-container/Dockerfile
  7. +40 −0 dev-container/entry.sh
  8. +34 −0 docker/bot/.dockerignore
  9. +20 −0 docker/bot/Dockerfile
  10. +33 −0 docker/compose.yaml
  11. +7 −0 docker/db/mongo-init.js
  12. +5 −0 docker/db/mongod.conf
  13. +3 −3 indexes/cppref/create_index.ts
  14. +2 −2 indexes/cppref/worker.ts
  15. +2 −2 indexes/man7/create_index.ts
  16. +2 −2 indexes/man7/worker.ts
  17. +3 −3 indexes/tsconfig.json
  18. +1,749 −1,273 package-lock.json
  19. +30 −26 package.json
  20. +2 −2 scripts/dbmigrate.ts
  21. +1 −1 scripts/scp.sh
  22. +6 −20 src/bot-component.ts
  23. +227 −0 src/bot-utilities.ts
  24. +1 −1 src/command-abstractions/button.ts
  25. +78 −0 src/command-abstractions/command-set-builder.ts
  26. +8 −2 src/command-abstractions/context-menu.ts
  27. +15 −26 src/command-abstractions/text-based-command-builder.ts
  28. +3 −0 src/command-abstractions/text-based-command-descriptor.ts
  29. +167 −80 src/command-abstractions/text-based-command.ts
  30. +418 −0 src/command-handler.ts
  31. +3 −1 src/common.ts
  32. +3 −5 src/components/anti-everyone.ts
  33. +3 −2 src/components/anti-executable.ts
  34. +18 −6 src/components/anti-invite-links.ts
  35. +24 −26 src/components/code.ts
  36. +0 −80 src/components/days-since-last-incident.ts
  37. +8 −10 src/components/format.ts
  38. +5 −6 src/components/google.ts
  39. +0 −29 src/components/headbang.ts
  40. +10 −9 src/components/help.ts
  41. +7 −8 src/components/inspect.ts
  42. +25 −5 src/components/join-leave-log.ts
  43. +1 −4 src/components/massban.ts
  44. +7 −7 src/components/members.ts
  45. +9 −10 src/components/moderation/ban.ts
  46. +5 −6 src/components/moderation/kick.ts
  47. +110 −43 src/components/moderation/moderation-common.ts
  48. +54 −27 src/components/moderation/moderation-control.ts
  49. +41 −34 src/components/moderation/modlogs.ts
  50. +7 −8 src/components/moderation/mute.ts
  51. +5 −6 src/components/moderation/note.ts
  52. +9 −10 src/components/moderation/rolepersist.ts
  53. +21 −13 src/components/moderation/timeout.ts
  54. +5 −6 src/components/moderation/warn.ts
  55. +1 −4 src/components/modmail.ts
  56. +6 −5 src/components/modstats.ts
  57. +7 −9 src/components/nodistractions.ts
  58. +0 −4 src/components/notify-about-brand-new-users.ts
  59. +0 −4 src/components/notify-about-formerly-banned-users.ts
  60. +44 −11 src/components/permissions-manager.ts
  61. +130 −0 src/components/pin-archive.ts
  62. +9 −9 src/components/ping.ts
  63. +14 −20 src/components/purge.ts
  64. +24 −29 src/components/quote.ts
  65. +26 −11 src/components/redirect.ts
  66. +71 −58 src/components/report.ts
  67. +5 −7 src/components/restart.ts
  68. +238 −31 src/components/role-manager.ts
  69. +29 −10 src/components/shortcuts.ts
  70. +0 −192 src/components/skill-role-suggestion.ts
  71. +5 −6 src/components/snowflake.ts
  72. +0 −1 src/components/speedrun.ts
  73. +57 −25 src/components/starboard.ts
  74. +9 −13 src/components/steal.ts
  75. +9 −11 src/components/thread-control.ts
  76. +0 −4 src/components/thread-created-message.ts
  77. +39 −0 src/components/topic.ts
  78. +0 −4 src/components/tracked-mentions.ts
  79. +0 −4 src/components/username-manager.ts
  80. +54 −5 src/components/utility-tools.ts
  81. +16 −32 src/components/wiki.ts
  82. +12 −9 src/infra/database-interface.ts
  83. +0 −35 src/infra/guild-command-manager.ts
  84. +56 −0 src/infra/log-limiter.ts
  85. +2 −0 src/infra/schemata/logged-messages.ts
  86. +1 −0 src/infra/schemata/moderation.ts
  87. +13 −0 src/infra/schemata/pins.ts
  88. +11 −0 src/infra/schemata/skill-role-suggestion.ts
  89. +5 −0 src/infra/schemata/user_roles.ts
  90. +5 −5 src/main.ts
  91. +3 −3 src/{ → modules/tccpp}/components/anti-autoreact.ts
  92. +4 −4 src/{ → modules/tccpp}/components/anti-forum-post-delete.ts
  93. +5 −9 src/{ → modules/tccpp}/components/anti-screenshot.ts
  94. +3 −7 src/{ → modules/tccpp}/components/anti-self-star.ts
  95. +8 −5 src/{ → modules/tccpp}/components/auto-reply.ts
  96. +64 −12 src/{ → modules/tccpp}/components/autoreact.ts
  97. +8 −8 src/{ → modules/tccpp}/components/buzzwords.ts
  98. +13 −14 src/{ → modules/tccpp}/components/c-help-redirect.ts
  99. +12 −13 src/{ → modules/tccpp}/components/core-guidelines.ts
  100. +14 −15 src/{ → modules/tccpp}/components/cppref.ts
  101. +87 −0 src/modules/tccpp/components/days-since-last-incident.ts
  102. +189 −0 src/modules/tccpp/components/formatting-error-detection.ts
  103. +10 −33 src/{ → modules/tccpp}/components/forum-channels.ts
  104. +31 −17 src/{ → modules/tccpp}/components/forum-control.ts
  105. +42 −0 src/modules/tccpp/components/insult.ts
  106. +12 −13 src/{ → modules/tccpp}/components/man7.ts
  107. +17 −16 src/{ → modules/tccpp}/components/roulette.ts
  108. +8 −12 src/{ → modules/tccpp}/components/server-suggestion-reactions.ts
  109. +34 −19 src/{ → modules/tccpp}/components/server-suggestion-tracker.ts
  110. +355 −0 src/modules/tccpp/components/skill-role-suggestion.ts
  111. +2 −6 src/{ → modules/tccpp}/components/status.ts
  112. +10 −13 src/{ → modules/tccpp}/components/the-button.ts
  113. +7 −11 src/{ → modules/tccpp}/components/thread-based-channels.ts
  114. +18 −6 src/utils/arrays.ts
  115. +8 −9 src/utils/debugging-and-logging.ts
  116. +1 −0 src/utils/discord.ts
  117. +0 −12 src/utils/filesystem.ts
  118. +31 −6 src/utils/strings.ts
  119. +152 −0 src/utils/synopsis.ts
  120. +0 −1 src/wheatley-private
  121. +103 −568 src/wheatley.ts
  122. +1 −1 test/cppref.ts
  123. +27 −25 test/invite-links.ts
  124. +1 −1 test/man7.ts
  125. +70 −0 test/synopsis.ts
  126. +20 −1 test/utils.ts
  127. +5 −21 test/wiki-articles.ts
  128. +1 −1 tsconfig.json
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@
.idea
node_modules
build
auth.*
config.json
*.js
./W*/
_bot.json
bot.json
backup
scratch
8 changes: 0 additions & 8 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
[submodule "wheatley-private"]
path = src/wheatley-private
url = https://github.com/TCCPP/empty.git
# url = git@github.com:TCCPP/wheatley-private.git

# [submodule "dyno-import"]
# path = dyno-import
# url = git@github.com:TCCPP/dyno-import.git
[submodule "wiki"]
path = wiki
url = git@github.com:TCCPP/wiki-articles.git
22 changes: 18 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ format: prereqs ## Formats source files
$(NPM) run format

.PHONY: test
test: prereqs ## Formats source files
test: prereqs ## Runs all tests
$(NPM) run test

.PHONY: clean
@@ -53,10 +53,24 @@ deploy: ts-check ## Deploys code
prod: format deploy ## Deploys code and restarts the bot
ssh $(SERVER) "export NVM_DIR=\"$$HOME/.nvm\"; source $$NVM_DIR/nvm.sh; screen -XS _Wheatley quit; cd projects/wheatley; ./start.sh"

.PHONY: npm-update
npm-update: ## Updates npm packages
.PHONY: update
update: ## Updates npm packages
./scripts/update_packages.sh

.PHONY: mongo
mongo:
mongo: ## Port forward mongo
ssh -L 27017:127.0.0.1:27017 x0 -N

PODMAN=podman

.PHONY: build-dev-container
build-dev-container: ## Build dev container container
$(PODMAN) build -t wheatley-dev -f dev-container/Dockerfile .

.PHONY: run-dev-container
run-dev-container: build-dev-container ## Runs the dev container container
$(PODMAN) run --user=wheatley --cap-drop=all -it wheatley-dev

.PHONY: dev
dev: build ## Runs in dev mode
node build/src/main.js
94 changes: 35 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
@@ -22,6 +22,25 @@ The bot is very modular and most components are completely independent of other

## Local Development

In order to run the bot locally you'll need to create a bot and setup some basic information for Wheatley:

1. Go to https://discord.com/developers/applications and create an application
2. Move or copy `config.default.json` to `config.json`
3. Go to Application Settings > Bot
1. Request or reset your bot's token, copy it to `token` in `config.json`
2. Under Privileged Gateway Intents, select presence intent, server members intent, and the message content intent
4. Go to Application Settings > Installation
1. Select "Guild Install"
2. Select "Discord Provided Link"
3. Select scopes: `applications.commands` and `bot`
4. Select permissions: `Administrator`
5. Setup a test server (ask on TCCPP for help if needed)
6. Install your bot on a test server

Once that is setup, the easiest way to get started with local bot development is to run `make run-dev-container`, builds
a container and runs it with podman. Once the container builds, run `make dev` in the container's shell and you should
be good to go.

The bot relies on a lot of server-specific information, such as IDs for channels and roles. Components which do not rely
on any server-specific information are marked as freestanding. When developing locally, configure the bot as
freestanding (see below). If you are working on a component which relies on server specific information, the best
@@ -39,9 +58,9 @@ solution currently is the following:
}
```

## auth.json
## config.json

Secrets and other bot info must be configured in the `auth.json` file. An example looks like:
Secrets and other bot info must be configured in the `config.json` file. An example looks like:

```json
{
@@ -52,17 +71,21 @@ Secrets and other bot info must be configured in the `auth.json` file. An exampl
"user": "wheatley",
"password": "<mongo password>",
"host": "127.0.0.1", // optional
"port": 27017 // optional
"port": 27017 // optional
},
"freestanding": false, // optional
"passive": false // optional
"freestanding": false, // optional,
"exclude": [
// optional
"components/the-button.js",
"modules/tccpp/components/buzzwords.js",
"modules/tccpp/private/**"
]
}
```

Mongo credentials can be omitted locally if you don't need to work on components that use mongo. `freestanding: true`
can be specified to turn on only components which don't rely on channels etc. specific to Together C & C++ to exist.
Freestanding mode also disables connecting to MongoDB.
`passive: true` can be specified to run the bot in passive mode, where it will not advertise or react to commands, and only load components that are marked as passive (useful for testing new features in a second instance without interfering with a running primary instance).

## Bot Component Abstraction

@@ -73,19 +96,8 @@ export class BotComponent {
static get is_freestanding() {
return false;
}
static get is_passive() {
return false;
}
// Add a command
add_command<T extends unknown[]>(
command:
| TextBasedCommandBuilder<T, true, true>
| TextBasedCommandBuilder<T, true, false, true>
| MessageContextMenuCommandBuilder<true>
| ModalHandler<true>,
);
// Called after all components are constructed and the bot logs in, but before bot commands are finalized
async setup();
// Called after all components are constructed and the bot logs in, commands can be added here
async setup(commands: CommandSetBuilder);
// Called when Wheatley is ready
async on_ready();
// General discord events
@@ -126,10 +138,8 @@ export default class Echo extends BotComponent {
return true;
}

constructor(wheatley: Wheatley) {
super(wheatley);

this.add_command(
override async setup(commands: CommandSetBuilder) {
commands.add(
new TextBasedCommandBuilder("echo")
.set_description("echo")
.add_string_option({
@@ -171,39 +181,5 @@ is read or a regex can be specified.

## Database

The bot uses MongoDB. It previously used a giant json file (the migration script is located in the scripts folder). For
local development you likely won't need to setup a mongo database, depending on the components you're working on.
However, if you are contributing to components that do need the database here are the installation steps for ubuntu:

https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/

```sh
sudo apt-get install gnupg curl
curl -fsSL https://pgp.mongodb.com/server-6.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-6.0.gpg \
--dearmor
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo systemctl start mongod
sudo systemctl status mongod
sudo systemctl enable mongod
sudo ufw deny 27017
mongosh
# use admin
# db.createUser({user:'mongoadmin', pwd: '<password>', roles:['userAdminAnyDatabase']})
# db.auth('mongoadmin', '<password>') # test that authentication works
# use wheatley
# db.createUser({user:'wheatley', pwd: '<password>', roles:[{db:'wheatley', role:'readWrite'}]})
# use wheatley
sudo vim /etc/mongod.conf
# net:
# port: 27017
# bindIp: 127.0.0.1
# security:
# authorization: enabled
sudo systemctl restart mongod
```

To connect with [compass](https://www.mongodb.com/try/download/compass) to a mongo server setup on another server:
`ssh -L 27017:127.0.0.1:27017 <server> -N`.
The bot uses MongoDB. It previously used a giant json file (the migration script is located in the scripts folder). The
development docker container sets up and orchestrates a mongodb server for the bot to use.
17 changes: 17 additions & 0 deletions config.default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "<bot id>",
"guild": "<guild id>",
"token": "<token>",
"mongo": {
"user": "wheatley",
"password": "wheatley",
"host": "127.0.0.1",
"port": 27017
},
"freestanding": true,
"exclude": [
"modules/tccpp/components/anti-self-star.js",
"modules/tccpp/components/buzzwords.js",
"modules/tccpp/private/**"
]
}
38 changes: 38 additions & 0 deletions dev-container/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND noninteractive

# Basic software setup
RUN apt update
RUN apt install -y curl wget vim git make build-essential net-tools gnupg expect

# node
RUN curl --proto "=https" -fsSL https://deb.nodesource.com/setup_21.x | bash -
RUN apt install -y nodejs

# Mongodb
RUN curl --proto "=https" -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \
gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg \
--dearmor
RUN echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/8.0 multiverse" | \
tee /etc/apt/sources.list.d/mongodb-org-8.0.list
RUN apt update
RUN apt install -y mongodb-org

# Cleanup apt stuff
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

# Setup a user
RUN groupadd wheatley
RUN useradd -m -g wheatley -s /bin/bash wheatley
RUN mkdir /opt/wheatley/ && chown wheatley:wheatley /opt/wheatley/
RUN mkdir /opt/mongo/ && mkdir /opt/mongo/data && chown -R wheatley:wheatley /opt/mongo/
USER wheatley

COPY --chmod=755 dev-container/entry.sh /entry.sh

WORKDIR /opt/wheatley
COPY --chown=wheatley:wheatley . .

ENTRYPOINT ["/entry.sh"]
40 changes: 40 additions & 0 deletions dev-container/entry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/sh

/usr/bin/mongod -f /etc/mongod.conf --logpath /opt/mongo/mongod.log --dbpath /opt/mongo/data &

PORT=27017
TIMEOUT=10
INTERVAL=1
SECONDS_ELAPSED=0

echo "Waiting for mongo to start..."

while [ $SECONDS_ELAPSED -lt $TIMEOUT ]; do
if netstat -ntlp | grep -q ":$PORT"; then
echo "Mongo now started"
break
fi
sleep $INTERVAL
SECONDS_ELAPSED=$((SECONDS_ELAPSED + INTERVAL))
done

if [ $SECONDS_ELAPSED -eq $TIMEOUT ]; then
echo "Timeout reached while waiting for mongodb, $PORT was never listed in netstat"
exit 1
fi

mongosh --eval << END
use admin
if (db.getUser("mongoadmin") == null) {
db.createUser({user:"mongoadmin", pwd: "password", roles:["userAdminAnyDatabase"]})
}
db.auth("mongoadmin", "password")
use wheatley
if (db.getUser("wheatley") == null) {
db.createUser({user:"wheatley", pwd: "wheatley", roles:[{db:"wheatley", role:"readWrite"}]})
}
END

npm i

exec /bin/bash
34 changes: 34 additions & 0 deletions docker/bot/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
**/.dockerignore
**/.env
**/.git
**/.github
**/.gitignore
**/.gitmodules
**/.husky
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/node_modules
**/npm-debug.log
**/build
.eslintrc.yml
.git-blame-ignore-revs
.prettierignore
.prettierrc.cjs
docker
indexes
LICENSE.txt
lint-staged.config.mjs
Makefile
package-lock.json
package.json
README.md
run-persist.sh
scripts
SECURITY.md
start.sh
test
tsconfig.json
vitest.config.ts
20 changes: 20 additions & 0 deletions docker/bot/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# syntax=docker/dockerfile:1

FROM node:current-alpine

RUN --mount=type=bind,source=./package.json,target=package.json \
--mount=type=bind,source=./package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci

RUN adduser -s /bin/bash -D wheatley;echo 'wheatley:wheatley' | chpasswd

USER wheatley

WORKDIR /home/wheatley/bot

COPY . .

RUN npm run build

CMD ["node", "build/src/main.js"]
33 changes: 33 additions & 0 deletions docker/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: wheatley

services:
bot:
container_name: wheatley_bot
image: TCCPP/wheatley
build:
context: ../
dockerfile: docker/bot/Dockerfile
# pull_policy: build
depends_on:
- db
# restart: always

db:
container_name: wheatley_db
image: mongo:latest
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password
- MONGO_INITDB_DATABASE=wheatley
volumes:
- ./db/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
- ./db/mongod.conf:/etc/mongod.conf:ro
- wheatley_data:/data/db
# expose:
# - 27017
ports:
- 127.0.0.1:27017:27017
# restart: always

volumes:
wheatley_data:
7 changes: 7 additions & 0 deletions docker/db/mongo-init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@


// https://stackoverflow.com/a/68253550
db = db.getSiblingDB('admin');
db.auth("admin", "password");
db = db.getSiblingDB('wheatley');
db.createUser({user:'wheatley', pwd: 'wheatley', roles:[{db:'wheatley', role:'readWrite'}]});
Loading