Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 26 additions & 22 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
AES_KEY=yourAESKey # The key used for AES encryption. Can be any ASCII string of any length.
BEZERK_URI=ws://localhost:1234 # URI for Bezerk https://github.com/thesharks/bezerk. Leave blank if not using (it's NOT required).
BEZERK_SECRET=yourBezerkSecret # Secret for Bezerk. Leave blank if not using (it's NOT required).
BOT_CREATOR_NAME=TizzySaurus # Your discord username. Used in certain messages to ping you (@{BOT_CREATOR_NAME}).
BOT_TOKEN=yourBotToken # The token of your bot, retrieved from the Developer Portal.
CREATOR_IDS=442244135840382978 # User IDs of the bot developer(s), separated by commas (NO spaces).
DISCORD_SUPPORT_SERVER=discord.gg/WYTxVjzHnc # Discord invite for your support server. Used in a number of message ouputs. Currently required.
# REQUIRED
AES_KEY="RANDOM_STRING_HERE" # The key used for AES encryption. Can be any ASCII string of any length.
BOT_CREATOR_NAME="TizzySaurus" # Your discord username. Used in certain messages to ping you (@{BOT_CREATOR_NAME}).
BOT_TOKEN="YOUR_DISCORD_BOT_TOKEN" # The token of your bot, retrieved from the Developer Portal.
CREATOR_IDS="442244135840382978" # User IDs of the bot developer(s), separated by commas (NO spaces).
DISCORD_SUPPORT_SERVER="discord.gg/WYTxVjzHnc" # Discord invite for your support server. Used in a number of message ouputs. Currently required.
DISCORD_WEBHOOK_URL=yourWebhookUrl # Webhook URL that's used for sending certain dev-logs to Discord.
ENABLE_TEXT_COMMANDS=true # Whether to allow users to utilise prefix/text commands. 'true' or 'false'. Recommend true.
GLOBAL_BOT_PREFIX=% # Prefix to use for prefix/text commands.
MESSAGE_BATCH_SIZE=100 # How many messages to store in 'cache' before dumping into database. Defaults to 1000.
MESSAGE_HISTORY_DAYS=2 # Maximum age of messages stored in the database. Note that you have to manually setup clearing, e.g. via pg_cron https://github.com/citusdata/pg_cron.
PASTE_SITE_ROOT_URL=https://my-paste-site.com # URL of your selfhosted [haste-server](https://github.com/toptal/haste-server). Used in archive commands and messageDeleteBulk event.
PASTE_SITE_TOKEN=yourHasteServerToken # Optional token to pass in the Authorization header of requests to your self-hosted haste-server.
PGDATABASE=logger # Name of the database that data is stored within.
PGHOST=localhost # Server that PostgreSQL is running on.
PGPASSWORD=postgresPassword # Password of the `PGUSER` user.
PGPORT=5432 # Port that PostgreSQL is running on. Defaults to 5432.
PGUSER=postgresUsername # Name of the user (role) to use when connecting to the database.
RAVEN_URI=sentryUri # Sentry URI obtained from sentry.io. Leave blank if not using sentry.
REDIS_LOCK_TTL=2000 # Time to Live (TTL) for Redis requests.
STAT_SUBMISSION_INTERVAL= # How often to submit stats to Zabbix. Leave blank to not submit stats.
ZABBIX_HOST= # Server that Zabbix is running on. Leave blank if not submitting stats.
ENABLE_TEXT_COMMANDS="true" # Whether to allow users to utilise prefix/text commands. 'true' or 'false'. Recommend true.
GLOBAL_BOT_PREFIX="%" # Prefix to use for prefix/text commands.
MESSAGE_ARCHIVE_SIZE="1000" # How many messages to fetch for the archive commands
MESSAGE_BATCH_SIZE="100" # How many messages to store in 'cache' before dumping into database. Defaults to 1000.
MESSAGE_HISTORY_DAYS="2" # Maximum age of messages stored in the database. Note that you have to manually setup clearing, e.g. via pg_cron https://github.com/citusdata/pg_cron.
PASTE_SITE_ROOT_URL="https://my-paste-site.com" # URL of your selfhosted [haste-server](https://github.com/toptal/haste-server). Used in archive commands and messageDeleteBulk event.
PGDATABASE="logger" # Name of the database that data is stored within.
PGHOST="localhost" # Server that PostgreSQL is running on.
PGPASSWORD="postgresPassword" # Password of the `PGUSER` user.
PGPORT="5432" # Port that PostgreSQL is running on. Defaults to 5432.
PGUSER="postgresUsername" # Name of the user (role) to use when connecting to the database.
REDIS_LOCK_TTL="2000" # Time to Live (TTL) for Redis requests.

