mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-03 01:10:07 +00:00
Add v19 bigbot rest
This commit is contained in:
152
examples/bigbot-19/.env.example
Normal file
152
examples/bigbot-19/.env.example
Normal file
@@ -0,0 +1,152 @@
|
||||
#
|
||||
# General Configurations
|
||||
#
|
||||
|
||||
# For Prisma use, it should be a postgres connection url
|
||||
# Template: postgres://[username]:[password]@[host]:[port]/[db]
|
||||
# Replate the [...] from the template with your values
|
||||
# TEMPLATE-SETUP: Add a postgres connection string
|
||||
DATABASE_URL=
|
||||
|
||||
# Whether or not this process is a local development version
|
||||
# In production this value should be set to false
|
||||
# TEMPLATE-SETUP: When deploying, set this value to false
|
||||
DEVELOPMENT=true
|
||||
|
||||
# The server id where you develop/test the bot
|
||||
# TEMPLATE-SETUP: Add the id to a server where you develop the bot
|
||||
DEV_SERVER_ID=
|
||||
|
||||
# The discord bot token
|
||||
# NOTE: It should not be prefixed with Bot
|
||||
# TEMPLATE-SETUP: Add the bot token here.
|
||||
DISCORD_TOKEN=
|
||||
|
||||
#
|
||||
# Bot Configuration
|
||||
#
|
||||
|
||||
# NOTE: With "bot code" we refer to the code that will handle the events
|
||||
|
||||
# The secret passcode that the bot code is checking for
|
||||
# This is used to prevent someone else from trying to send malicious events to your bot
|
||||
# TEMPLATE-SETUP: Add a secret passcode here. It can be whatever you want
|
||||
EVENT_HANDLER_AUTHORIZATION=
|
||||
|
||||
# The host where the event handler will run
|
||||
# Will be used together with EVENT_HANDLER_PORT to compose the HTTP url to send the events to
|
||||
# TEMPLATE-SETUP: Set the event handler's host here
|
||||
EVENT_HANDLER_HOST=localhost
|
||||
|
||||
# The port where the event handler will listening for events
|
||||
# TEMPLATE-SETUP: Set the port where events will be sent
|
||||
EVENT_HANDLER_PORT=8081
|
||||
|
||||
# The full discord webhook url where the bot can send errors to alert you that the bot is missing translations
|
||||
# TEMPLATE-SETUP: Add the full discord webhook url
|
||||
MISSING_TRANSLATION_WEBHOOK=
|
||||
|
||||
# The full webhook url where the bot can send errors to alert you that the bot is throwing errors.
|
||||
# TEMPLATE-SETUP: Add the full discord webhook url
|
||||
BUGS_ERRORS_REPORT_WEBHOOK=
|
||||
|
||||
#
|
||||
# Rest Proxy Configurations
|
||||
#
|
||||
|
||||
# The passcode that the REST proxy is checking for
|
||||
# This is used to prevent someone else from trying to send malicious API requests from your bot
|
||||
# TEMPLATE-SETUP: Add a secret passcode here. It can be whatever you want
|
||||
REST_AUTHORIZATION=
|
||||
|
||||
# The host where the REST proxy will run
|
||||
# Will be used together with REST_PORT to compose the HTTP url to send the API requests to
|
||||
# TEMPLATE-SETUP: Set the REST proxy's host here
|
||||
REST_HOST=localhost
|
||||
|
||||
# The port where the REST proxy will listen for API requests
|
||||
# TEMPLATE-SETUP: Set the port where API requests will be sent
|
||||
REST_PORT=8000
|
||||
|
||||
#
|
||||
# Gateway Proxy Configurations
|
||||
#
|
||||
|
||||
# The amount of shards to start
|
||||
# Useful with multiple servers where each server is handling a portion of your bot
|
||||
# OPTIONAL: You can leave this value unspecified if you want this server to manage all shards
|
||||
# TEMPLATE-SETUP: If you have separate servers, add the number of shards this process should handle
|
||||
TOTAL_SHARDS=
|
||||
|
||||
# The amount of shards to start per worker.
|
||||
# NOTE: If you are not sure just stick to 16
|
||||
# TEMPLATE-SETUP: Set how many shards to start per worker
|
||||
SHARDS_PER_WORKER=16
|
||||
|
||||
# The total amount of workers to start.
|
||||
# NOTE: Generally this should be equal to the number of cores your server has
|
||||
# TEMPLATE-SETUP: Choose how many workers to start up
|
||||
TOTAL_WORKERS=4
|
||||
|
||||
# The passcode that the gateway is checking for
|
||||
# This is used to prevent someone else from trying to send malicious messages to your bot
|
||||
# TEMPLATE-SETUP: Set a secret passcode here. It can be whatever you want
|
||||
GATEWAY_AUTHORIZATION=
|
||||
|
||||
# The host where the gateway will run
|
||||
# Will be used together with GATEWAY_PORT to compose the HTTP url to send the gateway messages to
|
||||
# TEMPLATE-SETUP: Set the gateway's host here
|
||||
GATEWAY_HOST=localhost
|
||||
|
||||
# The port where the gateway will listen for gateway messages
|
||||
# TEMPLATE-SETUP: Set the port where gateway messages will be sent
|
||||
GATEWAY_PORT=8080
|
||||
|
||||
#
|
||||
# Message queue (RabbitMQ configuration)
|
||||
#
|
||||
|
||||
# Whatever to queue messages from the gateway to bot
|
||||
# NOTE: If this is set to true, all other configuration in this section are requried
|
||||
# NOTE: if this is set to false, gateway messages will be sent directly to the bot code, and will fail if the bot code is not running
|
||||
MESSAGEQUEUE_ENABLE=false
|
||||
|
||||
# The url of the RabbitMQ instance
|
||||
MESSAGEQUEUE_URL=localhost:5672
|
||||
|
||||
# Username for the authentication against the RabbitMQ instance
|
||||
MESSAGEQUEUE_USERNAME=
|
||||
|
||||
# Password for the authentication against the RabbitMQ instance
|
||||
MESSAGEQUEUE_PASSWORD=
|
||||
|
||||
#
|
||||
# Analytics (InfluxDB configuration)
|
||||
#
|
||||
|
||||
# NOTE: This entire section is optional
|
||||
# TEMPLATE-SETUP: If you want to enable analytics, add the the following values
|
||||
|
||||
# The InfluxDB organization
|
||||
INFLUX_ORG=
|
||||
|
||||
# The InfluxDB bucket
|
||||
INFLUX_BUCKET=
|
||||
|
||||
# The InfluxDB secret API token
|
||||
# NOTE: this may need to be in quotes ("...") if it contains the = sign
|
||||
INFLUX_TOKEN=
|
||||
|
||||
# The InfluxDB Instance url
|
||||
INFLUX_URL=http://localhost:8086
|
||||
|
||||
#
|
||||
# Docker InfluxDB
|
||||
#
|
||||
|
||||
DOCKER_INFLUXDB_INIT_MODE=setup
|
||||
DOCKER_INFLUXDB_INIT_USERNAME=discordeno
|
||||
DOCKER_INFLUXDB_INIT_PASSWORD=discordeno
|
||||
DOCKER_INFLUXDB_INIT_ORG=discordeno
|
||||
DOCKER_INFLUXDB_INIT_BUCKET=discordeno
|
||||
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=discordeno
|
||||
32
examples/bigbot-19/.gitignore
vendored
Normal file
32
examples/bigbot-19/.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# build
|
||||
dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
24
examples/bigbot-19/.swcrc
Normal file
24
examples/bigbot-19/.swcrc
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"legacyDecorator": true,
|
||||
"decoratorMetadata": true
|
||||
},
|
||||
"target": "es2022",
|
||||
"keepClassNames": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "es6",
|
||||
"strict": false,
|
||||
"strictMode": true,
|
||||
"lazy": false,
|
||||
"noInterop": false
|
||||
}
|
||||
}
|
||||
33
examples/bigbot-19/package.json
Normal file
33
examples/bigbot-19/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "dd-bigbot",
|
||||
"version": "1.0.0",
|
||||
"description": "A scalable bot for big bot developers.",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"scripts": {
|
||||
"start:bot": "echo TODO: fill this up",
|
||||
"start:rest": "node dist/rest/index.js",
|
||||
"start:gatewa": "echo TODO: fill this up",
|
||||
"build": "swc src --strip-leading-paths --delete-dir-on-start --out-dir dist",
|
||||
"build:watch": "swc src --strip-leading-paths --delete-dir-on-start --watch --out-dir dist",
|
||||
"dev:bot": "echo TODO: fill this up",
|
||||
"dev:rest": "node --watch --watch-preserve-output dist/rest/index.js",
|
||||
"dev:gatewa": "echo TODO: fill this up",
|
||||
"setup-dd": ""
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordeno/bot": "19.0.0-next.ad7e74c",
|
||||
"@fastify/multipart": "^8.3.0",
|
||||
"@influxdata/influxdb-client": "^1.33.2",
|
||||
"fastify": "^4.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.3.12",
|
||||
"@swc/core": "^1.5.25",
|
||||
"@types/node": "^20.14.2",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
60
examples/bigbot-19/src/config.ts
Normal file
60
examples/bigbot-19/src/config.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Intents } from '@discordeno/bot'
|
||||
import 'dotenv/config'
|
||||
|
||||
// #region Mapping of environment variables to javascript variables
|
||||
|
||||
// General Configurations
|
||||
|
||||
export const DATABASE_URL = process.env.DATABASE_URL
|
||||
export const DEVELOPMENT = process.env.DEVELOPMENT
|
||||
export const DEV_SERVER_ID = process.env.DEV_SERVER_ID
|
||||
export const DISCORD_TOKEN = process.env.DISCORD_TOKEN
|
||||
|
||||
// Bot Configuration
|
||||
|
||||
export const EVENT_HANDLER_AUTHORIZATION = process.env.EVENT_HANDLER_AUTHORIZATION
|
||||
export const EVENT_HANDLER_HOST = process.env.EVENT_HANDLER_HOST
|
||||
export const EVENT_HANDLER_PORT = process.env.EVENT_HANDLER_PORT
|
||||
|
||||
export const MISSING_TRANSLATION_WEBHOOK = process.env.MISSING_TRANSLATION_WEBHOOK
|
||||
export const BUGS_ERRORS_REPORT_WEBHOOK = process.env.BUGS_ERRORS_REPORT_WEBHOOK
|
||||
|
||||
// Rest Proxy Configurations
|
||||
|
||||
export const REST_AUTHORIZATION = process.env.REST_AUTHORIZATION
|
||||
export const REST_HOST = process.env.REST_HOST
|
||||
export const REST_PORT = process.env.REST_PORT
|
||||
|
||||
// Gateway Proxy Configurations
|
||||
|
||||
export const TOTAL_SHARDS = process.env.TOTAL_SHARDS
|
||||
export const SHARDS_PER_WORKER = process.env.SHARDS_PER_WORKER
|
||||
export const TOTAL_WORKERS = process.env.TOTAL_WORKERS
|
||||
|
||||
export const GATEWAY_AUTHORIZATION = process.env.GATEWAY_AUTHORIZATION
|
||||
export const GATEWAY_HOST = process.env.GATEWAY_HOST
|
||||
export const GATEWAY_PORT = process.env.GATEWAY_PORT
|
||||
|
||||
// Message queue (RabbitMQ configuration)
|
||||
|
||||
export const MESSAGEQUEUE_ENABLE = process.env.MESSAGEQUEUE_ENABLE
|
||||
|
||||
export const MESSAGEQUEUE_URL = process.env.MESSAGEQUEUE_URL
|
||||
export const MESSAGEQUEUE_USERNAME = process.env.MESSAGEQUEUE_USERNAME
|
||||
export const MESSAGEQUEUE_PASSWORD = process.env.MESSAGEQUEUE_PASSWORD
|
||||
|
||||
// Analytics (InfluxDB configuration)
|
||||
|
||||
export const INFLUX_ORG = process.env.INFLUX_ORG
|
||||
export const INFLUX_BUCKET = process.env.INFLUX_BUCKET
|
||||
export const INFLUX_TOKEN = process.env.INFLUX_TOKEN
|
||||
export const INFLUX_URL = process.env.INFLUX_URL
|
||||
|
||||
// #endregion
|
||||
|
||||
export const EVENT_HANDLER_URL = `http://${EVENT_HANDLER_HOST}:${EVENT_HANDLER_PORT}`
|
||||
export const REST_URL = `http://${REST_HOST}:${REST_PORT}`
|
||||
export const GATEWAY_URL = `http://${GATEWAY_HOST}:${GATEWAY_PORT}`
|
||||
|
||||
// TEMPLATE-SETUP: Add/Remove the intents you need/don't need
|
||||
export const GATEWAY_INTENTS = Intents.Guilds | Intents.GuildMessages
|
||||
51
examples/bigbot-19/src/influx.ts
Normal file
51
examples/bigbot-19/src/influx.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import type { RestManager } from '@discordeno/bot'
|
||||
import { InfluxDB, Point } from '@influxdata/influxdb-client'
|
||||
import { INFLUX_BUCKET, INFLUX_ORG, INFLUX_TOKEN, INFLUX_URL } from './config.js'
|
||||
|
||||
const isInfluxConfigured = INFLUX_URL && INFLUX_TOKEN && INFLUX_ORG && INFLUX_BUCKET
|
||||
|
||||
// @ts-expect-error INFLUX_URL is not undefined if isInfluxConfigured is true, however this kinda of stuff confuses typescript. This will get fixed in TS 5.5
|
||||
export const influxDB = isInfluxConfigured ? new InfluxDB({ url: INFLUX_URL, token: INFLUX_TOKEN }) : undefined
|
||||
// @ts-expect-error INFLUX_ORG is not undefined if isInfluxConfigured is true, however this kinda of stuff confuses typescript. This will get fixed in TS 5.5
|
||||
export const influx = isInfluxConfigured && influxDB ? influxDB.getWriteApi(INFLUX_ORG, INFLUX_BUCKET) : undefined
|
||||
|
||||
export const setupRestAnalyticsHooks = (rest: RestManager, logger: RestManager['logger']): void => {
|
||||
// If influxdb data is provided, enable analytics in this proxy.
|
||||
if (!influx) return
|
||||
|
||||
const originalSendRequest = rest.sendRequest
|
||||
|
||||
rest.sendRequest = async (options) => {
|
||||
const fetchingPoint = new Point('restEvents')
|
||||
.timestamp(new Date())
|
||||
.stringField('type', 'REQUEST_FETCHING')
|
||||
.tag('method', options.method)
|
||||
.tag('route', options.route)
|
||||
.tag('bucket', options.bucketId ?? 'NA')
|
||||
|
||||
influx.writePoint(fetchingPoint)
|
||||
|
||||
await originalSendRequest(options)
|
||||
|
||||
const fetchedPoint = new Point('restEvents')
|
||||
.timestamp(new Date())
|
||||
.stringField('type', 'REQUEST_FETCHED')
|
||||
.tag('method', options.method)
|
||||
.tag('route', options.route)
|
||||
.tag('bucket', options.bucketId ?? 'NA')
|
||||
// FIXME: rest.sendRequest returns Promise<void>, so there is no way currently to get the response status
|
||||
// .intField('status', response.status)
|
||||
|
||||
influx.writePoint(fetchedPoint)
|
||||
}
|
||||
|
||||
setInterval(async () => {
|
||||
logger.info('Influx - Saving events...')
|
||||
try {
|
||||
await influx.flush()
|
||||
logger.info('Influx - events saved!')
|
||||
} catch (error) {
|
||||
logger.error('Influx - error saving events!', error)
|
||||
}
|
||||
}, 30_000 /* 30s */)
|
||||
}
|
||||
39
examples/bigbot-19/src/rest/fastify.ts
Normal file
39
examples/bigbot-19/src/rest/fastify.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import fastifyMultipart, { type MultipartFile, type MultipartValue } from '@fastify/multipart'
|
||||
import fastify, { type FastifyInstance } from 'fastify'
|
||||
import { REST_AUTHORIZATION } from '../config.js'
|
||||
|
||||
export function buildFastifyApp(): FastifyInstance {
|
||||
const app = fastify()
|
||||
|
||||
app.register(fastifyMultipart, { attachFieldsToBody: true })
|
||||
|
||||
// Authorization check
|
||||
app.addHook('onRequest', async (request, reply) => {
|
||||
if (request.headers.authorization !== REST_AUTHORIZATION) {
|
||||
reply.status(401).send({
|
||||
message: 'Credentials not valid.',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export async function parseMultiformBody(body: unknown): Promise<FormData> {
|
||||
const form = new FormData()
|
||||
|
||||
if (typeof body !== 'object' || !body) return form
|
||||
|
||||
for (const objectValue of Object.values(body)) {
|
||||
const value = objectValue as MultipartFile | MultipartValue
|
||||
|
||||
if (value.type === 'file') {
|
||||
form.append(value.fieldname, new Blob([await value.toBuffer()]), value.filename)
|
||||
}
|
||||
if (value.type === 'field' && typeof value.value === 'string') {
|
||||
form.append(value.fieldname, value.value)
|
||||
}
|
||||
}
|
||||
|
||||
return form
|
||||
}
|
||||
56
examples/bigbot-19/src/rest/index.ts
Normal file
56
examples/bigbot-19/src/rest/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { type RequestMethods } from '@discordeno/bot'
|
||||
import { REST_AUTHORIZATION, REST_HOST, REST_PORT } from '../config.js'
|
||||
import { buildFastifyApp, parseMultiformBody } from './fastify.js'
|
||||
import restManager, { logger } from './restManager.js'
|
||||
|
||||
if (!REST_AUTHORIZATION) throw new Error('The REST_AUTHORIZATION environment variable is missing')
|
||||
if (!REST_HOST) throw new Error('The REST_HOST environment variable is missing')
|
||||
if (!REST_PORT) throw new Error('The REST_PORT environment variable is missing')
|
||||
|
||||
const portNumber = Number.parseInt(REST_PORT)
|
||||
|
||||
if (Number.isNaN(portNumber)) throw new Error('The REST_PORT environment variable should be a valid number')
|
||||
|
||||
const app = buildFastifyApp()
|
||||
|
||||
app.get('/timecheck', async (_req, res) => {
|
||||
res.status(200).send({ message: Date.now() })
|
||||
})
|
||||
|
||||
app.all('/*', async (req, res) => {
|
||||
let url = req.originalUrl
|
||||
|
||||
if (url.startsWith('/v')) {
|
||||
url = url.slice(url.indexOf('/', 2))
|
||||
}
|
||||
|
||||
const isMultipart = req.headers['content-type']?.startsWith('multipart/form-data')
|
||||
const hasBody = req.method !== 'GET' && req.method !== 'DELETE'
|
||||
const body = hasBody ? (isMultipart ? await parseMultiformBody(req.body) : req.body) : undefined
|
||||
|
||||
try {
|
||||
const result = await restManager.makeRequest(req.method as RequestMethods, url, {
|
||||
body,
|
||||
})
|
||||
|
||||
if (result) {
|
||||
res.status(200).send(result)
|
||||
return
|
||||
}
|
||||
|
||||
res.status(204).send({})
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
|
||||
res.status(500).send({
|
||||
message: error,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
await app.listen({
|
||||
host: REST_HOST,
|
||||
port: portNumber,
|
||||
})
|
||||
|
||||
logger.info(`REST Proxy listening on port ${portNumber}`)
|
||||
16
examples/bigbot-19/src/rest/restManager.ts
Normal file
16
examples/bigbot-19/src/rest/restManager.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createLogger, createRestManager, LogDepth } from '@discordeno/bot'
|
||||
import { DISCORD_TOKEN } from '../config.js'
|
||||
import { setupRestAnalyticsHooks } from '../influx.js'
|
||||
|
||||
if (!DISCORD_TOKEN) throw new Error('The DISCORD_TOKEN environment variable is missing')
|
||||
|
||||
const manager = createRestManager({
|
||||
token: DISCORD_TOKEN,
|
||||
})
|
||||
|
||||
export const logger = createLogger({ name: 'REST' })
|
||||
logger.setDepth(LogDepth.Full)
|
||||
|
||||
setupRestAnalyticsHooks(manager, logger)
|
||||
|
||||
export default manager
|
||||
14
examples/bigbot-19/tsconfig.json
Normal file
14
examples/bigbot-19/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"moduleResolution": "node",
|
||||
"skipDefaultLibCheck": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"incremental": true
|
||||
}
|
||||
}
|
||||
2634
examples/bigbot-19/yarn.lock
Normal file
2634
examples/bigbot-19/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user