# OPTIONAL
BEZERK_URI="" # URI for Bezerk https://github.com/thesharks/bezerk. (example: ws://localhost:1234)
BEZERK_SECRET="" # Secret for Bezerk. Leave blank if not using (it's NOT required).
PASTE_SITE_TOKEN="" # Token to pass in the Authorization header of requests to your self-hosted haste-server.
RAVEN_URI="" # Sentry URI obtained from sentry.io.
STAT_SUBMISSION_INTERVAL="" # How often to submit stats to Zabbix.
ZABBIX_HOST="" # Server that Zabbix is running on.
17 changes: 17 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"env": {
"node": true,
"commonjs": true,
"es2021": true
},
"extends": "eslint:recommended",
"overrides": [
],
"parserOptions": {
"ecmaVersion": "latest"
},
"rules": {
"no-useless-escape": "off",
"no-prototype-builtins": "off"
}
}
32 changes: 18 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
<center><img src="https://cdn.discordapp.com/attachments/349356606883889152/616414555639382016/Logger.png" />
<a href="https://discordbots.org/bot/298822483060981760" >
<img src="https://discordbots.org/api/widget/298822483060981760.svg" alt="Logger" />
</a>
<center>
<img src="https://cdn.discordapp.com/avatars/1223274176786206853/8729a911ab234be299554a5ba006621d.png" />
</center>

Logger is a powerful [Discord](https://discordapp.com) bot meant to give staff members oversight over the various actions taking place in their server. Come talk about me with my creator at [Logger's Lounge](https://discord.gg/ed7Gaa3).
TizzyLog is a powerful [Discord](https://discord.com) bot meant to give staff members oversight over the various actions taking place in their server. Come talk about me with my creator at [TizzyLog server](https://discord.gg/WYTxVjzHnc).

## Fork Details:
**This is a fork of [Logger v3](https://github.com/curtisf/logger)**
- Public Instance: [Invite](https://discord.com/oauth2/authorize?client_id=1223274176786206853)
- This fork includes some improvements and features the main repo doesn't have.
> - Image/File logs for `messageUpdate`, `messageDelete`, `messageBulkDelete` events.

## Installation

You are mostly on your own selfhosting this version. Required applications:
- PostgreSQL 11
- Redis
- NodeJS 14+ (14.5.0)
- [PostgreSQL](https://www.postgresql.org/download/)
- [Redis](https://redis.io/downloads/)
- [NodeJS](https://nodejs.org/en/download)

1. Setup Postgres and add a superuser (default user works)
2. Clone bot repo and enter the created folder
3. Copy .env.example into .env
4. Fill out **all** fields in it (even Sentry unless you hotpatch it out)
4. Fill out **all** of the **required** fields in `.env`
5. `npm install`
6. `node src/miscellaneous/generateDB.js`
7. Set `ENABLE_TEXT_COMMANDS="true"` in .env
8. `node index.js`
9. Use your prefix to set the bot's commands. If yours is %, then you'd do `%setcmd global` to globally set commands, and `%setcmd guild` to quickly set server-specific slash commands
6. `npm run genDB`
7. `node index.js`
8. Use your prefix to set the bot's commands. If yours is %, then you'd do `%setcmd global` to globally set commands, and `%setcmd guild` to quickly set server-specific slash commands
> NOTE: You'll need to restart your Discord client in order for them to show up!

## Usage

Expand All @@ -34,6 +38,6 @@ Pull requests are welcome as long as it follows the following guidelines:
1. Is your idea really one that a large group of moderators would like?
2. Is your idea scalable?
3. Will your idea cause the bot to hit it's global ratelimit?
4. Have you proposed it to *piero#5432* in my [support server?](https://discord.gg/ed7Gaa3)
4. Have you proposed it to *tizzysaurus* in my [support server?](https://discord.gg/WYTxVjzHnc)

If you have done all of the above steps, then open a pull request and I will review it. Style guide and testing will be implemented in a later update.
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ global.webhook = require('./src/miscellaneous/webhooklogger')
global.cluster = require('cluster')
require('./src/miscellaneous/logger')
require('dotenv').config()
if (cluster.isMaster) {
if (global.cluster.isMaster) {
global.logger.startup('Master node init')
require('./primary')
} else {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"test": "standard",
"lint": "node_modules/.bin/eslint . --ext .js"
"lint": "eslint . --ext .js",
"genDB": "node src/miscellaneous/generateDB.js"
},
"keywords": [
"logging",
Expand Down
90 changes: 46 additions & 44 deletions primary.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,61 @@
const cluster = require('cluster')
const sa = require('superagent')
const { get } = require('superagent')
const addListeners = require('./src/miscellaneous/workerlistener')

require('dotenv').config()

const staggerLaunchQueue = []
let staggerInterval

async function init () {
sa.get('https://discord.com/api/gateway/bot').set('Authorization', `Bot ${process.env.BOT_TOKEN}`).then(async b => {
const totalShards = b.body.shards // get recommended shard count
let shardsPerWorker
if (process.env.USE_MAX_CONCURRENCY === 'true') { // eslint-disable-line eqeqeq
if (b.body.session_start_limit.max_concurrency === 1) {
global.logger.warn(`Use max concurrency was specified, but observed gateway concurrency is ${b.body.session_start_limit.max_concurrency}`)
async function init() {
get('https://discord.com/api/gateway/bot')
.set('Authorization', `Bot ${process.env.BOT_TOKEN}`)
.then(async b => {
const totalShards = b.body.shards // get recommended shard count
let shardsPerWorker
if (process.env.USE_MAX_CONCURRENCY === 'true') { // eslint-disable-line eqeqeq
if (b.body.session_start_limit.max_concurrency === 1) {
global.logger.warn(`Use max concurrency was specified, but observed gateway concurrency is ${b.body.session_start_limit.max_concurrency}`)
}
if (b.body.shards % 16 !== 0) {
global.logger.warn('Max concurrency mode is enabled and set on Discord, but the shard count is not a multiple of 16!')
}
global.logger.info(`Using max concurrency of ${b.body.session_start_limit.max_concurrency}. Cluster starting will be delayed!`) // shardsPerWorker is set to 16 below
}
if (b.body.shards % 16 !== 0) {
global.logger.warn('Max concurrency mode is enabled and set on Discord, but the shard count is not a multiple of 16!')
}
global.logger.info(`Using max concurrency of ${b.body.session_start_limit.max_concurrency}. Cluster starting will be delayed!`) // shardsPerWorker is set to 16 below
}
const coreCount = require('os').cpus().length
if (coreCount > totalShards) shardsPerWorker = 1
else shardsPerWorker = process.env.USE_MAX_CONCURRENCY === 'true' ? 16 : Math.ceil(totalShards / coreCount) + 2 // eslint-disable-line eqeqeq
// if max concurrency isn't enabled this will work
const workerCount = Math.ceil(totalShards / shardsPerWorker) // if max concurrency is 16, shard count / 16 will be an integer for how many workers are needed
global.webhook.generic(`Shard manager is booting up. Discord recommends ${totalShards} shards. With the core count being ${coreCount}, there will be ${shardsPerWorker} shards per worker, and ${workerCount} workers.${process.env.USE_MAX_CONCURRENCY === 'true' ? ' Max concurrency is enabled.' : ''}`) // eslint-disable-line eqeqeq
global.logger.startup(`[SHARDER]: TOTAL SHARDS: ${totalShards}\nCore count: ${coreCount}\nShards per worker: ${shardsPerWorker}\nWorker count: ${workerCount}${process.env.USE_MAX_CONCURRENCY === 'true' ? '\nMax concurrency is enabled.' : ''}`)
for (let i = 0; i < workerCount; i++) {
const shardStart = i * shardsPerWorker
let shardEnd = ((i + 1) * shardsPerWorker) - 1
if (shardEnd > totalShards - 1) shardEnd = totalShards - 1
let rangeForShard
if (shardStart === shardEnd) {
rangeForShard = `shard ${shardStart}`
} else {
rangeForShard = `shards ${shardStart}-${shardEnd}`
}
if (process.env.CUSTOM_CLUSTER_LAUNCH == 'true' && i === 0 && process.env.USE_MAX_CONCURRENCY !== 'true') { // eslint-disable-line eqeqeq
global.logger.info(`Custom launch mode specified, use range: ${rangeForShard} (start ${shardStart} end ${shardEnd})`)
global.webhook.generic(`Custom launch mode specified, use range: ${rangeForShard} (start ${shardStart} end ${shardEnd})`)
continue
}
if (process.env.USE_MAX_CONCURRENCY === 'true') {
staggerLaunch({ type: 'bot', shardStart, shardEnd, rangeForShard, totalShards })
continue
const coreCount = require('os').cpus().length
if (coreCount > totalShards) shardsPerWorker = 1
else shardsPerWorker = process.env.USE_MAX_CONCURRENCY === 'true' ? 16 : Math.ceil(totalShards / coreCount) + 2 // eslint-disable-line eqeqeq
// if max concurrency isn't enabled this will work
const workerCount = Math.ceil(totalShards / shardsPerWorker) // if max concurrency is 16, shard count / 16 will be an integer for how many workers are needed
global.webhook.generic(`Shard manager is booting up. Discord recommends ${totalShards} shards. With the core count being ${coreCount}, there will be ${shardsPerWorker} shards per worker, and ${workerCount} workers.${process.env.USE_MAX_CONCURRENCY === 'true' ? ' Max concurrency is enabled.' : ''}`) // eslint-disable-line eqeqeq
global.logger.startup(`[SHARDER]: TOTAL SHARDS: ${totalShards}\nCore count: ${coreCount}\nShards per worker: ${shardsPerWorker}\nWorker count: ${workerCount}${process.env.USE_MAX_CONCURRENCY === 'true' ? '\nMax concurrency is enabled.' : ''}`)
for (let i = 0; i < workerCount; i++) {
const shardStart = i * shardsPerWorker
let shardEnd = ((i + 1) * shardsPerWorker) - 1
if (shardEnd > totalShards - 1) shardEnd = totalShards - 1
let rangeForShard
if (shardStart === shardEnd) {
rangeForShard = `shard ${shardStart}`
} else {
rangeForShard = `shards ${shardStart}-${shardEnd}`
}
if (process.env.CUSTOM_CLUSTER_LAUNCH == 'true' && i === 0 && process.env.USE_MAX_CONCURRENCY !== 'true') { // eslint-disable-line eqeqeq
global.logger.info(`Custom launch mode specified, use range: ${rangeForShard} (start ${shardStart} end ${shardEnd})`)
global.webhook.generic(`Custom launch mode specified, use range: ${rangeForShard} (start ${shardStart} end ${shardEnd})`)
continue
}
if (process.env.USE_MAX_CONCURRENCY === 'true') {
staggerLaunch({ type: 'bot', shardStart, shardEnd, rangeForShard, totalShards })
continue
}
const worker = cluster.fork()
Object.assign(worker, { type: 'bot', shardStart, shardEnd, rangeForShard, totalShards })
addListeners(worker)
}
const worker = cluster.fork()
Object.assign(worker, { type: 'bot', shardStart, shardEnd, rangeForShard, totalShards })
addListeners(worker)
}
}).catch(console.error)
}).catch(console.error)
}

function staggerLaunch (info) {
function staggerLaunch(info) {
// WARNING: 16x sharding won't work on default eris as of today, you will need a fork (mine works!)
staggerLaunchQueue.push(info)
if (!staggerInterval) {
Expand Down
4 changes: 2 additions & 2 deletions replica.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const cluster = require('cluster')
const path = require('path')
const { resolve } = require('path')

async function assignWorkerInfo (info) {
if (info.type !== 'startup') {
Expand All @@ -9,7 +9,7 @@ async function assignWorkerInfo (info) {

if (info.processType === 'bot') {
Object.assign(cluster.worker, info)
require(path.resolve('src', 'bot', 'index'))
require(resolve('src', 'bot', 'index'))
}
}

Expand Down
36 changes: 16 additions & 20 deletions src/bot/commands/archive.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
const sa = require('superagent')
const { displayUser } = require('../utils/constants');
const { createHaste } = require('../utils/createHaste')

module.exports = {
func: async (message, suffix) => {
if (!process.env.PASTE_SITE_ROOT_URL) return message.channel.createMessage('The bot owner hasn\'t yet configured the paste site, so this command is unavailable.')
if (!suffix || isNaN(suffix)) return message.channel.createMessage('That isn\'t a valid suffix! Please provide any number between 5 and 1000 (10,000 if Patreon).')
const limit = parseInt(process.env.MESSAGE_ARCHIVE_SIZE || 1000);
if (!suffix || isNaN(suffix)) return message.channel.createMessage(`That isn't a valid suffix! Please provide any number between 5 and ${limit}.`)
const num = parseInt(suffix)
if (num < 5 || num > 1000) return message.channel.createMessage('That number is invalid! Please provide any number between 5 and 1000 (10,000 if Patreon)')
message.channel.getMessages({ limit: num }).then(messages => {
const pasteString = messages.reverse().filter(m => !m.applicationID).map(m => `${m.author.username}${m.author.discriminator === '0' ? '' : `#${m.author.discriminator}`} (${m.author.id}) | ${new Date(m.timestamp).toUTCString()}: ${m.content ? m.content : ''} ${m.embeds.length === 0 ? '' : `| {"embeds": [${m.embeds.map(e => JSON.stringify(e))}]}`} | ${m.attachments.length === 0 ? '' : ` =====> Attachment: ${m.attachments[0].filename}:${m.attachments[0].url}`}`).join('\r\n')
sa
.post(`${process.env.PASTE_SITE_ROOT_URL.endsWith("/") ? process.env.PASTE_SITE_ROOT_URL.slice(0, -1) : process.env.PASTE_SITE_ROOT_URL}/documents`)
.set('Authorization', process.env.PASTE_SITE_TOKEN ?? '')
.set('Content-Type', 'text/plain')
.send(pasteString || 'No messages were able to be archived')
.end((err, res) => {
if (!err && res.statusCode === 200 && res.body.key) {
message.channel.createMessage(`<@${message.author.id}>, **${messages.length}** message(s) could be archived. Link: ${process.env.PASTE_SITE_ROOT_URL.endsWith("/") ? process.env.PASTE_SITE_ROOT_URL.slice(0, -1) : process.env.PASTE_SITE_ROOT_URL}/${res.body.key}.txt`)
} else {
global.logger.error(err, res.body)
global.webhook.error('An error has occurred while posting to the paste website. Check logs for more.')
}
})
if (num < 5 || num > limit) return message.channel.createMessage(`That number is invalid! Please provide any number between 5 and ${limit}`)

const messages = await message.channel.getMessages({ limit: num })
const pasteString = messages.reverse().filter(m => !m.applicationID).map(m => `${displayUser(m.author)} (${m.author.id}) | ${new Date(m.timestamp).toUTCString()}: ${m.content ? m.content : ''} ${m.embeds.length === 0 ? '' : `| {"embeds": [${m.embeds.map(e => JSON.stringify(e))}]}`} | ${m.attachments.length === 0 ? '' : ` =====> Attachment: ${m.attachments[0].filename}:${m.attachments[0].url}`}`).join('\r\n')
const link = await createHaste(pasteString)
message.channel.createMessage({
content: `<@${message.author.id}>, **${messages.length}** message(s) could be archived. Link: ${link || "View the messages.txt file!"}`,
}, {
name: "messages.txt",
file: Buffer.from(pasteString)
})
},
name: 'archive',
category: 'Utility',
perm: 'manageMessages',
quickHelp: 'Makes a log online of up to the last 1000 messages in a channel. Does NOT delete any messages. Patreon bot only: fetch 10,000 messages & [upgraded log site](https://logs.discord.website/logs/W9NbmmULEpxMFMoiBuKrYG)',
quickHelp: `Makes a log online of up to the last ${process.env.MESSAGE_ARCHIVE_SIZE || 100} messages in a channel. Does NOT delete any messages.`,
examples: `\`${process.env.GLOBAL_BOT_PREFIX}archive 5\` <- lowest amount possible
\`${process.env.GLOBAL_BOT_PREFIX}archive 1000\` <- maximum count of messages to archive
\`${process.env.GLOBAL_BOT_PREFIX}archive ${process.env.MESSAGE_ARCHIVE_SIZE || 1000}\` <- maximum count of messages to archive
\`${process.env.GLOBAL_BOT_PREFIX}archive 25\` <- create a log of the last 25 messages in the channel`
}
Loading