mirror of
https://github.com/toast-ts/Daggerbot-TS.git
synced 2024-11-16 20:00:59 -05:00
Merry Christmas! Here's the V3
This commit is contained in:
parent
cefcbdb1ac
commit
bfec8ec431
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@ -0,0 +1,10 @@
|
||||
botStartup.bat
|
||||
.ncurc.json
|
||||
.gitignore
|
||||
README.md
|
||||
.github
|
||||
.vscode
|
||||
.yarn/sdks
|
||||
dist/
|
||||
docker-compose.yml
|
||||
startWithYarn.cjs
|
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,12 +1,5 @@
|
||||
.vscode
|
||||
# Yarn stuff
|
||||
.yarn/cache/
|
||||
.yarn/unplugged/
|
||||
.yarn/sdks/
|
||||
.yarn/install-state.gz
|
||||
# NodeJS stuff
|
||||
.ncurc.json
|
||||
# Bot stuff
|
||||
dist/
|
||||
src/database/
|
||||
.yarn
|
||||
# TypeScript stuff
|
||||
dist
|
||||
src/*.json
|
||||
|
4
.ncurc.json
Normal file
4
.ncurc.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"upgrade": true,
|
||||
"reject": []
|
||||
}
|
909
.pnp.loader.mjs
generated
909
.pnp.loader.mjs
generated
File diff suppressed because it is too large
Load Diff
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"arcanis.vscode-zipfs"
|
||||
]
|
||||
}
|
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
12
.vscode/tasks.json
vendored
Normal file
12
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: yarn dev",
|
||||
"detail": "yarn tsc && yarn node . src/DB-Beta.config.json"
|
||||
}
|
||||
]
|
||||
}
|
874
.yarn/releases/yarn-3.6.3.cjs
vendored
874
.yarn/releases/yarn-3.6.3.cjs
vendored
File diff suppressed because one or more lines are too long
10
.yarnrc.yml
10
.yarnrc.yml
@ -1 +1,9 @@
|
||||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
progressBarStyle: "simba"
|
||||
|
||||
npmScopes:
|
||||
toast:
|
||||
npmRegistryServer: https://git.toast-server.net/api/packages/toast/npm/
|
||||
|
28
README.md
28
README.md
@ -1,7 +1,25 @@
|
||||
<p align="center">
|
||||
<img width="650" height="240" src="https://cdn.discordapp.com/attachments/1118960531135541318/1151036641717260348/Daggerbot-TS-repo.gif">
|
||||
<h1 align="center">Daggerbot-TS Description</h1>
|
||||
<p align="center">
|
||||
This is a 1st generation bot that is a TypeScript-based bot converted from JavaScript at <s><a href="https://github.com/SpaceManBuzz/DaggerBot-">SpaceManBuzz/DaggerBot-</a></s> (now archived and privated)
|
||||
</p>
|
||||
<img width="630" height="250" src="https://cdn.toast-server.net/daggerwin/DaggerbotV3-Repo.gif">
|
||||
<h1 align="center">Daggerbot V3 Description</h1>
|
||||
</p>
|
||||
This is a repository for V3 revision that has been transitioned and rewritten from V2 bot to be more robust and reliable with today's standards.
|
||||
|
||||
This revision took **4 months** (Late September to Mid December) working on and off to do literally everything that needed a rewrite so badly that it cannot be done in V2.
|
||||
|
||||
**Q:** So what are the changes if it almost looks the same as V2?
|
||||
**A:** Here's the bullet points of the changes so far;
|
||||
- Reworked some of the files
|
||||
- Commands and events are now classes
|
||||
- Bot no longer stores short-term and long-term data locally
|
||||
- Transitioned MongoDB schemas to PostgreSQL models
|
||||
- MPModule got a facelift and rewritten from scratch
|
||||
- Moved the module files to another directory called `modules`
|
||||
- Renamed `funcs` to `components` as I don't think `funcs` directory makes sense anymore at this point.
|
||||
|
||||
If you're looking for V2 revision, it has been moved to a [branch called `old`](https://github.com/AnxietyisReal/Daggerbot-TS/tree/old).
|
||||
|
||||
This is a revision history of how far we come in development cycle;
|
||||
| Revision | Language | Library | Commands |
|
||||
|---------|----------|-----------|----------|
|
||||
| V1 | JavaScript | Discord.JS v13 | Message commands |
|
||||
| V2-V3 | TypeScript | Discord.JS v14 | Slash/message commands |
|
||||
|
@ -1,7 +1,19 @@
|
||||
services:
|
||||
cache:
|
||||
container_name: redis-cache
|
||||
image: redis/redis-stack-server:7.2.0-v2
|
||||
image: redis/redis-stack-server:7.2.0-v6
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 6379:6379/tcp
|
||||
- 6379:6379/tcp
|
||||
db:
|
||||
container_name: postgres-db
|
||||
image: postgres:16.1-alpine3.19
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 5432:5432/tcp
|
||||
volumes:
|
||||
- /var/lib/docker/volumes/daggerbot-db:/var/lib/postgresql/data:rw
|
||||
environment:
|
||||
POSTGRES_USER: daggerbot
|
||||
POSTGRES_PASSWORD: dagbot
|
||||
POSTGRES_DB: daggerbot
|
||||
|
39
package.json
39
package.json
@ -5,7 +5,11 @@
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/AnxietyisReal/Daggerbot-TS/tree/old"
|
||||
"url": "git+https://github.com/AnxietyisReal/Daggerbot-TS.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "yarn tsc && yarn node . src/DB-Beta.config.json",
|
||||
"sdk": "yarn dlx @yarnpkg/sdks vscode"
|
||||
},
|
||||
"author": "Toast",
|
||||
"license": "ISC",
|
||||
@ -21,30 +25,37 @@
|
||||
"!ia32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.17.0",
|
||||
"yarn": ">=3.6.1",
|
||||
"npm": "please use yarn instead of npm"
|
||||
"node": ">=18.18.0, <19 || >=20",
|
||||
"yarn": ">=4.0.0",
|
||||
"npm": "yarn is required, dont use npm"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"packageManager": "yarn@3.6.3",
|
||||
"packageManager": "yarn@4.0.2+sha256.825003a0f561ad09a3b1ac4a3b3ea6207af2796d54f62a9420520915721f5186",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "4.0.0",
|
||||
"@octokit/rest": "20.0.2",
|
||||
"@toast/tokenservice-client": "1.0.5",
|
||||
"ansi-colors": "4.1.3",
|
||||
"canvas": "2.11.2",
|
||||
"dayjs": "1.11.10",
|
||||
"discord.js": "14.13.0",
|
||||
"discord.js": "14.14.1",
|
||||
"fast-xml-parser": "4.3.2",
|
||||
"mongoose": "7.6.3",
|
||||
"ms": "2.1.3",
|
||||
"node-cron": "3.0.2",
|
||||
"redis": "4.6.10",
|
||||
"systeminformation": "5.21.13"
|
||||
"node-cron": "3.0.3",
|
||||
"pg": "8.11.3",
|
||||
"pg-hstore": "2.3.4",
|
||||
"redis": "4.6.12",
|
||||
"sequelize": "6.35.2",
|
||||
"simple-git": "3.21.0",
|
||||
"systeminformation": "5.21.22",
|
||||
"undici": "6.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ms": "0.7.33",
|
||||
"@types/node": "20.8.8",
|
||||
"@types/node-cron": "3.0.10",
|
||||
"typescript": "5.2.2"
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "20.10.5",
|
||||
"@types/node-cron": "3.0.11",
|
||||
"@types/pg": "8.10.9",
|
||||
"@types/sequelize": "4.28.19",
|
||||
"typescript": "5.3.3"
|
||||
}
|
||||
}
|
||||
|
126
src/client.ts
126
src/client.ts
@ -1,52 +1,49 @@
|
||||
interface repeatedMessages {
|
||||
[key: string]: {data: Discord.Collection<number,{type:string,channel:string}>,timeout: NodeJS.Timeout}
|
||||
interface IRepeatedMessages {
|
||||
[key:string]: {
|
||||
type:string;
|
||||
count:number;
|
||||
firstTime:number;
|
||||
timeout:NodeJS.Timeout;
|
||||
}
|
||||
}
|
||||
type MPServerCache = Record<string,{
|
||||
players: FSPlayer[],
|
||||
status: 'online' | 'offline' | null,
|
||||
name: string | null
|
||||
}>
|
||||
|
||||
import Discord from 'discord.js';
|
||||
import ConfigHelper from './helpers/ConfigHelper.js';
|
||||
import {readdirSync} from 'node:fs';
|
||||
import {Config, FSPlayer} from './typings/interfaces';
|
||||
import bannedWords from './models/bannedWords.js';
|
||||
import userLevels from './models/userLevels.js';
|
||||
import suggestion from './models/suggestion.js';
|
||||
import punishments from './models/punishments.js';
|
||||
import tags from './models/tagSystem.js';
|
||||
import bonkCount from './models/bonkCount.js';
|
||||
import MPServer from './models/MPServer.js';
|
||||
import DatabaseServer from './funcs/DatabaseServer.js';
|
||||
import CacheServer from './funcs/CacheServer.js';
|
||||
import {Config} from './interfaces';
|
||||
import {
|
||||
DailyMsgsSvc, UserLevelsSvc, BonkCountSvc,
|
||||
MPServerSvc, PunishmentsSvc, ProhibitedWordsSvc,
|
||||
SuggestionsSvc, TagSystemSvc, YouTubeChannelsSvc
|
||||
} from './models/IMPORTS.js';
|
||||
import DatabaseServer from './components/DatabaseServer.js';
|
||||
import CacheServer from './components/CacheServer.js';
|
||||
import fxp from 'fast-xml-parser';
|
||||
import dayjs from 'dayjs';
|
||||
import TSClient from './helpers/TSClient.js';
|
||||
const importconfig = ConfigHelper.loadConfig();
|
||||
|
||||
export default class TClient extends Discord.Client {
|
||||
invites: Map<any, any>;
|
||||
commands: Discord.Collection<string, any>;
|
||||
registry: Array<Discord.ApplicationCommandDataResolvable>;
|
||||
config: Config;
|
||||
embed: typeof Discord.EmbedBuilder;
|
||||
collection: typeof Discord.Collection;
|
||||
attachmentBuilder: typeof Discord.AttachmentBuilder;
|
||||
dayjs: typeof dayjs;
|
||||
fxp: typeof fxp;
|
||||
userLevels: userLevels;
|
||||
punishments: punishments;
|
||||
bonkCount: bonkCount;
|
||||
bannedWords: bannedWords;
|
||||
MPServer: MPServer;
|
||||
MPServerCache: MPServerCache = {};
|
||||
suggestion: suggestion;
|
||||
tags: tags;
|
||||
repeatedMessages: repeatedMessages;
|
||||
statsGraph: number;
|
||||
public invites: Map<any, any> = new Map();
|
||||
public commands: Discord.Collection<string, any> = new Discord.Collection();
|
||||
public registry: Array<Discord.ApplicationCommandDataResolvable> = [];
|
||||
public config: Config;
|
||||
public embed: typeof Discord.EmbedBuilder = Discord.EmbedBuilder;
|
||||
public collection: typeof Discord.Collection = Discord.Collection;
|
||||
public attachment: typeof Discord.AttachmentBuilder = Discord.AttachmentBuilder;
|
||||
public dayjs: typeof dayjs = dayjs;
|
||||
public fxp: typeof fxp = fxp;
|
||||
public dailyMsgs: DailyMsgsSvc = new DailyMsgsSvc();
|
||||
public userLevels: UserLevelsSvc = new UserLevelsSvc(this);
|
||||
public punishments: PunishmentsSvc = new PunishmentsSvc(this);
|
||||
public bonkCount: BonkCountSvc = new BonkCountSvc();
|
||||
public prohibitedWords: ProhibitedWordsSvc = new ProhibitedWordsSvc();
|
||||
public MPServer: MPServerSvc = new MPServerSvc();
|
||||
public suggestions: SuggestionsSvc = new SuggestionsSvc();
|
||||
public tags: TagSystemSvc = new TagSystemSvc();
|
||||
public ytChannels: YouTubeChannelsSvc = new YouTubeChannelsSvc();
|
||||
public repeatedMessages: IRepeatedMessages = {};
|
||||
public statsGraph: number = -120;
|
||||
|
||||
constructor(){
|
||||
constructor() {
|
||||
super({
|
||||
intents: [
|
||||
Discord.GatewayIntentBits.Guilds, Discord.GatewayIntentBits.GuildMembers,
|
||||
@ -56,60 +53,31 @@ export default class TClient extends Discord.Client {
|
||||
Discord.GatewayIntentBits.DirectMessages
|
||||
], partials: [
|
||||
Discord.Partials.Channel, Discord.Partials.Reaction, Discord.Partials.Message
|
||||
], allowedMentions: {users:[],roles:[]}
|
||||
], allowedMentions: {users:[], roles:[]}
|
||||
})
|
||||
this.invites = new Map();
|
||||
this.commands = new Discord.Collection();
|
||||
this.registry = [];
|
||||
this.config = importconfig as Config;
|
||||
this.embed = Discord.EmbedBuilder;
|
||||
this.collection = Discord.Collection;
|
||||
this.attachmentBuilder = Discord.AttachmentBuilder;
|
||||
this.dayjs = dayjs;
|
||||
this.fxp = fxp;
|
||||
this.userLevels = new userLevels(this);
|
||||
this.bonkCount = new bonkCount(this);
|
||||
this.punishments = new punishments(this);
|
||||
this.bannedWords = new bannedWords(this);
|
||||
this.MPServer = new MPServer(this);
|
||||
this.MPServerCache = {} as MPServerCache;
|
||||
this.suggestion = new suggestion(this);
|
||||
this.tags = new tags(this);
|
||||
this.repeatedMessages = {};
|
||||
this.setMaxListeners(62);
|
||||
this.statsGraph = -120;
|
||||
this.config = ConfigHelper.loadConfig() as Config;
|
||||
this.setMaxListeners(50);
|
||||
}
|
||||
async init(){
|
||||
async init() {
|
||||
console.time('Startup');
|
||||
await Promise.all([
|
||||
CacheServer.init(),
|
||||
DatabaseServer.init(),
|
||||
this.login((await TSClient.Token()).main)
|
||||
]);
|
||||
|
||||
const eventFiles = await Promise.all(
|
||||
readdirSync('dist/events').map(file=>import(`./events/${file}`))
|
||||
);
|
||||
const eventFiles = await Promise.all(readdirSync('dist/events').map(file=>import(`./events/${file}`)));
|
||||
eventFiles.forEach((eventFile, index)=>{
|
||||
const eventName = readdirSync('dist/events')[index].replace('.js', '');
|
||||
this.on(eventName, async(...args)=>eventFile.default.run(this, ...args));
|
||||
});
|
||||
|
||||
const commandFiles = await Promise.all(
|
||||
readdirSync('dist/commands').map(file=>import(`./commands/${file}`))
|
||||
);
|
||||
const commandFiles = await Promise.all(readdirSync('dist/commands').map(file=>import(`./commands/${file}`)));
|
||||
commandFiles.forEach(commandFile=>{
|
||||
const {default: command} = commandFile;
|
||||
this.commands.set(command.data.name, {command, uses: 0});
|
||||
this.registry.push(command.data.toJSON());
|
||||
});
|
||||
|
||||
Object.keys(this.config.MPStatsLocation).forEach(naming=>{
|
||||
this.MPServerCache[naming] = {
|
||||
players: [],
|
||||
status: null,
|
||||
name: null
|
||||
}
|
||||
});
|
||||
await Promise.all([
|
||||
CacheServer.init(),
|
||||
DatabaseServer.init(),
|
||||
this.login((await TSClient()).main)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Punish from '../funcs/Punish.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
import Punish from '../components/Punish.js';
|
||||
export default class Ban {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
Punish(client, interaction, 'ban');
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('ban')
|
||||
.setDescription('Ban a member from the server')
|
||||
.addUserOption(x=>x
|
||||
@ -18,4 +18,4 @@ export default {
|
||||
.addStringOption(x=>x
|
||||
.setName('reason')
|
||||
.setDescription('Reason for the ban'))
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import {writeFileSync} from 'node:fs';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (!MessageTool.isStaff(interaction.member) && !client.config.whitelist.includes(interaction.member.id)) return MessageTool.youNeedRole(interaction, 'admin');
|
||||
const word = interaction.options.getString('word');
|
||||
const wordExists = await client.bannedWords._content.findById(word);
|
||||
({
|
||||
add: async()=>{
|
||||
if (wordExists) return interaction.reply({content: `\`${word}\` is already added.`, ephemeral: true});
|
||||
await client.bannedWords._content.create({_id:word}).then(a=>a.save()).catch(e=>{if (e.name == 'No document found for query') return});
|
||||
interaction.reply(`Successfully added \`${word}\` to the database.`)
|
||||
},
|
||||
remove: async()=>{
|
||||
if (!wordExists) return interaction.reply({content: `\`${word}\` doesn't exist on the list.`, ephemeral: true});
|
||||
await client.bannedWords._content.findOneAndDelete({_id:word});
|
||||
interaction.reply(`Successfully removed \`${word}\` from the database.`)
|
||||
},
|
||||
view: async()=>{
|
||||
const findAll = await client.bannedWords.findInCache();
|
||||
writeFileSync('src/database/bw_dump.json', JSON.stringify(findAll.map(i=>i._id), null, 2), {encoding: 'utf8', flag: 'w+'});
|
||||
interaction.reply({content: 'Here\'s the dump file from the database.', files: ['src/database/bw_dump.json'], ephemeral: true}).catch(err=>interaction.reply({content: `Ran into an error, notify <@&${client.config.mainServer.roles.bottech}> if it happens again:\n\`${err.message}\``, ephemeral: true}))
|
||||
}
|
||||
} as any)[interaction.options.getSubcommand()]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
.setName('bannedwords')
|
||||
.setDescription('description placeholder')
|
||||
.addSubcommand(x=>x
|
||||
.setName('view')
|
||||
.setDescription('View the list of currently banned words'))
|
||||
.addSubcommand(x=>x
|
||||
.setName('add')
|
||||
.setDescription('Add the word to the list')
|
||||
.addStringOption(x=>x
|
||||
.setName('word')
|
||||
.setDescription('Add the specific word to automod\'s bannedWords database')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('remove')
|
||||
.setDescription('Remove the word from the list')
|
||||
.addStringOption(x=>x
|
||||
.setName('word')
|
||||
.setDescription('Remove the specific word from automod\'s bannedWords list')
|
||||
.setRequired(true)))
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
export default class Bonk {
|
||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
//if (!client.isStaff(interaction.member) && interaction.channelId == '468835415093411863') return interaction.reply('This command is restricted to staff only in this channel due to high usage.')
|
||||
const member = interaction.options.getMember('member') as Discord.GuildMember;
|
||||
const reason = interaction.options.getString('reason');
|
||||
if (member.permissions.has('Administrator')) return interaction.reply('You cannot bonk an admin!');
|
||||
|
||||
await client.bonkCount._incrementUser(member.id);
|
||||
await client.bonkCount.hitCountIncremental(member.id);
|
||||
interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor)
|
||||
.setDescription(`> <@${member.id}> has been bonked!\n${reason?.length == null ? '' : `> Reason: **${reason}**`}`)
|
||||
.setImage('https://media.tenor.com/7tRddlNUNNcAAAAd/hammer-on-head-minions.gif')
|
||||
.setFooter({text: `Bonk count for ${member.displayName}: ${await client.bonkCount._content.findById(member.id).then(b=>b.value.toLocaleString('en-US'))}`})
|
||||
.setFooter({text: `Bonk count for ${member.displayName}: ${await client.bonkCount.fetchUser(member.id).then(x=>x.count)}`})
|
||||
]})
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('bonk')
|
||||
.setDescription('Bonk a member')
|
||||
.addUserOption(x=>x
|
||||
|
@ -1,10 +1,10 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import FormatTime from '../helpers/FormatTime.js';
|
||||
import Formatters from '../helpers/Formatters.js';
|
||||
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
export default class Calculator {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
const now = Date.now();
|
||||
const exp = interaction.options.getString('expression', true).replace(/[^-()\d/*+.]/g, '');
|
||||
try {
|
||||
@ -22,12 +22,12 @@ export default {
|
||||
'-- Siri, 2015'
|
||||
));
|
||||
};
|
||||
interaction.reply({embeds:[new client.embed().setColor(client.config.embedColor).addFields({name: 'Expression', value: `\`\`\`js\n${exp}\n\`\`\``},{name: 'Answer', value: `\`\`\`js\n${result}\n\`\`\``}).setFooter({text: `Time taken: ${FormatTime(Date.now() - now, 3)}`})]})
|
||||
interaction.reply({embeds:[new client.embed().setColor(client.config.embedColor).addFields({name: 'Expression', value: `\`\`\`js\n${exp}\n\`\`\``},{name: 'Answer', value: `\`\`\`js\n${result}\n\`\`\``}).setFooter({text: `Time taken: ${Formatters.timeFormat(Date.now() - now, 3)}`})]})
|
||||
} catch {
|
||||
interaction.reply('The given expression is invalid.');
|
||||
}
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('calculator')
|
||||
.setDescription('Calculate a math expression or simple 2+2')
|
||||
.addStringOption(x=>x
|
||||
|
@ -1,42 +1,59 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import FormatTime from '../helpers/FormatTime.js';
|
||||
import Formatters from '../helpers/Formatters.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
export default class Case {
|
||||
private static async updateEntry(client:TClient, caseId:number, reason:string) {
|
||||
const logsArray = [client.config.dcServer.channels.logs, client.config.dcServer.channels.bankick_log];
|
||||
for (const channelID of logsArray) {
|
||||
const channel = await client.channels.fetch(channelID) as Discord.TextChannel;
|
||||
if (channel && channel.type === Discord.ChannelType.GuildText) {
|
||||
const messages = await channel.messages.fetch({limit: 3});
|
||||
messages.forEach(async message=>{
|
||||
if (message?.embeds[0]?.title.match(new RegExp(`Case #${caseId}`))) {
|
||||
const findIndex = message?.embeds[0].fields.findIndex(x=>x.name === 'Reason');
|
||||
await message.edit({embeds: [new client.embed(message.embeds[0]).spliceFields(findIndex, 1, {name: 'Reason', value: `\`${reason}\``})]});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (!MessageTool.isStaff(interaction.member)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||
const caseId = interaction.options.getInteger('id');
|
||||
({
|
||||
update: async()=>{
|
||||
const reason = interaction.options.getString('reason');
|
||||
await client.punishments._content.findByIdAndUpdate(caseId, {reason});
|
||||
if (await client.punishments._content.findById(caseId)) await interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorGreen).setTitle('Case updated').setDescription(`Case #${caseId} has been successfully updated with new reason:\n\`${reason}\``)]});
|
||||
else interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorRed).setTitle('Case not updated').setDescription(`Case #${caseId} is not stored on database, not updating the reason.`)]});
|
||||
await client.punishments.updateReason(caseId, reason);
|
||||
if (client.punishments.findCase(caseId)) {
|
||||
await this.updateEntry(client, caseId, reason);
|
||||
await interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorGreen).setTitle('Case updated').setDescription(`Case #${caseId} has been successfully updated with new reason:\n\`${reason}\``)]});
|
||||
} else interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorRed).setTitle('Case not updated').setDescription(`Case #${caseId} is not found in database, not updating the reason.`)]});
|
||||
},
|
||||
view: async()=>{
|
||||
const punishment = await client.punishments._content.findById(caseId);
|
||||
if (!punishment) return interaction.reply('Invalid Case ID');
|
||||
const cancelledBy = punishment.expired ? await client.punishments._content.findOne({cancels:punishment.id}) : null;
|
||||
const cancels = punishment.cancels ? await client.punishments._content.findOne({_id:punishment.cancels}) : null;
|
||||
const embed = new client.embed().setColor(client.config.embedColor).setTimestamp(punishment.time).setTitle(`${punishment.type[0].toUpperCase()+punishment.type.slice(1)} | Case #${punishment.id}`).addFields(
|
||||
{name: '🔹 User', value: `${MessageTool.formatMention(punishment.member, 'user')} \`${punishment.member}\``, inline: true},
|
||||
{name: '🔹 Moderator', value: `${MessageTool.formatMention(punishment.moderator, 'user')} \`${punishment.moderator}\``, inline: true},
|
||||
const punishment = await client.punishments.findCase(caseId);
|
||||
if (!punishment) return interaction.reply('Case ID is not found in database.');
|
||||
const cancelledBy = punishment.dataValues.expired ? await client.punishments.findByCancels(punishment.dataValues.case_id) : null;
|
||||
const cancels = punishment.dataValues.cancels ? await client.punishments.findCase(punishment.dataValues.cancels) : null;
|
||||
const embed = new client.embed().setColor(client.config.embedColor).setTimestamp(Number(punishment.dataValues.time)).setTitle(`${punishment.dataValues.type[0].toUpperCase()+punishment.dataValues.type.slice(1)} | Case #${punishment.dataValues.case_id}`).addFields(
|
||||
{name: 'User', value: `${MessageTool.formatMention(punishment.dataValues.member, 'user')} \`${punishment.dataValues.member}\``, inline: true},
|
||||
{name: 'Moderator', value: `${MessageTool.formatMention(punishment.dataValues.moderator, 'user')} \`${punishment.dataValues.moderator}\``, inline: true},
|
||||
{name: '\u200b', value: '\u200b', inline: true},
|
||||
{name: '🔹 Reason', value: `\`${punishment.reason || 'Reason unspecified'}\``, inline: true})
|
||||
if (punishment.duration) embed.addFields({name: '🔹 Duration', value: `${FormatTime(punishment.duration, 100)}`})
|
||||
if (punishment.expired) embed.addFields({name: '🔹 Expired', value: `This case has been overwritten by Case #${cancelledBy.id} for reason \`${cancelledBy.reason}\``})
|
||||
if (punishment.cancels) embed.addFields({name: '🔹 Overwrites', value: `This case overwrites Case #${cancels.id} with reason \`${cancels.reason}\``})
|
||||
{name: 'Reason', value: `\`${punishment.reason || 'Reason unspecified'}\``, inline: true})
|
||||
if (punishment.dataValues.duration) embed.addFields({name: 'Duration', value: `${Formatters.timeFormat(punishment.dataValues.duration, 100)}`})
|
||||
if (punishment.dataValues.expired) embed.addFields({name: 'Expired', value: `This case has been overwritten by Case #${cancelledBy.dataValues.case_id} for reason \`${cancelledBy.dataValues.reason}\``})
|
||||
if (punishment.dataValues.cancels) embed.addFields({name: 'Overwrites', value: `This case overwrites Case #${cancels.dataValues.case_id} with reason \`${cancels.dataValues.reason}\``})
|
||||
interaction.reply({embeds: [embed]});
|
||||
},
|
||||
member: async()=>{
|
||||
const user = (interaction.options.getUser('user') as Discord.User);
|
||||
if (user.bot) return interaction.reply(`**${user.username}**'s punishment history cannot be viewed as they are a bot.`)
|
||||
const punishments = await client.punishments._content.find({});
|
||||
const userPunishmentData = await client.punishments._content.find({'member':user.id});
|
||||
const userPunishment = userPunishmentData.sort((a,b)=>a.time-b.time).map((punishment)=>{
|
||||
const punishments = await client.punishments.getAllCases();
|
||||
const userPunishmentData = punishments.filter(x=>x.dataValues.member === user.id);
|
||||
const userPunishment = userPunishmentData.sort((a,b)=>a.dataValues.time-b.dataValues.time).map(punishment=>{
|
||||
return {
|
||||
name: `${punishment.type[0].toUpperCase()+punishment.type.slice(1)} | Case #${punishment.id}`,
|
||||
value: `Reason: \`${punishment.reason}\`\n${punishment.duration ? `Duration: ${FormatTime(punishment.duration, 3)}\n` : ''}Moderator: ${MessageTool.formatMention(punishment.moderator, 'user')}${punishment.expired ? `\nOverwritten by Case #${punishments.find(x=>x.cancels===punishment._id)?._id}` : ''}${punishment.cancels ? `\nOverwrites Case #${punishment.cancels}` : ''}`
|
||||
name: `${punishment.dataValues.type[0].toUpperCase()+punishment.dataValues.type.slice(1)} | Case #${punishment.dataValues.case_id}`,
|
||||
value: `Reason: \`${punishment.dataValues.reason}\`\n${punishment.dataValues.duration ? `Duration: ${Formatters.timeFormat(punishment.dataValues.duration, 3)}\n` : ''}Moderator: ${MessageTool.formatMention(punishment.dataValues.moderator, 'user')}${punishment.dataValues.expired ? `\nOverwritten by Case #${punishments.find(x=>x.dataValues.cancels===punishment.dataValues.case_id)?.case_id}` : ''}${punishment.dataValues.cancels ? `\nOverwrites Case #${punishment.dataValues.cancels}` : ''}`
|
||||
}
|
||||
});
|
||||
if (!punishments || !userPunishment) return interaction.reply(`**${user.username}** has a clean record.`)
|
||||
@ -44,13 +61,13 @@ export default {
|
||||
return interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle(`${user.username}'s punishment history`).setDescription(`**ID:** \`${user.id}\``).setFooter({text: `${userPunishment.length} total punishments. Viewing page ${pageNum} out of ${Math.ceil(userPunishment.length/6)}.`}).addFields(userPunishment.slice((pageNum - 1) * 6, pageNum * 6))]});
|
||||
}
|
||||
} as any)[interaction.options.getSubcommand()]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('case')
|
||||
.setDescription('Retrieve case information or user\'s punishment history')
|
||||
.addSubcommand(x=>x
|
||||
.setName('view')
|
||||
.setDescription('View a multiple or single case')
|
||||
.setDescription('View information of the case ID')
|
||||
.addIntegerOption(x=>x
|
||||
.setName('id')
|
||||
.setDescription('Case ID')
|
||||
@ -76,4 +93,4 @@ export default {
|
||||
.setName('reason')
|
||||
.setDescription('New reason for the case')
|
||||
.setRequired(true)))
|
||||
};
|
||||
}
|
||||
|
@ -1,16 +1,13 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
export default class Contributors {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Daggerbot contributors').setDescription(MessageTool.concatMessage(
|
||||
'**Thanks to those below that contributed to/developed the bot!**',
|
||||
client.config.contribList.map(id=>{
|
||||
const member = interaction.guild.members.cache.get(id);
|
||||
return `${member?.user?.username ?? 'N/A'} <@${id}>`}
|
||||
).join('\n')))]})
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
'**Thanks to those below that made their contributions to the bot!**',
|
||||
client.config.contribList.map(id=>`${interaction.guild.members.cache.get(id)?.user?.username ?? 'N/A'} <@${id}>`).join('\n')))]})
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('contributors')
|
||||
.setDescription('List of people who contributed to the bot')
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,26 @@
|
||||
import Discord from 'discord.js';
|
||||
import {Octokit} from '@octokit/rest';
|
||||
import {createTokenAuth} from '@octokit/auth-token';
|
||||
import {exec} from 'node:child_process';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import UsernameHelper from '../helpers/UsernameHelper.js';
|
||||
import FormatTime from '../helpers/FormatTime.js';
|
||||
import TSClient from '../helpers/TSClient.js';
|
||||
import Formatters from '../helpers/Formatters.js';
|
||||
import GitHub from '../helpers/GitHub.js';
|
||||
import TClient from '../client.js';
|
||||
import fs from 'node:fs';
|
||||
import util from 'node:util';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>) {
|
||||
import fs from 'node:fs';
|
||||
export default class Developer {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>) {
|
||||
if (!client.config.whitelist.includes(interaction.user.id)) return MessageTool.youNeedRole(interaction, 'bottech');
|
||||
({
|
||||
eval: async()=>{
|
||||
if (!client.config.eval) return interaction.reply({content: 'Eval is currently disabled.', ephemeral: true});
|
||||
fs;
|
||||
const code = interaction.options.getString('code') as string;
|
||||
let consoleOutput:string = '';
|
||||
|
||||
const deleteEmbedBtn = new Discord.ButtonBuilder().setCustomId('deleteEmbed').setLabel('Delete').setStyle(Discord.ButtonStyle.Danger).setEmoji('🗑️');
|
||||
const deleteEmbedBtn = new Discord.ButtonBuilder().setCustomId('deleteEvalEmbed').setLabel('Delete').setStyle(Discord.ButtonStyle.Danger).setEmoji('🗑️');
|
||||
const deleteEmbedRow = new Discord.ActionRowBuilder<Discord.ButtonBuilder>().addComponents(deleteEmbedBtn);
|
||||
const deleteEmbedCollector = interaction.channel.createMessageComponentCollector({componentType: Discord.ComponentType.Button});
|
||||
deleteEmbedCollector.on('collect', async i=>{
|
||||
if (i.customId === 'deleteEmbed') deleteEmbedCollector.stop();
|
||||
if (i.customId === 'deleteEvalEmbed') deleteEmbedCollector.stop();
|
||||
});
|
||||
|
||||
try {
|
||||
@ -33,13 +31,13 @@ export default {
|
||||
}
|
||||
|
||||
const output = await eval(interaction.options.getBoolean('async') ? `(async()=>{${code}})()` : code);
|
||||
let outVal = output !== undefined ? output : 'No output';
|
||||
let outVal = output;
|
||||
if (outVal && outVal.includes && outVal.includes(client.token)) outVal = outVal.replace(client.token, '*'.repeat(8));
|
||||
const embedFields:Discord.APIEmbedField[] = [
|
||||
{name: 'Input', value: `\`\`\`js\n${code.slice(0,1020)}\n\`\`\``},
|
||||
{name: 'Output', value: `**\`\`\`${UsernameHelper.stripName(outVal === 'string' ? String(outVal) : 'ansi\n'+util.formatWithOptions({depth: 3, colors: true}, '%O', outVal)).slice(0,1012)}\n\`\`\`**`}
|
||||
{name: 'Output', value: `**\`\`\`${UsernameHelper(outVal === 'string' ? String(outVal) : 'ansi\n'+util.formatWithOptions({depth: 3, colors: true}, '%O', outVal)).slice(0,1012)}\n\`\`\`**`}
|
||||
];
|
||||
if (consoleOutput) embedFields.push({name: 'Console', value: `**\`\`\`ansi\n${UsernameHelper.stripName(consoleOutput).slice(0,1008)}\n\`\`\`**`});
|
||||
if (consoleOutput) embedFields.push({name: 'Console', value: `**\`\`\`ansi\n${UsernameHelper(consoleOutput).slice(0,1008)}\n\`\`\`**`});
|
||||
if (typeof output === 'object') {
|
||||
const embed = new client.embed().setColor(client.config.embedColor).addFields(embedFields);
|
||||
interaction.reply({embeds: [embed], components: [deleteEmbedRow]}).catch(()=>(interaction.channel as Discord.TextChannel).send({embeds: [embed], components: [deleteEmbedRow]}));
|
||||
@ -57,7 +55,7 @@ export default {
|
||||
const messagecollector = (interaction.channel as Discord.TextChannel).createMessageCollector({filter, max: 1, time: 60000});
|
||||
messagecollector.on('collect', collected=>{
|
||||
console.log(err)
|
||||
collected.reply(`\`\`\`\n${UsernameHelper.stripName(err.stack)}\n\`\`\``);
|
||||
collected.reply(`\`\`\`\n${UsernameHelper(err.stack)}\n\`\`\``);
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
@ -65,32 +63,31 @@ export default {
|
||||
}
|
||||
},
|
||||
update: async()=>{
|
||||
const SummonAuthentication = createTokenAuth((await TSClient.Token()).octokit);
|
||||
const {token} = await SummonAuthentication();
|
||||
var githubRepo = {owner: 'AnxietyisReal', repo: 'Daggerbot-TS', ref: 'HEAD'};
|
||||
const hammond = await interaction.reply({content: 'Pulling from repository...', fetchReply: true});
|
||||
const octokit = new Octokit({auth: token, timeZone: 'Australia/NSW', userAgent: 'Daggerbot-TS'});
|
||||
const github = {
|
||||
fetchCommit: {
|
||||
msg: await octokit.repos.getCommit(githubRepo).then(x=>x.data.commit.message).catch((err:Error)=>err.message),
|
||||
author: await octokit.repos.getCommit(githubRepo).then(x=>x.data.commit.author.name).catch((err:Error)=>err.message),
|
||||
url: await octokit.repos.getCommit(githubRepo).then(x=>x.data.html_url).catch((err:Error)=>err.message)
|
||||
},
|
||||
fetchChanges: {
|
||||
total: await octokit.repos.getCommit(githubRepo).then(x=>x.data.stats.total.toLocaleString('en-US')).catch((err:Error)=>err.message),
|
||||
addition: await octokit.repos.getCommit(githubRepo).then(x=>x.data.stats.additions.toLocaleString('en-US')).catch((err:Error)=>err.message),
|
||||
deletion: await octokit.repos.getCommit(githubRepo).then(x=>x.data.stats.deletions.toLocaleString('en-US')).catch((err:Error)=>err.message)
|
||||
}
|
||||
const hammondYouIdiot = await interaction.reply({content: 'Pulling...', fetchReply: true});
|
||||
const repoData = await GitHub.RemoteRepository();
|
||||
const commitStats = {
|
||||
total: repoData.stats.total.toLocaleString('en-US'),
|
||||
addition: repoData.stats.additions.toLocaleString('en-US'),
|
||||
deletion: repoData.stats.deletions.toLocaleString('en-US')
|
||||
};
|
||||
exec('git pull',{windowsHide:true},(err:Error,stdout)=>{
|
||||
if (err) hammond.edit(`\`\`\`${UsernameHelper.stripName(err.message)}\`\`\``)
|
||||
else if (stdout.includes('Already up to date')) hammond.edit('I am already up to date with the upstream repository.')
|
||||
else hammond.edit('Compiling TypeScript files...').then(()=>exec('yarn tsc', {windowsHide:true}, (err:Error)=>{
|
||||
if (err) hammond.edit(`\`\`\`${UsernameHelper.stripName(err.message)}\`\`\``)
|
||||
if (interaction.options.getBoolean('restart')) hammond.edit(`[Commit:](<${github.fetchCommit.url}>) **${github.fetchCommit.msg.length === 0 ? 'No commit message' : github.fetchCommit.msg}**\nCommit author: **${github.fetchCommit.author}**\n\n__Commit changes__\nTotal: **${github.fetchChanges.total}**\nAdditions: **${github.fetchChanges.addition}**\nDeletions: **${github.fetchChanges.deletion}**\n\nSuccessfully compiled TypeScript files into JavaScript!\nUptime before restarting: **${FormatTime(client.uptime, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot', {windowsHide:true}));
|
||||
else hammond.edit(`[Commit:](<${github.fetchCommit.url}>) **${github.fetchCommit.msg.length === 0 ? 'No commit message' : github.fetchCommit.msg}**\nCommit author: **${github.fetchCommit.author}**\n\n__Commit changes__\nTotal: **${github.fetchChanges.total}**\nAdditions: **${github.fetchChanges.addition}**\nDeletions: **${github.fetchChanges.deletion}**\n\nSuccessfully compiled TypeScript files into JavaScript!`)
|
||||
}))
|
||||
})
|
||||
const msgBody = MessageTool.concatMessage(
|
||||
`[Commit pulled:](<${repoData.commit.url}>)`,
|
||||
`Message: **${repoData.commit.message.length === 0 ? '*No commit message*' : repoData.commit.message}**`,
|
||||
`Author: **${repoData.commit.author.name}**`,
|
||||
'Changes',
|
||||
`╰ ${commitStats.addition.length > 1 ? `Additions:` : 'Addition:'} **${commitStats.addition}**`,
|
||||
`╰ ${commitStats.deletion.length > 1 ? `Deletions:` : 'Deletion:'} **${commitStats.deletion}**`,
|
||||
`╰ Total: **${commitStats.total}**`
|
||||
);
|
||||
exec('git pull', {windowsHide:true}, (err:Error, stdout)=>{
|
||||
if (err) hammondYouIdiot.edit(`\`\`\`${UsernameHelper(err.message)}\`\`\``);
|
||||
else if (stdout.includes('Already up to date')) hammondYouIdiot.edit('Repository is currently up to date.');
|
||||
else hammondYouIdiot.edit('Running `yarn tsc`...').then(()=>exec('yarn tsc', {windowsHide:true}, (err:Error)=>{
|
||||
if (err) hammondYouIdiot.edit(`\`\`\`${UsernameHelper(err.message)}\`\`\``);
|
||||
else if (interaction.options.getBoolean('restart')) hammondYouIdiot.edit(msgBody + `\nUptime: ${Formatters.timeFormat(process.uptime()*1000, 4, {longNames:true, commas:true})}`).then(()=>process.exit(0));
|
||||
else hammondYouIdiot.edit(msgBody);
|
||||
}));
|
||||
});
|
||||
},
|
||||
presence: ()=>{
|
||||
function convertType(Type?: number){
|
||||
@ -121,24 +118,11 @@ export default {
|
||||
`URL: \`${currentActivities[0].url}\``
|
||||
))
|
||||
},
|
||||
statsgraph: ()=>{
|
||||
client.statsGraph = -(interaction.options.getInteger('number', true));
|
||||
interaction.reply(`Successfully set to \`${client.statsGraph}\`\n*Total data points: **${JSON.parse(fs.readFileSync(`src/database/${interaction.options.getString('server')}PlayerData.json`, {encoding: 'utf8'})).length.toLocaleString()}***`)
|
||||
},
|
||||
logs: ()=>(client.channels.resolve(client.config.mainServer.channels.console) as Discord.TextChannel).send({content: `Uploaded the current console dump as of <t:${Math.round(Date.now()/1000)}:R>`, files: [`${process.env.pm2_home}/logs/Daggerbot-out.log`, `${process.env.pm2_home}/logs/Daggerbot-error.log`]}).then(()=>interaction.reply('It has been uploaded to dev server.')).catch((e:Error)=>interaction.reply(`\`${e.message}\``)),
|
||||
restart: async()=>{
|
||||
const i = await interaction.reply({content: 'Compiling TypeScript files...', fetchReply: true});
|
||||
exec('yarn tsc',{windowsHide:true},(err:Error)=>{
|
||||
if (err) i.edit(`\`\`\`${UsernameHelper.stripName(err.message)}\`\`\``)
|
||||
else i.edit(`Successfully compiled TypeScript files into JavaScript!\nUptime before restarting: **${FormatTime(client.uptime, 3, {commas: true, longNames: true})}**`).then(()=>exec('pm2 restart Daggerbot', {windowsHide:true}))
|
||||
})
|
||||
},
|
||||
file: ()=>interaction.reply({files:[`./src/database/${interaction.options.getString('name')}.json`]}).catch(()=>'Filesize is too large, upload cancelled.'),
|
||||
wake_device: async()=>{
|
||||
const i = await interaction.reply({content: 'Spawning a task...', fetchReply: true});
|
||||
exec(`cd "../../Desktop/System Tools/wakemeonlan" && WakeMeOnLan.exe /wakeup ${interaction.options.getString('name')}`, {windowsHide:true}, (err:Error)=>{
|
||||
if (err) i.edit(UsernameHelper.stripName(err.message))
|
||||
else i.edit('Your device should be awake by now!\n||Don\'t blame me if it isn\'t on.||')
|
||||
const int = await interaction.reply({content: 'Running `yarn tsc`...', fetchReply: true});
|
||||
exec('yarn tsc', {windowsHide:true}, (err:Error)=>{
|
||||
if (err) int.edit(`\`\`\`${UsernameHelper(err.message)}\`\`\``);
|
||||
else int.edit(`Restarting...\nUptime: **${Formatters.timeFormat(process.uptime()*1000, 4, {longNames:true, commas:true})}**`).then(()=>process.exit(0));
|
||||
})
|
||||
},
|
||||
dm: async()=>{
|
||||
@ -148,8 +132,8 @@ export default {
|
||||
member.send(message).then(()=>int.edit(`Successfully sent a DM to **${member.user.username}** with the following message:\n\`\`\`${message}\`\`\``)).catch((e:Error)=>int.edit(`\`${e.message}\``))
|
||||
}
|
||||
} as any)[interaction.options.getSubcommand()]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('dev')
|
||||
.setDescription('Developer commands')
|
||||
.addSubcommand(x=>x
|
||||
@ -163,9 +147,6 @@ export default {
|
||||
.setName('async')
|
||||
.setDescription('Asynchronously execute your code')
|
||||
.setRequired(false)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('logs')
|
||||
.setDescription('Retrieve the logs from host and sends it to dev server'))
|
||||
.addSubcommand(x=>x
|
||||
.setName('restart')
|
||||
.setDescription('Restart the bot for technical reasons'))
|
||||
@ -177,24 +158,6 @@ export default {
|
||||
.setDescription('Restart the bot after pulling from repository')
|
||||
.setRequired(true)
|
||||
))
|
||||
.addSubcommand(x=>x
|
||||
.setName('wake_device')
|
||||
.setDescription('Remotely wake up a device in the same network as the bot')
|
||||
.addStringOption(x=>x
|
||||
.setName('name')
|
||||
.setDescription('Device name')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('statsgraph')
|
||||
.setDescription('Edit the number of data points to pull')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('Server name')
|
||||
.setRequired(true))
|
||||
.addIntegerOption(x=>x
|
||||
.setName('number')
|
||||
.setDescription('Number of data points to pull')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('presence')
|
||||
.setDescription('Update the bot\'s presence')
|
||||
@ -224,13 +187,6 @@ export default {
|
||||
{name: 'Do Not Distrub', value: Discord.PresenceUpdateStatus.DoNotDisturb},
|
||||
{name: 'Invisible', value: Discord.PresenceUpdateStatus.Offline}
|
||||
)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('file')
|
||||
.setDescription('Send a JSON file from database directory on the host')
|
||||
.addStringOption(x=>x
|
||||
.setName('name')
|
||||
.setDescription('JSON filename, don\'t include an extension')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('dm')
|
||||
.setDescription('Reply or send a DM to a member')
|
||||
|
@ -1,45 +1,28 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import FAQStore from '../helpers/FAQStore.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
const CDN =(filename:string)=>'https://cdn.toast-server.net/daggerwin/'+filename+'.png';
|
||||
const verifyFaq = MessageTool.concatMessage(
|
||||
'```ansi',
|
||||
'[34m[1mSteam[0m (Top panel)',
|
||||
'1. Go to your game library and right click on Farming Simulator 22',
|
||||
'2. Click on Properties and navigate to "Installed Files"',
|
||||
'3. Click on "Verify integrity of game files"',
|
||||
'4. Steam will scan your game installation directory and will re-download anything that is corrupted or tampered with.',
|
||||
'',
|
||||
'[37m[1mEpic Games[0m (Bottom panel)',
|
||||
'1. Go to your game library and click on 3 dots (...)',
|
||||
'2. Click on Manage and click on "Verify"',
|
||||
'3. Epic Launcher will scan your game installation directory and will re-download anything that is corrupted or tampered with.',
|
||||
'```'
|
||||
);
|
||||
const youCanGetRole = (role:string, roleEmoji:string)=>`You can get the ${MessageTool.formatMention(client.config.mainServer.roles[role], 'role')} role from <#802283932430106624> by clicking :${roleEmoji}: button on a webhook's message.`;
|
||||
import FAQHelper from '../helpers/FAQHelper.js';
|
||||
export default class FAQ {
|
||||
static run(_client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
({
|
||||
srp: ()=>FAQStore.reply(interaction, null, '[Ballyspring](<https://www.farming-simulator.com/mod.php?mod_id=270745>) is the map that is used in Survival Roleplay S4.\n\n> ℹ️ __Note__\n> The map won\'t look closely like the one in SRP as it is privately edited version of the public map.', null, false),
|
||||
vtcR: ()=>interaction.reply(youCanGetRole('vtcmember', 'truck')+'\n*VTC skin can also be found in <#801975222609641472> as well.*'),
|
||||
mpR: ()=>interaction.reply(youCanGetRole('mpplayer', 'tractor')),
|
||||
ytscam: ()=>FAQStore.reply(interaction, 'Scammers in YouTube comments section', 'If you ever see a comment mentioning a giveaway or anything else, **it\'s a scam!**\nYou should report it to YouTube and move on or ignore it.\nP.S: They\'re on every channel and not just Daggerwin.', CDN('YTScam'), true),
|
||||
steamscam: ()=>FAQStore.reply(interaction, 'Steam account report scam', 'If you received a DM about this, please report it to Discord Moderators or open a [ticket](https://discord.com/channels/468835415093411861/942173932339986472/1054128182468546631)', CDN('SteamScam'), true),
|
||||
fsVerifyGame: ()=>FAQStore.reply(interaction, 'Verifying your game files', `You can verify your game files if you experience any issues with your game.\n${verifyFaq}`, CDN('Steam-Epic-VerifyGamesLocation'), true),
|
||||
fsShader: ()=>FAQStore.reply(interaction, 'Clearing your shader cache folder', 'If your game keeps crashing shortly after opening your game, then the shaders might be an issue.\nTo resolve this, you can go to `Documents/My Games/FarmingSimulator2022` and delete the folder called `shader_cache`', CDN('shader_cache-Location'), true),
|
||||
fsLogfile: ()=>FAQStore.reply(interaction, 'Uploading your log file', 'You can find `log.txt` in `Documents/My Games/FarmingSimulator2022` and upload it into <#596989522395398144> along with your issue, so people can assist you further and help you resolve.', CDN('log_txt-Location'), true),
|
||||
fsDevConsole: ()=>FAQStore.reply(interaction, 'Enabling the development console', 'Head over to `game.xml` in `Documents/My Games/FarmingSimulator2022` and find the section that mentions `<controls>false</controls>` inside development section, change it to `true` then you are good to go!\nFYI: The keybind to open console is \``\u200b\` (backtick).', CDN('enableDevConsole'), true)
|
||||
srp: ()=>FAQHelper.reply(interaction, null, `[Ballyspring](<${FAQHelper.linkMapping.ballyspring}>) is the map that is used in Survival Roleplay S4.\n\n> ℹ️ __Note__\n> The map won't look closely like the one in SRP as it is privately edited version of the public map.`, null, false),
|
||||
vtcR: ()=>interaction.reply(FAQHelper.youCanGetRole('vtcmember', 'truck')+'\n*VTC skin can also be found in <#801975222609641472> as well.*'),
|
||||
mpR: ()=>interaction.reply(FAQHelper.youCanGetRole('mpplayer', 'tractor')),
|
||||
ytscam: ()=>FAQHelper.reply(interaction, 'Scammers in YouTube comments section', 'If you ever see a comment mentioning a giveaway or anything else, **it\'s a scam!**\nYou should report it to YouTube and move on or ignore it.\nP.S: They\'re on every channel and not just Daggerwin.', FAQHelper.CDN('YTScam'), true),
|
||||
steamscam: ()=>FAQHelper.reply(interaction, 'Steam account report scam', `If you received a DM about this, please report it to Discord Moderators or open a [ticket](${FAQHelper.linkMapping.staffTicket})`, FAQHelper.CDN('SteamScam'), true),
|
||||
fsVerifyGame: ()=>FAQHelper.reply(interaction, 'Verifying your game files', `You can verify your game files if you experience any issues with your game.\n${FAQHelper.verifyGameFiles}`, FAQHelper.CDN('Steam-Epic-VerifyGamesLocation'), true),
|
||||
fsShader: ()=>FAQHelper.reply(interaction, 'Clearing your shader cache folder', 'If your game keeps crashing shortly after opening your game, then the shaders might be an issue.\nTo resolve this, you can go to `Documents/My Games/FarmingSimulator2022` and delete the folder called `shader_cache`', FAQHelper.CDN('shader_cache-Location'), true),
|
||||
fsLogfile: ()=>FAQHelper.reply(interaction, 'Uploading your log file', 'You can find `log.txt` in `Documents/My Games/FarmingSimulator2022` and upload it into <#596989522395398144> along with your issue, so people can assist you further and help you resolve.', FAQHelper.CDN('log_txt-Location'), true),
|
||||
fsDevConsole: ()=>FAQHelper.reply(interaction, 'Enabling the development console', 'Head over to `game.xml` in `Documents/My Games/FarmingSimulator2022` and find the section that mentions `<controls>false</controls>` inside development section, change it to `true` then you are good to go!\nFYI: The keybind to open console is \``\u200b\` (backtick).', FAQHelper.CDN('enableDevConsole'), true)
|
||||
} as any)[interaction.options.getString('question', true)]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('faq')
|
||||
.setDescription('List of questions, e.g; log file for FS, YT Scams and etc.')
|
||||
.addStringOption(x=>x
|
||||
.setName('question')
|
||||
.setDescription('What question do you want answered?')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
.setChoices(
|
||||
{ name: 'Survival Roleplay Map', value: 'srp'},
|
||||
{ name: 'Scams in YT comments', value: 'ytscam' },
|
||||
{ name: 'Steam account report scam', value: 'steamscam' },
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
export default class InviteInfo {
|
||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
await client.fetchInvite(interaction.options.getString('code',true).replace(/(https:\/\/|discord.gg\/)/g,'')).then(async inviteData=>
|
||||
await interaction.reply({embeds:[new client.embed()
|
||||
.setColor(client.config.embedColor).setURL(`https://discord.gg/${inviteData.code}`).setTitle(inviteData.guild.name).setDescription(MessageTool.concatMessage(
|
||||
@ -13,12 +13,12 @@ export default {
|
||||
`Channel: \`#${inviteData.channel.name}\``,
|
||||
)).setThumbnail(inviteData.guild.iconURL({size:1024,extension:'webp'})).setImage(inviteData.guild.bannerURL({size:2048,extension:'webp'}))
|
||||
]})).catch((err:Discord.DiscordAPIError)=>interaction.reply(err.message));
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('inviteinfo')
|
||||
.setDescription('View the server data from invite link')
|
||||
.addStringOption(x=>x
|
||||
.setName('code')
|
||||
.setDescription('Discord invite code')
|
||||
.setRequired(true))
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Punish from '../funcs/Punish.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
import Punish from '../components/Punish.js';
|
||||
export default class Kick {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
Punish(client, interaction, 'kick');
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('kick')
|
||||
.setDescription('Boot a member from the server')
|
||||
.setDescription('Kick a member from the server')
|
||||
.addUserOption(x=>x
|
||||
.setName('member')
|
||||
.setDescription('Which member to kick?')
|
||||
@ -15,4 +15,4 @@ export default {
|
||||
.addStringOption(x=>x
|
||||
.setName('reason')
|
||||
.setDescription('Reason for the kick'))
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (client.config.mainServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.mainServer.roles.mpmanager) && !interaction.member.roles.cache.has(client.config.mainServer.roles.bottech) && !interaction.member.roles.cache.has(client.config.mainServer.roles.admin)) return MessageTool.youNeedRole(interaction, 'mpmanager');
|
||||
}
|
||||
const maintenanceMessage = interaction.options.getString('message');
|
||||
const activePlayersChannel = '739084625862852715';
|
||||
const channel = (client.channels.cache.get(activePlayersChannel) as Discord.TextChannel);
|
||||
const embed = new client.embed().setColor(client.config.embedColor).setAuthor({name: interaction.member.displayName, iconURL: interaction.member.displayAvatarURL({size:1024})}).setTimestamp();
|
||||
|
||||
if (channel.permissionsFor(interaction.guildId).has('SendMessages')) {
|
||||
channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: false}, {type: 0, reason: `Locked by ${interaction.member.displayName}`});
|
||||
channel.send({embeds: [embed.setTitle('🔒 Channel locked').setDescription(`**Reason:**\n${maintenanceMessage}`)]});
|
||||
interaction.reply({content: `<#${activePlayersChannel}> has been locked!`, ephemeral: true});
|
||||
} else if (!channel.permissionsFor(interaction.guildId).has('SendMessages')) {
|
||||
channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: true}, {type: 0, reason: `Unlocked by ${interaction.member.displayName}`});
|
||||
channel.send({embeds: [embed.setTitle('🔓 Channel unlocked').setDescription(`**Reason:**\n${maintenanceMessage}`)]});
|
||||
interaction.reply({content: `<#${activePlayersChannel}> has been unlocked!`, ephemeral: true});
|
||||
}
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
.setName('mp-maintenance') // Just a workaround because I am too fucking tired of issues with it, so it gets to be in dedicated file for now. (Also sorry for the swear word, I am just stressed right now.)
|
||||
.setDescription('Toggle maintenance mode for #mp-active-players')
|
||||
.addStringOption(x=>x
|
||||
.setName('message')
|
||||
.setDescription('The message to display in the channel')
|
||||
.setRequired(true))
|
||||
}
|
@ -1,274 +1,326 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import path from 'node:path';
|
||||
import canvas from 'canvas';
|
||||
import PalletLibrary from '../helpers/PalletLibrary.js';
|
||||
import FormatPlayer from '../helpers/FormatPlayer.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {FSData} from '../typings/interfaces';
|
||||
import CanvasBuilder from '../components/CanvasGraph.js';
|
||||
import RanIntoHumor from '../helpers/RanIntoHumor.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import PalletLibrary from '../helpers/PalletLibrary.js';
|
||||
import {FSData} from '../interfaces';
|
||||
import {requestServerData, mpModuleDisabled, refreshTimerSecs, playtimeStat} from '../modules/MPModule.js';
|
||||
|
||||
const serverChoices = [
|
||||
{name: 'Main Server', value: 'mainServer'},
|
||||
{name: 'Second Server', value: 'secondServer'}
|
||||
]
|
||||
async function fetchData(client:TClient, interaction:Discord.ChatInputCommandInteraction, serverName:string):Promise<FSData|Discord.InteractionResponse> {
|
||||
const db = await client.MPServer.findInCache();
|
||||
const {dss} = await requestServerData(client, db.find(x=>x.serverName === serverName));
|
||||
if (!dss) return interaction.reply('Ran into a '+RanIntoHumor+' while trying to retrieve server data, please try again later.');
|
||||
return dss as FSData;
|
||||
}
|
||||
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (client.uptime < 35000) return interaction.reply('I have just restarted, please wait for MPLoop to finish initializing.');
|
||||
const serverSelector = interaction.options.getString('server');
|
||||
if (['468835769092669461', '1149238561934151690'].includes(interaction.channelId) && !MessageTool.isStaff(interaction.member) && ['status', 'players'].includes(interaction.options.getSubcommand())) return interaction.reply('Please use <#739084625862852715> for `/mp status/players` commands to prevent clutter in this channel.').then(()=>setTimeout(()=>interaction.deleteReply(), 6000));
|
||||
|
||||
const database = await client.MPServer.findInCache(interaction.guildId);
|
||||
const endpoint = await fetch(database[serverSelector].ip+'/feed/dedicated-server-stats.json?code='+database[serverSelector].code, {signal: AbortSignal.timeout(7500),headers:{'User-Agent':'Daggerbot - MPdata/undici'}}).then(r=>r.json() as Promise<FSData>);
|
||||
const embed = new client.embed();
|
||||
const logPrefix = 'MPDB';
|
||||
const channels = {
|
||||
activePlayers: '739084625862852715',
|
||||
announcements: '1084864116776251463',
|
||||
mainMpChat: '468835769092669461',
|
||||
mfMpChat: '1149238561934151690',
|
||||
serverInfo: '543494084363288637',
|
||||
}
|
||||
export default class MP {
|
||||
static async autocomplete(client: TClient, interaction: Discord.AutocompleteInteraction<'cached'>) {
|
||||
const serversInCache = await client.MPServer?.findInCache();
|
||||
const filterByActive = serversInCache?.filter(x=>x.isActive)?.map(x=>x.serverName);
|
||||
await interaction?.respond(filterByActive?.map(server=>({name: server, value: server})));
|
||||
}
|
||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>) {
|
||||
if (client.config.botSwitches.mpSys === false) return interaction.reply({embeds: [mpModuleDisabled(client)]});
|
||||
if (client.uptime < refreshTimerSecs) return interaction.reply('MPModule isn\'t initialized yet, please wait a moment and try again.');
|
||||
if ([channels.mainMpChat, channels.mfMpChat].includes(interaction.channelId) && !MessageTool.isStaff(interaction.member) && ['status', 'players'].includes(interaction.options.getSubcommand())) return interaction.reply(`Please use <#${channels.activePlayers}> for \`/mp status/players\` commands to prevent clutter in this channel.`).then(()=>setTimeout(()=>interaction.deleteReply(), 6000));
|
||||
const choiceSelector = interaction.options.getString('server');
|
||||
({
|
||||
players: async()=>{
|
||||
const data = JSON.parse(readFileSync(path.join(`src/database/${client.MPServerCache[serverSelector].name}PlayerData.json`), {encoding: 'utf8'})).slice(client.statsGraph);
|
||||
// handle negative days
|
||||
for (const [i, change] of data.entries()) if (change < 0) data[i] = data[i - 1] || data[i + 1] || 0;
|
||||
const DSS = await fetchData(client, interaction, choiceSelector) as FSData;
|
||||
if (!DSS) return console.log('Endpoint failed - players');
|
||||
|
||||
const first_graph_top = 16;
|
||||
const second_graph_top = 16;
|
||||
const textSize = 40;
|
||||
const img = canvas.createCanvas(1500, 750);
|
||||
const ctx = img.getContext('2d');
|
||||
const graphOrigin = [15, 65];
|
||||
const graphSize = [1300, 630];
|
||||
const nodeWidth = graphSize[0] / (data.length - 1);
|
||||
ctx.fillStyle = '#36393f';
|
||||
ctx.fillRect(0, 0, img.width, img.height);
|
||||
|
||||
// grey horizontal lines
|
||||
ctx.lineWidth = 5;
|
||||
|
||||
const interval_candidates: [number, number, number][] = [];
|
||||
for (let i = 4; i < 10; i++) {
|
||||
const interval = first_graph_top / i;
|
||||
if (Number.isInteger(interval)) {
|
||||
let intervalString = interval.toString();
|
||||
const reference_number = i * Math.max(intervalString.split('').filter(x => x === '0').length / intervalString.length, 0.3) * (['1', '2', '4', '5', '6', '8'].includes(intervalString[0]) ? 1.5 : 0.67)
|
||||
interval_candidates.push([interval, i, reference_number]);
|
||||
}
|
||||
const PDArr = await client.MPServer.fetchPlayerData(choiceSelector);
|
||||
const canvas = await new CanvasBuilder().generateGraph(PDArr.slice(client.statsGraph), 'players');
|
||||
const players:string[] = [];
|
||||
let embedColor:Discord.ColorResolvable;
|
||||
switch (true){
|
||||
case DSS?.slots?.used === DSS?.slots.capacity:
|
||||
embedColor = client.config.embedColorRed;
|
||||
break;
|
||||
case DSS?.slots?.used > 8:
|
||||
embedColor = client.config.embedColorYellow;
|
||||
break;
|
||||
default:
|
||||
embedColor = client.config.embedColorGreen;
|
||||
}
|
||||
const chosen_interval = interval_candidates.sort((a, b) => b[2] - a[2])[0];
|
||||
const previousY: number[] = [];
|
||||
ctx.strokeStyle = '#202225';
|
||||
for (const player of DSS.slots.players.filter(x=>x.isUsed)) players.push(playtimeStat(player))
|
||||
|
||||
for (let i = 0; i <= chosen_interval[1]; i++) {
|
||||
const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]);
|
||||
if (y < graphOrigin[1]) continue;
|
||||
const even = ((i + 1) % 2) === 0;
|
||||
if (even) ctx.strokeStyle = '#2c2f33';
|
||||
ctx.beginPath();
|
||||
ctx.lineTo(graphOrigin[0], y);
|
||||
ctx.lineTo(graphOrigin[0] + graphSize[0], y);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
if (even) ctx.strokeStyle = '#202225';
|
||||
previousY.push(y, i * chosen_interval[0]);
|
||||
}
|
||||
let attachmentName:string = 'MPModule.jpg';
|
||||
await interaction.reply({embeds:[new client.embed()
|
||||
.setTitle(DSS.server?.name.length > 0 ? DSS.server.name : 'Offline')
|
||||
.setColor(embedColor)
|
||||
.setDescription(DSS?.slots?.used < 1 ? '*Nobody is playing*' : players.join('\n\n'))
|
||||
.setImage('attachment://'+attachmentName)
|
||||
.setAuthor({name: `${DSS.slots.used}/${DSS.slots.capacity}`})
|
||||
.setFooter({text: 'Current time: '+`${('0'+Math.floor((DSS?.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((DSS?.server.dayTime/60/1000)%60)).slice(-2)}`})
|
||||
], files: [new client.attachment(canvas.toBuffer(), {name: attachmentName})]})
|
||||
},
|
||||
details: async()=>{
|
||||
const DSS = await fetchData(client, interaction, choiceSelector) as FSData;
|
||||
if (!DSS) return console.log('Endpoint failed - details');
|
||||
const db = await client.MPServer.findInCache();
|
||||
const server = db.find(x=>x.serverName === choiceSelector);
|
||||
|
||||
// 30d mark
|
||||
ctx.setLineDash([8, 16]);
|
||||
ctx.beginPath();
|
||||
const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 60));
|
||||
ctx.lineTo(lastMonthStart, graphOrigin[1]);
|
||||
ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// draw points
|
||||
ctx.lineWidth = 5;
|
||||
|
||||
const gradient = ctx.createLinearGradient(0, graphOrigin[1], 0, graphOrigin[1] + graphSize[1]);
|
||||
gradient.addColorStop(1 / 16, '#e62c3b'); // Red
|
||||
gradient.addColorStop(5 / 16, '#ffea00'); // Yellow
|
||||
gradient.addColorStop(12 / 16, '#57f287'); // Green
|
||||
|
||||
let lastCoords: number[] = [];
|
||||
|
||||
for (let [i, curPC /* current player count */] of data.entries()) {
|
||||
if (curPC < 0) curPC = 0;
|
||||
const x = i * nodeWidth + graphOrigin[0];
|
||||
const y = ((1 - (curPC / second_graph_top)) * graphSize[1]) + graphOrigin[1];
|
||||
const nexPC /* next player count */ = data[i + 1];
|
||||
const prvPC /* previous player count */ = data[i - 1];
|
||||
ctx.strokeStyle = gradient;
|
||||
ctx.beginPath();
|
||||
if (lastCoords.length) ctx.moveTo(lastCoords[0], lastCoords[1]);
|
||||
// if the line being drawn is horizontal, make it go until it has to go down
|
||||
if (y === lastCoords[1]) {
|
||||
let newX = x;
|
||||
for (let j = i + 1; j <= data.length; j++) {
|
||||
if (data[j] === curPC) newX += nodeWidth;
|
||||
else break;
|
||||
}
|
||||
ctx.lineTo(newX, y);
|
||||
} else ctx.lineTo(x, y);
|
||||
lastCoords = [x, y];
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
if (curPC !== prvPC || curPC !== nexPC) { // Ball if vertical different to next or prev point
|
||||
// ball
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, ctx.lineWidth * 1.3, 0, 2 * Math.PI)
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
};
|
||||
}
|
||||
|
||||
// draw text
|
||||
ctx.font = '400 ' + textSize + 'px sans-serif';
|
||||
ctx.fillStyle = 'white';
|
||||
|
||||
// highest value
|
||||
if (!isNaN(previousY.at(-2) as number)) {
|
||||
const maxx = graphOrigin[0] + graphSize[0] + textSize / 2;
|
||||
const maxy = (previousY.at(-2) as number) + (textSize / 3);
|
||||
ctx.fillText((previousY.at(-1) as number).toLocaleString('en-US'), maxx, maxy);
|
||||
}
|
||||
|
||||
// lowest value
|
||||
const lowx = graphOrigin[0] + graphSize[0] + textSize / 2;
|
||||
const lowy = graphOrigin[1] + graphSize[1] + (textSize / 3);
|
||||
ctx.fillText('0 players', lowx, lowy);
|
||||
|
||||
// 30d
|
||||
ctx.fillText('30 min ago', lastMonthStart, graphOrigin[1] - (textSize / 2));
|
||||
|
||||
// time ->
|
||||
const tx = graphOrigin[0] + (textSize / 2);
|
||||
const ty = graphOrigin[1] + graphSize[1] + (textSize);
|
||||
ctx.fillText('time ->', tx, ty);
|
||||
|
||||
const playerData: string[] = [];
|
||||
let Color = client.config.embedColor;
|
||||
if (endpoint.slots.used === endpoint.slots.capacity) Color = client.config.embedColorRed;
|
||||
else if (endpoint.slots.used > 8) Color = client.config.embedColorYellow;
|
||||
else Color = client.config.embedColorGreen;
|
||||
|
||||
for (const player of endpoint.slots.players.filter(x=>x.isUsed)) playerData.push(`**${player.name}${FormatPlayer.decoratePlayerIcons(player)}**\nFarming for ${FormatPlayer.uptimeFormat(player.uptime)}`)
|
||||
|
||||
const slot = `${endpoint.slots.used}/${endpoint.slots.capacity}`;
|
||||
const ingameTime = `${('0'+Math.floor((endpoint.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((endpoint.server.dayTime/60/1000)%60)).slice(-2)}`;
|
||||
interaction.reply({embeds:[new client.embed().setColor(Color).setTitle(endpoint.server.name.length > 0 ? endpoint.server.name : 'Offline').setDescription(endpoint.slots.used < 1 ? '*No players online*' : playerData.join('\n\n')).setImage('attachment://FSStats.png').setAuthor({name:slot}).setFooter({text: 'Current time: '+ingameTime})], files: [new client.attachmentBuilder(img.toBuffer(),{name:'FSStats.png'})]})
|
||||
const dEmbed = new client.embed().setColor(client.config.embedColor).setAuthor({name: 'Crossplay server'}).setDescription(MessageTool.concatMessage(
|
||||
`**Name:** \`${DSS?.server.name.length > 0 ? DSS.server.name : '\u200b'}\``,
|
||||
`**Password:** \`mf4700\``,
|
||||
`**Map:** \`${DSS.server.mapName.length > 0 ? DSS.server.mapName : 'No map'}\``,
|
||||
`**Mods:** [Click here](http://${server.ip}/mods.html) **|** [Direct link](http://${server.ip}/all_mods_download?onlyActive=true)`,
|
||||
'**Filters:** [Click here](https://discord.com/channels/468835415093411861/468835769092669461/926581585938120724)',
|
||||
`Please see <#${channels.serverInfo}> for more additional information and rules.`
|
||||
));
|
||||
if (DSS.server.name.length < 1) dEmbed.setFooter({text: 'Server is currently offline'});
|
||||
await interaction.reply({embeds: [dEmbed]})
|
||||
},
|
||||
status: async()=>{
|
||||
if (!endpoint) return console.log('Endpoint failed - status');
|
||||
try {
|
||||
if (endpoint.server.name.length > 1){
|
||||
interaction.reply({embeds: [embed.setTitle('Status/Details').setColor(client.config.embedColor).addFields(
|
||||
{name: 'Server name', value: `${endpoint?.server.name.length === 0 ? '\u200b' : `\`${endpoint?.server.name}\``}`, inline: true},
|
||||
{name: 'Players', value: `${endpoint.slots.used} out of ${endpoint.slots.capacity}`, inline: true},
|
||||
{name: 'Current map', value: `${endpoint?.server.mapName.length === 0 ? '\u200b' : endpoint.server.mapName}`, inline: true},
|
||||
{name: 'Version', value: `${endpoint?.server.version.length === 0 ? '\u200b' : endpoint.server.version}`, inline: true},
|
||||
{name: 'In-game Time', value: `${('0' + Math.floor((endpoint.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((endpoint.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}
|
||||
)]})
|
||||
} else if (endpoint.server.name.length === 0) interaction.reply('Server is currently offline.')
|
||||
} catch (err){
|
||||
console.log(err)
|
||||
interaction.reply('Ah, you caught a rare one... Please notify <@&'+client.config.mainServer.roles.bottech+'>')
|
||||
}
|
||||
},
|
||||
info: async()=>{
|
||||
if (!endpoint) return console.log('Endpoint failed - info')
|
||||
if (endpoint.server.name.length < 1) embed.setFooter({text: 'Server is currently offline.'})
|
||||
interaction.reply({embeds: [embed.setColor(client.config.embedColor).setDescription(MessageTool.concatMessage(
|
||||
`**Server name**: \`${endpoint?.server.name.length === 0 ? '\u200b' : endpoint.server.name}\``,
|
||||
'**Password:** `mf4700`',
|
||||
'**Crossplay server**',
|
||||
`**Map:** ${endpoint.server.mapName.length < 1 ? 'Null Island' : endpoint.server.mapName}`,
|
||||
`**Mods:** [Click here](${database[serverSelector].ip}/mods.html) **|** [Direct Download](${database[serverSelector].ip}/all_mods_download?onlyActive=true)`,
|
||||
'**Filters:** [Click here](https://discord.com/channels/468835415093411861/468835769092669461/926581585938120724)',
|
||||
'Please see <#543494084363288637> for additional information.'
|
||||
))]});
|
||||
},
|
||||
url: async()=>{
|
||||
if (client.config.mainServer.id == interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.mainServer.roles.mpmanager) && !interaction.member.roles.cache.has(client.config.mainServer.roles.bottech) && !interaction.member.roles.cache.has(client.config.mainServer.roles.admin)) return MessageTool.youNeedRole(interaction, 'mpmanager');
|
||||
}
|
||||
const address = interaction.options.getString('address');
|
||||
if (!address){
|
||||
try {
|
||||
const Url = await client.MPServer.findInCache(interaction.guildId);
|
||||
if (Url[serverSelector].ip && Url[serverSelector].code) return interaction.reply(Url[serverSelector].ip+'/feed/dedicated-server-stats.json?code='+Url[serverSelector].code)
|
||||
} catch(err){
|
||||
Logger.forwardToConsole('error', 'MPDB', err);
|
||||
interaction.reply(`\`\`\`${err}\`\`\``)
|
||||
}
|
||||
} else {
|
||||
if (!address.match(/dedicated-server-stats/)) return interaction.reply('The URL does not match `dedicated-server-stats.xml`');
|
||||
const newURL = address.replace('xml','json').split('/feed/dedicated-server-stats.json?code=');
|
||||
try {
|
||||
Logger.forwardToConsole('log', 'MPDB', `${serverSelector}\'s URL for ${interaction.guild.name} has been updated by ${interaction.member.displayName} (${interaction.member.id})`);
|
||||
const affected = await client.MPServer._content.findByIdAndUpdate({_id: interaction.guildId}, {$set: {[serverSelector]: {ip: newURL[0], code: newURL[1]}}})
|
||||
if (affected) return interaction.reply('URL successfully updated.')
|
||||
} catch (err) {
|
||||
Logger.forwardToConsole('log', 'MPDB', `${serverSelector}\'s URL for ${interaction.guild.name} has been created by ${interaction.member.displayName} (${interaction.member.id})`);
|
||||
await client.MPServer._content.create({_id: interaction.guildId, [serverSelector]: { ip: newURL[0], code: newURL[1] }})
|
||||
.then(()=>interaction.reply('This server doesn\'t have any data in the database, therefore I have created it for you.'))
|
||||
.catch((err:Error)=>interaction.reply(`I got hit by a flying brick while trying to populate the server data:\n\`\`\`${err.message}\`\`\``))
|
||||
}
|
||||
}
|
||||
const DSS = await fetchData(client, interaction, choiceSelector) as FSData;
|
||||
if (!DSS) return console.log('Endpoint failed - status');
|
||||
if (DSS.server.name.length > 0) {
|
||||
await interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).addFields(
|
||||
{name: 'Name', value: `\`${DSS?.server.name}\``, inline: true},
|
||||
{name: 'Players', value: `${DSS.slots.used}/${DSS.slots.capacity}`, inline: true},
|
||||
{name: 'Map', value: DSS?.server.mapName, inline: true}
|
||||
).setFooter({text: `Version: ${DSS?.server.version} | Time: ${`${('0'+Math.floor((DSS?.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((DSS?.server.dayTime/60/1000)%60)).slice(-2)}`}`})]})
|
||||
} else return interaction.reply('Server is currently offline.')
|
||||
},
|
||||
pallets: async()=>{
|
||||
if (!endpoint) return console.log('Endpoint failed - pallets');
|
||||
const filter = endpoint.vehicles.filter(v=>v.type === 'pallet');
|
||||
if (filter.length < 1) return interaction.reply('There are no pallets on the server.');
|
||||
else interaction.reply(`There are currently ${filter.length} pallets on the server. Here\'s the breakdown:\`\`\`\n${Object.values(PalletLibrary(endpoint)).map(t=>`${t.name.padEnd(12)}${t.size}`).join('\n')}\`\`\``)
|
||||
const DSS = await fetchData(client, interaction, choiceSelector) as FSData;
|
||||
if (!DSS) return console.log('Endpoint failed - pallets');
|
||||
const filter = DSS?.vehicles.filter(x=>x.category === 'PALLETS');
|
||||
if (filter.length < 1) return interaction.reply('No pallets found on the server.');
|
||||
else {
|
||||
const getLongestName = Object.entries(PalletLibrary(DSS)).map(([name, _])=>name.length).sort((a,b)=>b-a)[0];
|
||||
await interaction.reply(MessageTool.concatMessage(
|
||||
`There are currently **${filter.length}** pallets on the server. Here\'s the breakdown:\`\`\``,
|
||||
Object.entries(PalletLibrary(DSS)).map(([name, count])=>`${name.padEnd(getLongestName+3)}${count}`).join('\n'),
|
||||
'```'
|
||||
))
|
||||
}
|
||||
},
|
||||
maintenance: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.mpmod) && !interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'mpmod');
|
||||
}
|
||||
|
||||
const reason = interaction.options.getString('reason');
|
||||
const channel = interaction.guild.channels.cache.get(channels.activePlayers) as Discord.TextChannel;
|
||||
const embed = new client.embed().setColor(client.config.embedColor).setAuthor({name: interaction.member.displayName, iconURL: interaction.member.displayAvatarURL({size:1024})}).setTimestamp();
|
||||
|
||||
if (channel.permissionsFor(interaction.guildId).has('SendMessages')) {
|
||||
channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: false}, {type: 0, reason: `Locked by ${interaction.member.displayName}`});
|
||||
channel.send({embeds: [embed.setTitle('🔒 Locked').setDescription(`**Reason:**\n${reason}`)]});
|
||||
interaction.reply({content: `${MessageTool.formatMention(channels.activePlayers, 'channel')} locked successfully`, ephemeral: true});
|
||||
} else {
|
||||
channel.permissionOverwrites.edit(interaction.guildId, {SendMessages: true}, {type: 0, reason: `Unlocked by ${interaction.member.displayName}`});
|
||||
channel.send({embeds: [embed.setTitle('🔓 Unlocked').setDescription(`**Reason:**\n${reason}`)]});
|
||||
interaction.reply({content: `${MessageTool.formatMention(channels.activePlayers, 'channel')} unlocked successfully`, ephemeral: true});
|
||||
}
|
||||
},
|
||||
start: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.mpmod) && !interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'mpmod');
|
||||
}
|
||||
const map_names = interaction.options.getString('map_names', true).split('|');
|
||||
if (map_names.length > 10) return interaction.reply('You can only have up to 10 maps in a poll!');
|
||||
|
||||
const msg = await (interaction.guild.channels.cache.get(channels.announcements) as Discord.TextChannel).send({content: MessageTool.formatMention(client.config.dcServer.roles.mpplayer, 'role'), embeds: [
|
||||
new client.embed()
|
||||
.setColor(client.config.embedColor)
|
||||
.setTitle('Vote for next map!')
|
||||
.setDescription(map_names.map((map,i)=>`${i+1}. **${map}**`).join('\n'))
|
||||
.setFooter({text: `Poll started by ${interaction.user.tag}`, iconURL: interaction.member.displayAvatarURL({extension: 'webp', size: 1024})})
|
||||
], allowedMentions: {parse: ['roles']}});
|
||||
await interaction.reply(`Successfully created a poll in <#${channels.announcements}>`)
|
||||
this.reactionSystem(msg, map_names.length);
|
||||
},
|
||||
end: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.mpmod) && !interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'mpmod');
|
||||
}
|
||||
const msg = await (interaction.guild.channels.cache.get(channels.announcements) as Discord.TextChannel).messages.fetch(interaction.options.getString('message_id', true));
|
||||
if (!msg) return interaction.reply('Message not found, please make sure you have the correct message ID.');
|
||||
|
||||
if (msg.embeds[0].title !== 'Vote for next map!') return interaction.reply('This message is not a poll!');
|
||||
if (msg.embeds[0].footer?.text?.startsWith('Poll ended by')) return interaction.reply('This poll has already ended!');
|
||||
|
||||
const pollResults = Buffer.from(JSON.stringify({
|
||||
map_names: msg.embeds[0].description.split('\n').map(x=>x.slice(3)),
|
||||
votes: msg.reactions.cache.map(x=>x.count)
|
||||
}, null, 2));
|
||||
(client.channels.cache.get(client.config.dcServer.channels.mpmod_chat) as Discord.TextChannel).send({files: [new client.attachment(pollResults, {name: `pollResults-${msg.id}.json`})]});
|
||||
|
||||
msg.edit({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Voting has ended!').setDescription('The next map will be '+msg.embeds[0].description.split('\n')[msg.reactions.cache.map(x=>x.count).indexOf(Math.max(...msg.reactions.cache.map(x=>x.count)))].slice(3)).setFooter({text: `Poll ended by ${interaction.user.tag}`, iconURL: interaction.member.displayAvatarURL({extension: 'webp', size: 1024})})]}).then(()=>msg.reactions.removeAll());
|
||||
await interaction.reply(`Successfully ended the [poll](<https://discord.com/channels/${interaction.guildId}/${channels.announcements}/${msg.id}>) in <#${channels.announcements}>`)
|
||||
},
|
||||
maps: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.mpmod) && !interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'mpmod');
|
||||
}
|
||||
const suggestionPool = await (interaction.guild.channels.cache.get(client.config.dcServer.channels.mpmod_chat) as Discord.TextChannel).messages.fetch('1141293129673232435');
|
||||
interaction.reply({embeds: [suggestionPool.embeds[0]]});
|
||||
}, // Server management group
|
||||
create_server: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.mpmanager) && !interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'mpmanager');
|
||||
}
|
||||
const dedicatedServerStatsURL = interaction.options.getString('dss-url');
|
||||
if (!dedicatedServerStatsURL) {
|
||||
const fetchUrls = await client.MPServer.findInCache();
|
||||
const urlByName = fetchUrls.find(x=>x.serverName === choiceSelector);
|
||||
if (urlByName) return await interaction.reply(`http://${urlByName.ip}/feed/dedicated-server-stats.json?code=${urlByName.code}`);
|
||||
} else {
|
||||
if (!dedicatedServerStatsURL.match(/http.*dedicated-server-stats/)) return interaction.reply(`Improper URL provided, you sent: \`${dedicatedServerStatsURL}\`\nFormat: \`http://<ip>:<port>/feed/dedicated-server-stats.xml?code=<MD5-Code>\`\nI can accept either XML or JSON variants, no need to panic.`);
|
||||
const stripURL = dedicatedServerStatsURL.replace(/http:\/\//, '').replace(/\.xml|\.json/g, '.json').split('/feed/dedicated-server-stats.json?code=')
|
||||
const stripped = {
|
||||
ip: stripURL[0],
|
||||
code: stripURL[1]
|
||||
};
|
||||
|
||||
Logger.console('log', logPrefix, `Updating the IP for "${choiceSelector}" to ${stripped.ip}`)
|
||||
await client.MPServer.addServer(choiceSelector, stripped.ip, stripped.code);
|
||||
await interaction.reply(`**${choiceSelector}**'s entry has been successfully created!`);
|
||||
}
|
||||
},
|
||||
remove_server: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.mpmanager) && !interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'mpmanager');
|
||||
}
|
||||
try {
|
||||
Logger.console('log', logPrefix, `Removing "${choiceSelector}" from database`)
|
||||
await client.MPServer.removeServer(choiceSelector);
|
||||
await interaction.reply(`**${choiceSelector}**'s entry has been successfully removed!`);
|
||||
} catch {
|
||||
Logger.console('log', logPrefix, `Failed to remove "${choiceSelector}", it probably does not exist or something went very wrong`)
|
||||
await interaction.reply(`**${choiceSelector}**'s entry does not exist!`);
|
||||
}
|
||||
},
|
||||
visibility_toggle: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.mpmanager) && !interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'mpmanager');
|
||||
}
|
||||
const toggleFlag = interaction.options.getBoolean('is_active');
|
||||
const submitFlagUpdate = await client.MPServer.toggleServerUsability(choiceSelector, toggleFlag);
|
||||
Logger.console('log', logPrefix, `Toggling isActive flag for ${choiceSelector} to ${toggleFlag}`);
|
||||
let visibilityTxt = `**${choiceSelector}** is now `;
|
||||
if (toggleFlag) {
|
||||
submitFlagUpdate
|
||||
await interaction.reply(visibilityTxt += 'visible to public');
|
||||
} else if (!toggleFlag) {
|
||||
submitFlagUpdate
|
||||
await interaction.reply(visibilityTxt += 'hidden from public');
|
||||
}
|
||||
}
|
||||
})[interaction.options.getSubcommand()]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
.setName('mp')
|
||||
.setDescription('Display MP status and other things')
|
||||
.addSubcommand(x=>x
|
||||
.setName('status')
|
||||
.setDescription('Display server status')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to update')
|
||||
.setRequired(true)
|
||||
.setChoices(...serverChoices)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('players')
|
||||
.setDescription('Display players on server')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to display players for')
|
||||
.setRequired(true)
|
||||
.setChoices(...serverChoices)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('url')
|
||||
.setDescription('View or update the server URL')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to update')
|
||||
.setRequired(true)
|
||||
.setChoices(...serverChoices))
|
||||
.addStringOption(x=>x
|
||||
.setName('address')
|
||||
.setDescription('The URL to the dedicated-server-stats.json file')
|
||||
.setRequired(false)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('info')
|
||||
.setDescription('Display server information')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to display information for')
|
||||
.setRequired(true)
|
||||
.setChoices(...serverChoices)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('pallets')
|
||||
.setDescription('Check total amount of pallets on the server')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to get amount of pallets from')
|
||||
.setRequired(true)
|
||||
.setChoices(...serverChoices)))
|
||||
})[interaction.options.getSubcommand() ?? interaction.options.getSubcommandGroup()]();
|
||||
}
|
||||
private static async reactionSystem(message:Discord.Message, length:number) {
|
||||
const numbersArr = ['1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣','🔟'];
|
||||
await Promise.all(numbersArr.slice(0, length).map(emote=>message.react(emote)));
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('mp')
|
||||
.setDescription('Get information from the FSMP server(s)')
|
||||
.addSubcommand(x=>x
|
||||
.setName('players')
|
||||
.setDescription('Fetches the player list from the requested server')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to fetch the player list from')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('details')
|
||||
.setDescription('Fetches the information about the requested server')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to fetch the information from')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('status')
|
||||
.setDescription('Display the status of the requested server')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to fetch the status from')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('pallets')
|
||||
.setDescription('Fetches how many pallets are on the requested server')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to fetch the pallet count from')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)))
|
||||
.addSubcommandGroup(x=>x
|
||||
.setName('server_mgmnt')
|
||||
.setDescription('Manage the server entries in database, e.g toggling server visiblity, adding/removing, etc.')
|
||||
.addSubcommand(x=>x
|
||||
.setName('create_server')
|
||||
.setDescription('View or update the URL for the requested server')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to create or update')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true))
|
||||
.addStringOption(x=>x
|
||||
.setName('dss-url')
|
||||
.setDescription('The URL to the dedicated-server-stats')
|
||||
.setRequired(false)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('remove_server')
|
||||
.setDescription('Remove the requested server from database')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to be removed')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('visibility_toggle')
|
||||
.setDescription('Toggle isActive flag for the requested server')
|
||||
.addStringOption(x=>x
|
||||
.setName('server')
|
||||
.setDescription('The server to toggle the flag')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true))
|
||||
.addBooleanOption(x=>x
|
||||
.setName('is_active')
|
||||
.setDescription('Whether to hide or show the server from the public view')
|
||||
.setRequired(true))))
|
||||
.addSubcommand(x=>x
|
||||
.setName('maintenance')
|
||||
.setDescription('Toggle the maintenance mode for #mp-active-players channel')
|
||||
.addStringOption(x=>x
|
||||
.setName('reason')
|
||||
.setDescription('The message to send to the channel after toggling')
|
||||
.setRequired(true)))
|
||||
.addSubcommandGroup(x=>x
|
||||
.setName('poll')
|
||||
.setDescription('Create or end a map poll in #mp-announcements channel')
|
||||
.addSubcommand(x=>x
|
||||
.setName('start')
|
||||
.setDescription('Start a map poll')
|
||||
.addStringOption(x=>x
|
||||
.setName('map_names')
|
||||
.setDescription('Map names separated by |\'s, up to 10 maps!')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('end')
|
||||
.setDescription('End a map poll')
|
||||
.addStringOption(x=>x
|
||||
.setName('message_id')
|
||||
.setDescription('Message ID of the poll')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('maps')
|
||||
.setDescription('Fetch the list of maps currently in the suggestion pool')))
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Punish from '../funcs/Punish.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
import Punish from '../components/Punish.js';
|
||||
export default class Mute {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
Punish(client, interaction, 'mute');
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('mute')
|
||||
.setDescription('Mute a member')
|
||||
.addUserOption(x=>x
|
||||
@ -18,4 +18,4 @@ export default {
|
||||
.addStringOption(x=>x
|
||||
.setName('reason')
|
||||
.setDescription('Reason for the mute'))
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,20 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import FormatTime from '../helpers/FormatTime.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (client.uptime < 15500) return interaction.reply('I just restarted, wait 15 seconds and try again.')
|
||||
const msg = await interaction.reply({content: 'Pinging...', fetchReply: true})
|
||||
msg.edit(`API Latency: \`${FormatTime(client.ws.ping, 3, {longNames: false, commas: true})}\`\nBot Latency: \`${FormatTime(msg.createdTimestamp - interaction.createdTimestamp, 3, {longNames: false, commas: true})}\``)
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
import {fetch} from 'undici';
|
||||
import Formatters from '../helpers/Formatters.js';
|
||||
export default class Ping {
|
||||
static async run(client:TClient, interaction:Discord.ChatInputCommandInteraction<'cached'>) {
|
||||
const expectedUptime:number = 16300;
|
||||
if (client.uptime < expectedUptime) return interaction.reply(`I just restarted, try again in <t:${Math.round((Date.now() + expectedUptime - client.uptime) / 1000)}:R>`);
|
||||
const timeOpt = {longNames: false, commas: true};
|
||||
const apiResp = (await fetch('https://discordstatus.com/metrics-display/5k2rt9f7pmny/day.json')).json();
|
||||
const msg = await interaction.reply({content: 'Pinging...', fetchReply: true});
|
||||
msg.edit({content: null, embeds:[new client.embed().setColor('#7e96fd').addFields(
|
||||
{name: 'Discord', value: Formatters.timeFormat(await apiResp.then((data:any)=>data.metrics[0].summary.mean.toFixed(0)), 3, timeOpt), inline: true},
|
||||
{name: 'WebSocket', value: Formatters.timeFormat(client.ws.ping, 3, timeOpt), inline: true}
|
||||
)]})
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('ping')
|
||||
.setDescription('Check latency between bot and Discord API')
|
||||
}
|
||||
|
@ -1,65 +0,0 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import {writeFileSync, existsSync, mkdirSync} from 'node:fs';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (client.config.mainServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.mainServer.roles.mpmod) && !interaction.member.roles.cache.has(client.config.mainServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'mpmod');
|
||||
}
|
||||
const channelId = '1084864116776251463'; // #mp-announcements
|
||||
({
|
||||
start: async()=>{
|
||||
const map_names = interaction.options.getString('map_names', true).split('|');
|
||||
if (map_names.length > 10) return interaction.reply('You can only have up to 10 maps in a poll!');
|
||||
|
||||
const msg = await (interaction.guild.channels.cache.get(channelId) as Discord.TextChannel).send({content: MessageTool.formatMention(client.config.mainServer.roles.mpplayer, 'role'), embeds: [
|
||||
new client.embed()
|
||||
.setColor(client.config.embedColor)
|
||||
.setTitle('Vote for next map!')
|
||||
.setDescription(map_names.map((map,i)=>`${i+1}. **${map}**`).join('\n'))
|
||||
.setFooter({text: `Poll started by ${interaction.user.tag}`, iconURL: interaction.member.displayAvatarURL({extension: 'webp', size: 1024})})
|
||||
], allowedMentions: {parse: ['roles']}});
|
||||
await interaction.reply(`Successfully created a poll in <#${channelId}>`)
|
||||
|
||||
const numbers = ['1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣','🔟'];
|
||||
for (let i = 0; i < map_names.length; i++) await msg.react(numbers[i])
|
||||
},
|
||||
end: async()=>{
|
||||
const msg = await (interaction.guild.channels.cache.get(channelId) as Discord.TextChannel).messages.fetch(interaction.options.getString('message_id', true));
|
||||
if (!msg) return interaction.reply('Message not found, please make sure you have the correct message ID.');
|
||||
|
||||
if (msg.embeds[0].title !== 'Vote for next map!') return interaction.reply('This message is not a poll!');
|
||||
if (msg.embeds[0].footer?.text?.startsWith('Poll ended by')) return interaction.reply('This poll has already ended!');
|
||||
if (msg.reactions.cache.size < 2) return interaction.reply('This poll has not been voted on yet!');
|
||||
|
||||
if (!existsSync('src/database/polls')) mkdirSync('src/database/polls');
|
||||
writeFileSync(`src/database/polls/pollResults-${msg.id}.json`, JSON.stringify({
|
||||
map_names: msg.embeds[0].description.split('\n').map(x=>x.slice(3)),
|
||||
votes: msg.reactions.cache.map(x=>x.count)
|
||||
}, null, 2));
|
||||
(client.channels.cache.get('516344221452599306') as Discord.TextChannel).send({files: [`src/database/polls/pollResults-${msg.id}.json`]});
|
||||
|
||||
msg.edit({embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Voting has ended!').setDescription('The next map will be '+msg.embeds[0].description.split('\n')[msg.reactions.cache.map(x=>x.count).indexOf(Math.max(...msg.reactions.cache.map(x=>x.count)))].slice(3)).setFooter({text: `Poll ended by ${interaction.user.tag}`, iconURL: interaction.member.displayAvatarURL({extension: 'webp', size: 1024})})]});
|
||||
await interaction.reply(`Successfully ended the [poll](<https://discord.com/channels/${interaction.guildId}/${channelId}/${msg.id}>) in <#${channelId}>`)
|
||||
}
|
||||
})[interaction.options.getSubcommand()]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
.setName('poll')
|
||||
.setDescription('Poll system for FSMP server')
|
||||
.addSubcommand(x=>x
|
||||
.setName('start')
|
||||
.setDescription('Start a poll')
|
||||
.addStringOption(x=>x
|
||||
.setName('map_names')
|
||||
.setDescription('Map names separated by |\'s, up to 10 maps')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('end')
|
||||
.setDescription('End a poll')
|
||||
.addStringOption(x=>x
|
||||
.setName('message_id')
|
||||
.setDescription('Message ID of the poll')
|
||||
.setRequired(true)))
|
||||
}
|
70
src/commands/prohibitedWords.ts
Normal file
70
src/commands/prohibitedWords.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default class ProhibitedWords {
|
||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (!MessageTool.isStaff(interaction.member)) return MessageTool.youNeedRole(interaction, 'admin');
|
||||
const word = interaction.options.getString('word');
|
||||
const wordExists = await client.prohibitedWords.findWord(word);
|
||||
({
|
||||
view: async()=>{
|
||||
const pwList = await client.prohibitedWords.getAllWords();
|
||||
interaction.reply({
|
||||
ephemeral: true,
|
||||
content: `There are currently **${pwList.length}** words in the list`,
|
||||
files: [
|
||||
new client.attachment(Buffer.from(JSON.stringify(pwList.map(x=>x.dataValues.word), null, 2)), {name: 'pwDump.json'})
|
||||
]
|
||||
})
|
||||
},
|
||||
add: async()=>{
|
||||
if (wordExists) return interaction.reply({ephemeral: true, content: `\`${word}\` already exists in the list`});
|
||||
else {
|
||||
await client.prohibitedWords.insertWord(word);
|
||||
interaction.reply({ephemeral: true, content: `Successfully added \`${word}\` to the list`});
|
||||
}
|
||||
},
|
||||
remove: async()=>{
|
||||
if (!wordExists) return interaction.reply({ephemeral: true, content: `\`${word}\` does not exist in the list`});
|
||||
else {
|
||||
await client.prohibitedWords.removeWord(word);
|
||||
interaction.reply({ephemeral: true, content: `Successfully removed \`${word}\` from the list`});
|
||||
}
|
||||
},
|
||||
import: async()=>{
|
||||
const file = interaction.options.getAttachment('file', true);
|
||||
if (!file.contentType.match(/application\/json/)) return interaction.reply({ephemeral: true, content: 'This file is not a JSON file!'});
|
||||
const success = await client.prohibitedWords.importWords(file.url);
|
||||
if (success) interaction.reply({ephemeral: true, content: `Successfully imported the list from \`${file.name}\` into the database`});
|
||||
else interaction.reply({ephemeral: true, content: `Failed to import the list from \`${file.name}\` into the database`});
|
||||
}
|
||||
} as any)[interaction.options.getSubcommand()]();
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('pw')
|
||||
.setDescription('Manage the database of prohibited words')
|
||||
.addSubcommand(x=>x
|
||||
.setName('view')
|
||||
.setDescription('View the list of currently banned words'))
|
||||
.addSubcommand(x=>x
|
||||
.setName('add')
|
||||
.setDescription('Add the word to the list')
|
||||
.addStringOption(x=>x
|
||||
.setName('word')
|
||||
.setDescription('Add the specific word to automod\'s prohibitedWords database')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('remove')
|
||||
.setDescription('Remove the word from the list')
|
||||
.addStringOption(x=>x
|
||||
.setName('word')
|
||||
.setDescription('Remove the specific word from automod\'s prohibitedWords database')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('import')
|
||||
.setDescription('Import a JSON file of words into the database')
|
||||
.addAttachmentOption(x=>x
|
||||
.setName('file')
|
||||
.setDescription('The JSON file to import')
|
||||
.setRequired(true)))
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (client.config.mainServer.id === interaction.guildId) {
|
||||
if (!MessageTool.isStaff(interaction.member)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||
}
|
||||
export default class Purge {
|
||||
static async run(_client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (!MessageTool.isStaff(interaction.member)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||
const amount = interaction.options.getInteger('amount') as number;
|
||||
if (amount > 100) return interaction.reply({content: 'Discord API limits purging up to 100 messages.', ephemeral: true})
|
||||
const user = interaction.options.getUser('user');
|
||||
@ -23,8 +21,8 @@ export default {
|
||||
})
|
||||
}
|
||||
await interaction.reply({content: `Successfully purged ${amount} messages.`, ephemeral: true})
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('purge')
|
||||
.setDescription('Purge the amount of messages in this channel')
|
||||
.addIntegerOption(x=>x
|
||||
|
@ -1,20 +1,20 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import path from 'node:path';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import canvas from 'canvas';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (interaction.guildId !== client.config.mainServer.id) return interaction.reply({content: 'This command doesn\'t work in this server.', ephemeral: true});
|
||||
const allData = await client.userLevels._content.find({});
|
||||
import CanvasBuilder from '../components/CanvasGraph.js';
|
||||
export default class Rank {
|
||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (interaction.guildId !== client.config.dcServer.id) return interaction.reply({content: 'This command doesn\'t work in this server.', ephemeral: true});
|
||||
// const allData = await client.userLevels._content.find();
|
||||
const allData = await client.userLevels.fetchEveryone();
|
||||
({
|
||||
view: async()=>{
|
||||
// fetch user or user interaction sender
|
||||
const member = interaction.options.getMember('member') ?? interaction.member as Discord.GuildMember;
|
||||
if (member.user.bot) return interaction.reply('Bots don\'t level up, try viewing the rank data from the users instead.');
|
||||
// information about users progress on level roles
|
||||
const userData = await client.userLevels._content.findById(member.user.id);
|
||||
// const userData = await client.userLevels._content.findById(member.user.id);
|
||||
const userData = await client.userLevels.fetchUser(member.user.id);
|
||||
|
||||
const pronounBool = (you: string, they: string) => { // takes 2 words and chooses which to use based on if user did this command on themself
|
||||
if (interaction.user.id === member.user.id) return you || true;
|
||||
@ -22,150 +22,45 @@ export default {
|
||||
};
|
||||
if (!userData) return interaction.reply(`${pronounBool('You', 'They')} currently don't have a level, send some messages to level up.`)
|
||||
|
||||
const index = allData.sort((a, b) => b.messages - a.messages).map(x => x._id).indexOf(member.id) + 1;
|
||||
const memberDifference = userData.messages - client.userLevels.algorithm(userData.level);
|
||||
const levelDifference = client.userLevels.algorithm(userData.level+1) - client.userLevels.algorithm(userData.level);
|
||||
interaction.reply({embeds: [new client.embed().setColor(member.displayColor).setTitle(`Level: **${userData.level}**\nRank: **${index ? '#' + index : 'last'}**\nProgress: **${memberDifference}/${levelDifference} (${(memberDifference/levelDifference*100).toFixed(2)}%)**\nTotal: **${userData.messages.toLocaleString('en-US')}**`).setThumbnail(member.avatarURL({extension:'png',size:1024}) || member.user.avatarURL({extension:'png',size:1024}) || member.user.defaultAvatarURL).setFooter({text: userData.notificationPing === true ? 'Ping notification enabled' : 'Ping notification disabled'})]})
|
||||
const index = allData.sort((a, b) => b.messages - a.messages).map(x=>x.dataValues.id).indexOf(member.id) + 1;
|
||||
const memberDifference = userData.dataValues.messages - client.userLevels.algorithm(userData.dataValues.level);
|
||||
const levelDifference = client.userLevels.algorithm(userData.dataValues.level+1) - client.userLevels.algorithm(userData.dataValues.level);
|
||||
let ptText = 'Ping toggle ';
|
||||
interaction.reply({embeds: [new client.embed().setColor(member.displayColor).setTitle(`Level: **${userData.dataValues.level}**\nRank: **${index ? '#' + index : 'last'}**\nProgress: **${memberDifference}/${levelDifference} (${(memberDifference/levelDifference*100).toFixed(2)}%)**\nTotal: **${userData.dataValues.messages/* .toLocaleString('en-US') */}**`).setThumbnail(member.avatarURL({extension:'png',size:1024}) || member.user.avatarURL({extension:'png',size:1024}) || member.user.defaultAvatarURL).setFooter({text: userData.pingToggle === true ? ptText += 'enabled' : ptText += 'disabled'})]})
|
||||
},
|
||||
leaderboard: ()=>{
|
||||
const data = JSON.parse(readFileSync(path.join('./src/database/dailyMsgs.json'), 'utf8')).map((x: Array<number>, i: number, a: any) => {
|
||||
leaderboard: async()=>{
|
||||
const data = (await client.dailyMsgs.fetchDays()).map(x=>[x.dataValues.day, x.dataValues.total]).sort((a,b)=>a[0]-b[0]).slice(-60).map((x: number[], i: number, a: any)=>{
|
||||
return x[1] - ((a[i - 1] || [])[1] || x[1])
|
||||
}).slice(1).slice(-60);
|
||||
});
|
||||
if (data.length < 3) return interaction.reply('Not enough data to generate graph.');
|
||||
|
||||
// handle negative days
|
||||
data.forEach((change: number, i: number) => {
|
||||
if (change < 0) data[i] = data[i - 1] || data[i + 1] || 0;
|
||||
});
|
||||
|
||||
const maxValue = Math.max(...data);
|
||||
const maxValueArr = maxValue.toString().split('');
|
||||
|
||||
const first_graph_top = Math.ceil(maxValue * 10 ** (-maxValueArr.length + 1)) * 10 ** (maxValueArr.length - 1);
|
||||
const second_graph_top = Math.ceil(maxValue * 10 ** (-maxValueArr.length + 2)) * 10 ** (maxValueArr.length - 2);
|
||||
const textSize = 32;
|
||||
|
||||
const img = canvas.createCanvas(1200, 600);
|
||||
const ctx = img.getContext('2d');
|
||||
|
||||
const graphOrigin = [25, 50];
|
||||
const graphSize = [1020, 500];
|
||||
const nodeWidth = graphSize[0] / (data.length - 1);
|
||||
ctx.fillStyle = '#36393f'; //'#111111';
|
||||
ctx.fillRect(0, 0, img.width, img.height);
|
||||
|
||||
// grey horizontal lines
|
||||
ctx.lineWidth = 3;
|
||||
|
||||
let interval_candidates = [];
|
||||
for (let i = 4; i < 10; i++) {
|
||||
const interval = first_graph_top / i;
|
||||
if (Number.isInteger(interval)) {
|
||||
let intervalString = interval.toString();
|
||||
const reference_number = i * Math.max(intervalString.split('').filter(x => x === '0').length / intervalString.length, 0.3) * (['1', '2', '4', '5', '6', '8'].includes(intervalString[0]) ? 1.5 : 0.67)
|
||||
interval_candidates.push([interval, i, reference_number]);
|
||||
}
|
||||
}
|
||||
const chosen_interval = interval_candidates.sort((a, b) => b[2] - a[2])[0];
|
||||
let previousY: Array<number> = [];
|
||||
|
||||
ctx.strokeStyle = '#202225'; //'#555B63';
|
||||
if (chosen_interval === undefined) return interaction.reply({content: MessageTool.concatMessage(
|
||||
'No data to display for now. It is also possible that the following either happened:',
|
||||
'- No interval was found for the graph. This is likely due to the fact that there is not enough data to generate a graph.',
|
||||
'- The level system was recently reset.',
|
||||
'- The graph is currently being generated in the background. Please try again in a few minutes.',
|
||||
'If you believe this is a mistake, please contact **Toast** or the **Discord Moderation** team.'
|
||||
), ephemeral: true});
|
||||
for (let i = 0; i <= chosen_interval[1]; i++) {
|
||||
const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]);
|
||||
if (y < graphOrigin[1]) continue;
|
||||
const even = ((i + 1) % 2) === 0;
|
||||
if (even) ctx.strokeStyle = '#2c2f33'; //'#3E4245';
|
||||
ctx.beginPath();
|
||||
ctx.lineTo(graphOrigin[0], y);
|
||||
ctx.lineTo(graphOrigin[0] + graphSize[0], y);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
if (even) ctx.strokeStyle = '#202225'; //'#555B63';
|
||||
previousY = [y, i * chosen_interval[0]];
|
||||
}
|
||||
|
||||
// 30d mark
|
||||
ctx.setLineDash([8, 16]);
|
||||
ctx.beginPath();
|
||||
const lastMonthStart = graphOrigin[0] + (nodeWidth * (data.length - 30));
|
||||
ctx.lineTo(lastMonthStart, graphOrigin[1]);
|
||||
ctx.lineTo(lastMonthStart, graphOrigin[1] + graphSize[1]);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// draw points
|
||||
ctx.strokeStyle = client.config.embedColor as string;
|
||||
ctx.fillStyle = client.config.embedColor as string;
|
||||
ctx.lineWidth = 4;
|
||||
|
||||
function getYCoordinate(value: number) {
|
||||
return ((1 - (value / second_graph_top)) * graphSize[1]) + graphOrigin[1];
|
||||
}
|
||||
|
||||
let lastCoords: Array<number> = [];
|
||||
data.forEach((val: number, i: number) => {
|
||||
ctx.beginPath();
|
||||
if (lastCoords) ctx.moveTo(lastCoords[0], lastCoords[1]);
|
||||
if (val < 0) val = 0;
|
||||
const x = i * nodeWidth + graphOrigin[0];
|
||||
const y = getYCoordinate(val);
|
||||
ctx.lineTo(x, y);
|
||||
lastCoords = [x, y];
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// ball
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, ctx.lineWidth * 1.2, 0, 2 * Math.PI)
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
// draw text
|
||||
ctx.font = '400 ' + textSize + 'px sans-serif';
|
||||
ctx.fillStyle = 'white';
|
||||
|
||||
// highest value
|
||||
ctx.fillText(previousY[1].toLocaleString('en-US'), graphOrigin[0] + graphSize[0] + textSize, previousY[0] + (textSize / 3));
|
||||
|
||||
// lowest value
|
||||
ctx.fillText('0 msgs', graphOrigin[0] + graphSize[0] + textSize, graphOrigin[1] + graphSize[1] + (textSize / 3));
|
||||
|
||||
// 30d
|
||||
ctx.fillText('30d ago', lastMonthStart, graphOrigin[1] - (textSize / 3));
|
||||
|
||||
// time ->
|
||||
ctx.fillText('time ->', graphOrigin[0] + (textSize / 2), graphOrigin[1] + graphSize[1] + (textSize));
|
||||
|
||||
interaction.reply({embeds: [
|
||||
new client.embed().setTitle('Ranking leaderboard')
|
||||
.setDescription(`Level System was created **${Math.floor((Date.now()-client.config.LRSstart)/1000/60/60/24)}** days ago. Since then, a total of **${allData.reduce((a, b)=>a+b.messages, 0).toLocaleString('en-US')}** messages have been sent in this server.`)
|
||||
.addFields({name: 'Top users by messages sent:', value: allData.sort((a,b)=>b.messages - a.messages).slice(0,10).map((x,i)=>`${i+1}. <@${x._id}>: ${x.messages.toLocaleString('en-US')}`).join('\n')})
|
||||
.setImage('attachment://dailymsgs.png').setColor(client.config.embedColor)
|
||||
.setFooter({text: 'Graph updates daily.'})
|
||||
], files: [new client.attachmentBuilder(img.toBuffer(),{name: 'dailymsgs.png'})]})
|
||||
const graph = await new CanvasBuilder().generateGraph(data, 'leaderboard');
|
||||
interaction.reply({
|
||||
embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Leaderboard')
|
||||
.setDescription(MessageTool.concatMessage(
|
||||
`Level System was created **${Math.floor((Date.now()-client.config.LRSstart)/1000/60/60/24)}** days ago.`,
|
||||
`Since then, a total of **${allData.reduce((a, b)=>a+b.messages, 0).toLocaleString('en-US')}** messages have been sent in this server.`
|
||||
)).addFields({
|
||||
name: 'Top users sorted by messages sent:',
|
||||
value: allData.sort((a,b)=>b.messages - a.messages).slice(0,15).map((x,i)=>`${i+1}. <@${x.dataValues.id}>: ${x.messages.toLocaleString('en-US')}`).join('\n')
|
||||
}).setImage('attachment://dailyMessages.jpg').setFooter({text: 'Graph updates daily'})],
|
||||
files: [new client.attachment(graph.toBuffer(),{name: 'dailyMessages.jpg'})]
|
||||
})
|
||||
},
|
||||
notification: async()=>{
|
||||
const findUserInMongo = await client.userLevels._content.findById(interaction.user.id);
|
||||
const findUserInDatabase = await client.userLevels.fetchUser(interaction.user.id);
|
||||
const textDeco = ' be pinged for level-up notification in the future.'
|
||||
if (!findUserInMongo.notificationPing) {
|
||||
await findUserInMongo.updateOne({_id: interaction.user.id, notificationPing: true})
|
||||
if (!findUserInDatabase.pingToggle) {
|
||||
await findUserInDatabase.update({pingToggle: true}, {where: {id: interaction.user.id}})
|
||||
interaction.reply({content: 'You will'+textDeco, ephemeral: true})
|
||||
} else if (findUserInMongo.notificationPing) {
|
||||
await findUserInMongo.updateOne({_id: interaction.user.id, notificationPing: false})
|
||||
} else if (findUserInDatabase.pingToggle) {
|
||||
await findUserInDatabase.update({pingToggle: false}, {where: {id: interaction.user.id}})
|
||||
interaction.reply({content: 'You won\'t'+textDeco, ephemeral: true})
|
||||
}
|
||||
}
|
||||
} as any)[interaction.options.getSubcommand()]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('rank')
|
||||
.setDescription('Level system')
|
||||
.addSubcommand(x=>x
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
export default class RoleInfo {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
const role = interaction.options.getRole('role') as Discord.Role;
|
||||
const permissions = role.permissions.toArray();
|
||||
interaction.reply({embeds: [new client.embed().setColor(role.color || '#fefefe').setThumbnail(role?.iconURL()).setTitle(`Role Info: ${role.name}`).addFields(
|
||||
@ -10,13 +10,13 @@ export default {
|
||||
{name: '🔹 Creation Date', value: `<t:${Math.round(role.createdTimestamp/1000)}>\n<t:${Math.round(role.createdTimestamp/1000)}:R>`, inline: true},
|
||||
{name: '🔹 Misc', value: `Hoist: \`${role.hoist}\`\nMentionable: \`${role.mentionable}\`\nPosition: \`${role.position}\` from bottom\nMembers: \`${role.members.size}\`\n${role.members.size < 21 ? role.members.map((e:Discord.GuildMember)=>`**${e.user.username}**`).join('\n') || '' : ''}`, inline: true},
|
||||
{name: '🔹 Permissions', value: `${permissions.includes('Administrator') ? ['Administrator'] : permissions.join(', ').replace(/([a-z])([A-Z])/g, '$1 $2') || 'No permissions'}`, inline: true}
|
||||
)]})// https://stackoverflow.com/a/15343790 - For anonymous programmer, you know who I am talking to. You're welcome...
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
)]})
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('roleinfo')
|
||||
.setDescription('View information about the selected role')
|
||||
.addRoleOption(x=>x
|
||||
.setName('role')
|
||||
.setDescription('Role name to view information')
|
||||
.setRequired(true))
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Punish from '../funcs/Punish.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
import Punish from '../components/Punish.js';
|
||||
export default class Softban {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
Punish(client, interaction, 'softban');
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('softban')
|
||||
.setDescription('Softban a member from the server')
|
||||
.addUserOption(x=>x
|
||||
@ -15,4 +15,4 @@ export default {
|
||||
.addStringOption(x=>x
|
||||
.setName('reason')
|
||||
.setDescription('Reason for the softban'))
|
||||
}
|
||||
}
|
||||
|
@ -1,94 +1,83 @@
|
||||
interface CommitHashes {
|
||||
localHash: string,
|
||||
remoteHash: string
|
||||
}
|
||||
import Discord from 'discord.js';
|
||||
import pkg from 'typescript';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import FormatBytes from '../helpers/FormatBytes.js';
|
||||
import FormatTime from '../helpers/FormatTime.js';
|
||||
import si from 'systeminformation';
|
||||
import TClient from '../client.js';
|
||||
import TSClient from '../helpers/TSClient.js';
|
||||
import Formatters from '../helpers/Formatters.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import GitHub from '../helpers/GitHub.js';
|
||||
import si from 'systeminformation';
|
||||
import os from 'node:os';
|
||||
import {Octokit} from '@octokit/rest';
|
||||
import {createTokenAuth} from '@octokit/auth-token';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {Worker} from 'node:worker_threads';
|
||||
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
|
||||
const workerThread = new Worker(new URL('../helpers/CommitHashes.js', import.meta.url));
|
||||
const hashData = await new Promise<CommitHashes>(resolve=>workerThread.on('message', (data:CommitHashes)=>resolve(data)));
|
||||
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
const waitForData = await interaction.reply({content: '<a:sakjdfsajkfhsdjhjfsa:1065342869428252743>', fetchReply:true})
|
||||
const cpu = await si.cpu();
|
||||
const ram = await si.mem();
|
||||
const osInfo = await si.osInfo();
|
||||
const currentLoad = await si.currentLoad();
|
||||
|
||||
const columns = ['Command name', 'Count'];
|
||||
const includedCommands = client.commands.filter(x=>x.uses).sort((a,b)=>b.uses - a.uses);
|
||||
if (includedCommands.size === 0) return interaction.reply(`No commands have been used yet.\nUptime: **${FormatTime(client.uptime, 3, {longNames: true, commas: true})}**`);
|
||||
const nameLength = Math.max(...includedCommands.map(x=>x.command.data.name.length), columns[0].length) + 2;
|
||||
const amountLength = Math.max(...includedCommands.map(x=>x.uses.toString().length), columns[1].length) + 1;
|
||||
const rows = [`${columns[0] + ' '.repeat(nameLength - columns[0].length)}|${' '.repeat(amountLength - columns[1].length) + columns[1]}\n`, '-'.repeat(nameLength) + '-'.repeat(amountLength) + '\n'];
|
||||
includedCommands.forEach(command=>{
|
||||
const name = command.command.data.name;
|
||||
const count = command.uses.toString();
|
||||
rows.push(`${name + ' '.repeat(nameLength - name.length)}${' '.repeat(amountLength - count.length) + count}\n`);
|
||||
});
|
||||
const embed = new client.embed().setColor(client.config.embedColor).setTitle('Statistics: Command Usage')
|
||||
.setDescription(MessageTool.concatMessage(
|
||||
'List of commands that have been used in this session, ordered by amount of use. Table contains command name and amount of uses.',
|
||||
`Total amount of commands used in this session: ${client.commands.filter(x=>x.uses).map(x=>x.uses).reduce((a,b)=>a+b, 0)}`
|
||||
import ts from 'typescript';
|
||||
import {readFileSync} from 'fs';
|
||||
export default class Statistics {
|
||||
static async run(client:TClient, interaction:Discord.ChatInputCommandInteraction<'cached'>) {
|
||||
const initialMsg = await interaction.reply({content: '<a:sakjdfsajkfhsdjhjfsa:1065342869428252743>', fetchReply:true});
|
||||
const repoData = await GitHub.LocalRepository();
|
||||
const embed = new client.embed().setColor(client.config.embedColor).setTitle('Statistics').setDescription(MessageTool.concatMessage(
|
||||
'This is a list of commands ordered by their names and how many times they had been used in this session.',
|
||||
'Underneath is a list of main dependencies and their versions as well as the bot/host statistics.'
|
||||
));
|
||||
if (rows.join('').length > 1024){
|
||||
let fieldValue = '';
|
||||
rows.forEach(row=>{
|
||||
if (fieldValue.length + row.length > 1024){
|
||||
embed.addFields({name: '\u200b', value: `\`\`\`\n${fieldValue}\`\`\``});
|
||||
fieldValue = row;
|
||||
} else fieldValue += row
|
||||
});
|
||||
embed.addFields({name: '\u200b', value: `\`\`\`\n${fieldValue}\`\`\``});
|
||||
} else embed.addFields({name: '\u200b', value: `\`\`\`\n${rows.join('')}\`\`\``});
|
||||
|
||||
const SummonAuthentication = createTokenAuth((await TSClient.Token()).octokit);
|
||||
const {token} = await SummonAuthentication();
|
||||
let githubRepo = {owner: 'AnxietyisReal', repo: 'Daggerbot-TS', ref: 'HEAD'};
|
||||
const octokit = new Octokit({auth: token, timeZone: 'Australia/NSW', userAgent: 'Daggerbot-TS'});
|
||||
const github = {
|
||||
remoteCommit: await octokit.repos.getCommit({...githubRepo, ref: hashData.remoteHash}),
|
||||
localCommit: await octokit.repos.getCommit({...githubRepo, ref: hashData.localHash}),
|
||||
}
|
||||
const systemInfo = {
|
||||
cpu: await si.cpu(),
|
||||
mem: await si.mem(),
|
||||
osInfo: await si.osInfo(),
|
||||
currLoad: await si.currentLoad()
|
||||
};
|
||||
|
||||
const col = ['Command', 'Uses'];
|
||||
const cmdUses = client.commands.filter(x=>x.uses).sort((a,b)=>b.uses - a.uses);
|
||||
|
||||
const nameLen = Math.max(...cmdUses.map(x=>x.command.data.name.length), col[0].length) + 2;
|
||||
const usesLen = Math.max(...cmdUses.map(x=>x.uses.toString().length), col[1].length) + 1;
|
||||
|
||||
const rows = [`${col[0] + ' '.repeat(nameLen-col[0].length)}|${' '.repeat(usesLen-col[1].length) + col[1]}\n`, '-'.repeat(nameLen) + '-'.repeat(usesLen) + '\n'];
|
||||
cmdUses.forEach(cmd=>{
|
||||
const name = cmd.command.data.name;
|
||||
const uses = cmd.uses.toString();
|
||||
rows.push(`${name+' '.repeat(nameLen-name.length)}${' '.repeat(usesLen-uses.length)+uses}\n`);
|
||||
});
|
||||
if (rows.join('').length > 1024) {
|
||||
let field = '';
|
||||
rows.forEach(r=>{
|
||||
if (field.length+r.length > 1024) {
|
||||
embed.addFields({name: '\u200b', value: `\`\`\`\n${field}\`\`\``});
|
||||
field = r;
|
||||
}
|
||||
});
|
||||
embed.addFields({name: '\u200b', value: `\`\`\`\n${field}\`\`\``});
|
||||
} else embed.addFields({name: '\u200b', value: `\`\`\`\n${rows.join('')}\`\`\``});
|
||||
|
||||
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
embed.addFields(
|
||||
{
|
||||
name: '> __Repository__', value: MessageTool.concatMessage(
|
||||
`**Local:** [${hashData.localHash}](${github.localCommit.data.html_url})`,
|
||||
`**Remote:** [${hashData.remoteHash}](${github.remoteCommit.data.html_url})`
|
||||
)
|
||||
},
|
||||
{name: '> __Dependencies__', value: MessageTool.concatMessage(
|
||||
`**TypeScript:** ${pkg.version}`,
|
||||
`**NodeJS:** ${process.version}`,
|
||||
`**DiscordJS:** ${Discord.version}`,
|
||||
`**Yarn:** ${packageJson.packageManager.slice(5)}`
|
||||
{name: '🔹 *Dependencies*', value: MessageTool.concatMessage(
|
||||
`>>> **Yarn:** ${pkg.packageManager.split('@')[1].split('+')[0]}`,
|
||||
`**Node.js:** ${process.version.slice(1)}`,
|
||||
`**Discord.js:** ${pkg.dependencies['discord.js']}`,
|
||||
`**TypeScript:** ${ts.version}`,
|
||||
`**Postgres:** ${pkg.dependencies.pg}`,
|
||||
`**Redis:** ${pkg.dependencies.redis}`
|
||||
)},
|
||||
{name: '> __Host__', value: MessageTool.concatMessage(
|
||||
`**Operating System:** ${osInfo.distro + ' ' + osInfo.release}`,
|
||||
`**CPU:** ${cpu.manufacturer} ${cpu.brand}`,
|
||||
`**Memory:** ${FormatBytes(ram.used)}/${FormatBytes(ram.total)}`,
|
||||
`**Process:** ${FormatBytes(process.memoryUsage().heapUsed)}/${FormatBytes(process.memoryUsage().heapTotal)}`,
|
||||
`**Load Usage:**\nUser: ${currentLoad.currentLoadUser.toFixed(1)}%\nSystem: ${currentLoad.currentLoadSystem.toFixed(1)}%`,
|
||||
`**Uptime:**\nHost: ${FormatTime((os.uptime()*1000), 2, {longNames: true, commas: true})}\nBot: ${FormatTime(client.uptime, 2, {commas: true, longNames: true})}`
|
||||
{name: '🔹 *Host*', value: MessageTool.concatMessage(
|
||||
`>>> **OS:** ${systemInfo.osInfo.distro} ${systemInfo.osInfo.release}`,
|
||||
`**CPU:** ${systemInfo.cpu.manufacturer} ${systemInfo.cpu.brand} ∙ ${systemInfo.cpu.speed} GHz`,
|
||||
'**RAM**',
|
||||
`╰ **Host:** ${this.progressBar(systemInfo.mem.used, systemInfo.mem.total)} (${Formatters.byteFormat(systemInfo.mem.used)}/${Formatters.byteFormat(systemInfo.mem.total)})`,
|
||||
`╰ **Bot:** ${this.progressBar(process.memoryUsage().heapUsed, process.memoryUsage().heapTotal)} (${Formatters.byteFormat(process.memoryUsage().heapUsed)}/${Formatters.byteFormat(process.memoryUsage().heapTotal)})`,
|
||||
'**Uptime**',
|
||||
`╰ **Host:** ${Formatters.timeFormat(os.uptime()*1000, 3, {longNames: true, commas: true})}`,
|
||||
`╰ **Bot:** ${Formatters.timeFormat(process.uptime()*1000, 3, {longNames: true, commas: true})}`,
|
||||
'**Load Usage**',
|
||||
`╰ **User:** ${this.progressBar(systemInfo.currLoad.currentLoadUser, 100)} (${systemInfo.currLoad.currentLoadUser.toFixed(2)}%)`,
|
||||
`╰ **Sys:** ${this.progressBar(systemInfo.currLoad.currentLoadSystem, 100)} (${systemInfo.currLoad.currentLoadSystem.toFixed(2)}%)`
|
||||
)}
|
||||
);
|
||||
waitForData.edit({content:null,embeds:[embed]}).then(x=>x.edit({embeds:[new client.embed(x.embeds[0].data).setFooter({text: `Load time: ${FormatTime(x.createdTimestamp - interaction.createdTimestamp, 2, {longNames: true, commas: true})}`})]}))
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
).setFooter({text: `Version: ${repoData.hash.slice(0,7)} ∙ ${repoData.message}`});
|
||||
initialMsg.edit({content: null, embeds: [embed]});
|
||||
}
|
||||
private static progressBar(used:number, total:number):string {
|
||||
const length:number = 10;
|
||||
const percent = used/total;
|
||||
const bar = '▓'.repeat(Math.round(percent*length)) + '░'.repeat(length-Math.round(percent*length));
|
||||
return `${bar} ${Math.round(percent*100)}%`;
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('statistics')
|
||||
.setDescription('See a list of commands ordered by their usage or host stats')
|
||||
.setDescription('List of commands used in current session and host statistics')
|
||||
}
|
||||
|
@ -1,106 +1,77 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import HookMgr from '../funcs/HookManager.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
const replyInDM = interaction.options.getString('message');
|
||||
const suggestionIDReply = interaction.options.getString('id');
|
||||
const suggestionID = (Math.random() + 1).toString(36).substring(5);
|
||||
const userid = (await client.suggestion._content.findById(suggestionIDReply))?.user._id;
|
||||
const theirIdea = (await client.suggestion._content.findById(suggestionIDReply))?.idea;
|
||||
const timeFormatting = client.dayjs().format('DD/MM/YY h:mm A');
|
||||
const stateChanged = 'Suggestion state has been successfully updated and DM is sent.';
|
||||
const dmFail = `Failed to send a DM to ${MessageTool.formatMention(userid, 'user')}, they possibly have it turned off or blocked me.\nSuggestion ID: **${suggestionIDReply}**`;
|
||||
import HookMgr from '../components/HookManager.js';
|
||||
export default class Suggest {
|
||||
static async run(client:TClient, interaction:Discord.ChatInputCommandInteraction<'cached'>) {
|
||||
const idVal = interaction.options.getInteger('id');
|
||||
({
|
||||
your: async()=>{
|
||||
const suggestionText = interaction.options.getString('suggestion');
|
||||
const suggestionImage = interaction.options.getAttachment('image');
|
||||
const notifEmbed = new client.embed()
|
||||
.setColor(client.config.embedColor)
|
||||
.setTitle(`Suggestion ID: ${suggestionID}`)
|
||||
.setAuthor({name: interaction.user.username, iconURL: interaction.user.avatarURL({size: 256})})
|
||||
.setFooter({text: `Timestamp: ${timeFormatting}`})
|
||||
.setDescription(MessageTool.concatMessage(
|
||||
'> **Suggestion:**',
|
||||
suggestionText
|
||||
));
|
||||
if (suggestionImage) notifEmbed.setImage(suggestionImage.url);
|
||||
HookMgr.send(client, 'bot_suggestions', '1079621523561779272', {embeds:[notifEmbed], username: `${client.user.username} Suggestions`, avatarURL: client.user.avatarURL({size:256})}).catch(e=>{
|
||||
console.log(e.message);
|
||||
interaction.reply({content: 'Failed to send suggestion, try again later.', ephemeral: true})
|
||||
});
|
||||
await client.suggestion._content.create({_id: suggestionID, idea: suggestionText, user: {_id: interaction.user.id, name: interaction.user.username}, state: 'Pending'});
|
||||
interaction.reply({content: `Suggestion sent, here is your suggestion ID to take note of it: \`${suggestionID}\``, ephemeral: true})
|
||||
create: async()=>{
|
||||
const suggestion = interaction.options.getString('suggestion', true);
|
||||
const newSugg = await client.suggestions.create(interaction.user.id, suggestion);
|
||||
this.newWebhookMessage(client, newSugg.dataValues.id, suggestion, interaction.user.username);
|
||||
return interaction.reply({content: `Your suggestion has been sent to bot developers. \`#${newSugg.dataValues.id}\``, ephemeral: true});
|
||||
},
|
||||
approve: async()=>{
|
||||
if (client.config.mainServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.mainServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'bottech');
|
||||
delete: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'bottech');
|
||||
}
|
||||
if ((await client.suggestion._content.findById(suggestionIDReply)).state === 'Rejected') return interaction.reply({content: 'This suggestion\'s state is locked and cannot be modified.', ephemeral: true});
|
||||
(await client.users.fetch(userid)).send({embeds: [new client.embed()
|
||||
.setColor(client.config.embedColorGreen)
|
||||
.setAuthor({name: interaction.user.username, iconURL: interaction.user.avatarURL({size: 256})})
|
||||
.setTitle('Your suggestion has been approved.')
|
||||
.setDescription(`> **Your suggestion:**\n${theirIdea}\n> **Their message:**\n${replyInDM}`)
|
||||
.setFooter({text: `Timestamp: ${timeFormatting} | Suggestion ID: ${suggestionIDReply}`})
|
||||
]}).catch((err:Discord.DiscordjsErrorCodes)=>{if (err) return (client.channels.resolve('1040018521746325586') as Discord.TextChannel).send(dmFail)});
|
||||
await client.suggestion._content.findByIdAndUpdate(suggestionIDReply, {state: 'Approved'});
|
||||
return interaction.reply({embeds:[new client.embed().setColor(client.config.embedColorGreen).setTitle(`Suggestion approved | ${suggestionIDReply}`).setDescription(stateChanged)]});
|
||||
const sugg = await this.deleteSuggestion(client, idVal);
|
||||
if (sugg) return interaction.reply(`Suggestion \`#${idVal}\` has been deleted.`);
|
||||
else return interaction.reply(`Suggestion \`#${idVal}\` does not exist.`);
|
||||
},
|
||||
reject: async()=>{
|
||||
if (client.config.mainServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.mainServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'bottech');
|
||||
update: async()=>{
|
||||
if (client.config.dcServer.id === interaction.guildId) {
|
||||
if (!interaction.member.roles.cache.has(client.config.dcServer.roles.bottech)) return MessageTool.youNeedRole(interaction, 'bottech');
|
||||
}
|
||||
if ((await client.suggestion._content.findById(suggestionIDReply)).state === 'Approved') return interaction.reply({content: 'This suggestion\'s state is locked and cannot be modified.', ephemeral: true});
|
||||
(await client.users.fetch(userid)).send({embeds: [new client.embed()
|
||||
.setColor(client.config.embedColorRed)
|
||||
.setAuthor({name: interaction.user.username, iconURL: interaction.user.avatarURL({size: 256})})
|
||||
.setTitle('Your suggestion has been rejected.')
|
||||
.setDescription(`> **Your suggestion:**\n${theirIdea}\n> **Their message:**\n${replyInDM}`)
|
||||
.setFooter({text: `Timestamp: ${timeFormatting} | Suggestion ID: ${suggestionIDReply}`})
|
||||
]}).catch((err:Discord.DiscordjsErrorCodes)=>{if (err) return (client.channels.resolve('1040018521746325586') as Discord.TextChannel).send(dmFail)});
|
||||
await client.suggestion._content.findByIdAndUpdate(suggestionIDReply, {state: 'Rejected'});
|
||||
return interaction.reply({embeds:[new client.embed().setColor(client.config.embedColorRed).setTitle(`Suggestion rejected | ${suggestionIDReply}`).setDescription(stateChanged)]});
|
||||
const status = interaction.options.getString('status', true);
|
||||
await this.updateSuggestion(client, idVal, status as 'Accepted'|'Rejected');
|
||||
client.users.fetch((await client.suggestions.fetchById(idVal)).dataValues.userid).then(x=>x.send(`Your suggestion \`#${idVal}\` has been updated to \`${status}\` by **${interaction.user.username}**`)).catch(()=>interaction.channel.send(`Unable to send DM to user of suggestion \`#${idVal}\``))
|
||||
return await interaction.reply(`Suggestion \`#${idVal}\` has been updated to \`${status}\`.`);
|
||||
}
|
||||
} as any)[interaction.options.getSubcommand()]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static async updateSuggestion(client:TClient, id:number, status: 'Accepted'|'Rejected') {
|
||||
return await client.suggestions.updateStatus(id, status);
|
||||
}
|
||||
static async deleteSuggestion(client:TClient, id:number) {
|
||||
return await client.suggestions.delete(id);
|
||||
}
|
||||
static newWebhookMessage(client:TClient, id:number, suggestion:string, username:string) {
|
||||
const hook = new HookMgr(client, 'bot_suggestions', '1079621523561779272');
|
||||
if (hook) return hook.send({embeds: [new client.embed().setColor(client.config.embedColor).setTitle(`Suggestion #${id}`).setAuthor({name: username}).setDescription(`\`\`\`${suggestion}\`\`\``)]});
|
||||
else throw new Error('[SUGGESTION-HOOK] Provided webhook cannot be fetched, not sending message.')
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('suggest')
|
||||
.setDescription('Want to suggest ideas/thoughts to bot techs? Suggest it here')
|
||||
.setDescription('Want to suggest something to the bot devs? You can do so!')
|
||||
.addSubcommand(x=>x
|
||||
.setName('your')
|
||||
.setDescription('What do you want to suggest?')
|
||||
.setName('create')
|
||||
.setDescription('Create a new suggestion for your idea')
|
||||
.addStringOption(x=>x
|
||||
.setName('suggestion')
|
||||
.setDescription('Suggest something to bot techs. (You will be DM\'d by bot if your idea was approved/rejected)')
|
||||
.setMaxLength(1024)
|
||||
.setRequired(true))
|
||||
.addAttachmentOption(x=>x
|
||||
.setName('image')
|
||||
.setDescription('If your idea seems complicated or prefer to show what your idea may look like then attach the image.')))
|
||||
.setDescription('Your precious idea')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('approve')
|
||||
.setDescription('[Bot Tech] Approve the suggestion sent by the user')
|
||||
.addStringOption(x=>x
|
||||
.setName('delete')
|
||||
.setDescription('Delete a suggestion (Bot Tech only)')
|
||||
.addIntegerOption(x=>x
|
||||
.setName('id')
|
||||
.setDescription('User\'s suggestion ID')
|
||||
.setRequired(true))
|
||||
.addStringOption(x=>x
|
||||
.setName('message')
|
||||
.setDescription('(Optional) Include a message with your approval')
|
||||
.setRequired(true)
|
||||
.setMaxLength(256)))
|
||||
.setDescription('The ID of the suggestion')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('reject')
|
||||
.setDescription('[Bot Tech] Reject the suggestion sent by the user')
|
||||
.addStringOption(x=>x
|
||||
.setName('update')
|
||||
.setDescription('Update a suggestion (Bot Tech only)')
|
||||
.addIntegerOption(x=>x
|
||||
.setName('id')
|
||||
.setDescription('User\'s suggestion ID')
|
||||
.setDescription('The ID of the suggestion')
|
||||
.setRequired(true))
|
||||
.addStringOption(x=>x
|
||||
.setName('message')
|
||||
.setDescription('(Optional) Include a message with your rejection')
|
||||
.setName('status')
|
||||
.setDescription('The status of the suggestion (Accepted/Rejected)')
|
||||
.setRequired(true)
|
||||
.setMaxLength(256)))
|
||||
.setChoices(
|
||||
{name: 'Accept', value: 'Accepted'},
|
||||
{name: 'Reject', value: 'Rejected'}
|
||||
)))
|
||||
}
|
||||
|
@ -1,128 +1,85 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async autocomplete(client: TClient, interaction: Discord.AutocompleteInteraction){
|
||||
const array = (await client.tags?.findInCache())?.map(x=>x._id).filter(c=>c.startsWith(interaction.options.getFocused()));
|
||||
await interaction?.respond(array?.map(c=>({name: c, value: c})));
|
||||
// If you question all those '?.', let me tell you: Discord.JS is fricking stupid and I am too stressed to find a solution for it.
|
||||
},
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (interaction.options.getSubcommandGroup() === 'management' && !MessageTool.isStaff(interaction.member) && !client.config.whitelist.includes(interaction.member.id)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||
const tagData = async()=>await client.tags._content.findOne({_id: interaction.options.getString('name')});
|
||||
const tagMeta = {
|
||||
isEmbedTrue: async()=>(await tagData()).embedBool,
|
||||
title: async()=>(await tagData())._id,
|
||||
message: async()=>(await tagData()).message,
|
||||
creatorName: async()=>(await client.users.fetch((await tagData()).user._id)).displayName
|
||||
};
|
||||
export default class Tag {
|
||||
static async autocomplete(client:TClient, interaction:Discord.AutocompleteInteraction<'cached'>) {
|
||||
const tagsInCache = await client.tags?.findInCache();
|
||||
const filterArray = tagsInCache?.map(x=>x.tagname).filter(x=>x.startsWith(interaction.options.getFocused()));
|
||||
await interaction?.respond(filterArray?.map(tag=>({name: tag, value: tag})));
|
||||
}
|
||||
static async run(client:TClient, interaction:Discord.ChatInputCommandInteraction<'cached'>) {
|
||||
const tagName = interaction.options.getString('tag-name');
|
||||
const tagMsg = interaction.options.getString('message');
|
||||
({
|
||||
send: async()=>{
|
||||
if (!await tagData()) return interaction.reply({content:'This tag is not available in the database.',ephemeral:true});
|
||||
let targetField = '';
|
||||
const targetMember = interaction.options.getMember('target_user');
|
||||
if (targetMember) targetField = `*This tag is for <@${targetMember.id}>*`;
|
||||
const embedTemplate = new client.embed().setColor(client.config.embedColor).setTitle(await tagMeta.title()).setDescription(await tagMeta.message()).setFooter({text: `Tag creator: ${await tagMeta.creatorName()}`});
|
||||
const messageTemplate = MessageTool.concatMessage(
|
||||
targetField ? targetField : '',
|
||||
`**${await tagMeta.title()}**`,
|
||||
await tagMeta.message(),
|
||||
'',
|
||||
`Tag creator: **${await tagMeta.creatorName()}**`
|
||||
);
|
||||
if (await tagMeta.isEmbedTrue()) return interaction.reply({content: targetField ? targetField : null, embeds: [embedTemplate], allowedMentions:{parse:['users']}});
|
||||
else return interaction.reply({content: messageTemplate, allowedMentions:{parse:['users']}})
|
||||
send: async()=>await client.tags.sendTag(interaction, tagName, interaction.options.getMember('target')?.id),
|
||||
create: async()=>{
|
||||
const newTag = await client.tags.createTag(interaction.member.id, tagName, tagMsg, interaction.options.getBoolean('toggle-embed'));
|
||||
await interaction.reply(newTag ? 'Tag successfully created, should be available in the list soon!' : 'Tag already exists, try again with a different name.');
|
||||
},
|
||||
create: async()=>await client.tags._content.create({
|
||||
_id: interaction.options.getString('name'),
|
||||
message: interaction.options.getString('message').replaceAll(/\\n/g, '\n'),
|
||||
embedBool: interaction.options.getBoolean('embed'),
|
||||
user: {
|
||||
_id: interaction.member.id,
|
||||
name: interaction.user.username
|
||||
}
|
||||
})
|
||||
.then(()=>{
|
||||
interaction.reply('Tag successfully created, should be available in a few seconds!')
|
||||
client.tags.updateCache();
|
||||
})
|
||||
.catch(err=>interaction.reply(`There was an error while trying to create your tag:\n\`\`\`${err}\`\`\``)),
|
||||
delete: async()=>await client.tags._content.findByIdAndDelete(interaction.options.getString('name'))
|
||||
.then(()=>{
|
||||
interaction.reply('Tag successfully deleted.')
|
||||
client.tags.updateCache();
|
||||
}).catch(err=>interaction.reply(`Failed to delete the tag:\n\`\`\`${err}\`\`\``)),
|
||||
edit: async()=>await client.tags._content.findByIdAndUpdate(interaction.options.getString('name'), {
|
||||
$set: {
|
||||
message: interaction.options.getString('new-message').replaceAll(/\\n/g, '\n'),
|
||||
embedBool: interaction.options.getBoolean('embed')
|
||||
}
|
||||
})
|
||||
.then(()=>{
|
||||
interaction.reply('Tag successfully updated, enjoy!')
|
||||
client.tags.updateCache();
|
||||
})
|
||||
.catch(err=>interaction.reply(`Tag couldn\'t be updated:\n\`\`\`${err}\`\`\``))
|
||||
delete: async()=>{
|
||||
await client.tags.deleteTag(tagName);
|
||||
return interaction.reply('Tag successfully deleted.');
|
||||
},
|
||||
modify: async()=>{
|
||||
await client.tags.modifyTag(tagName, interaction.options.getString('new-message'));
|
||||
return interaction.reply('Tag successfully modified.')
|
||||
}
|
||||
} as any)[interaction.options.getSubcommand() ?? interaction.options.getSubcommandGroup()]();
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('tag')
|
||||
.setDescription('Send user the resources/FAQ provided in the tag')
|
||||
.setDescription('Send a tag containing the resources/FAQ provided in tag to the user')
|
||||
.addSubcommand(x=>x
|
||||
.setName('send')
|
||||
.setDescription('Send a resource tag')
|
||||
.addStringOption(x=>x
|
||||
.setName('name')
|
||||
.setName('tag-name')
|
||||
.setDescription('Name of an existing tag to send')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true))
|
||||
.addUserOption(x=>x
|
||||
.setName('target_user')
|
||||
.setDescription('Directly mention the target with this tag')))
|
||||
.setName('target')
|
||||
.setDescription('Directly mention the member with this tag')
|
||||
.setRequired(false)))
|
||||
.addSubcommandGroup(x=>x
|
||||
.setName('management')
|
||||
.setDescription('Add a new tag or delete/edit your current tag')
|
||||
.setName('tools')
|
||||
.setDescription('Management tools for the tags system (Discord mods & Bot Tech only)')
|
||||
.addSubcommand(x=>x
|
||||
.setName('create')
|
||||
.setDescription('Create a new tag')
|
||||
.addStringOption(x=>x
|
||||
.setName('name')
|
||||
.setDescription('Name of your tag, must be within 3-25 characters')
|
||||
.setMinLength(3)
|
||||
.setMaxLength(25)
|
||||
.setName('tag-name')
|
||||
.setDescription('Name of the tag, must be within 4-32 characters')
|
||||
.setMinLength(4)
|
||||
.setMaxLength(32)
|
||||
.setRequired(true))
|
||||
.addStringOption(x=>x
|
||||
.setName('message')
|
||||
.setDescription('Message to be included in your tag; e.g, you\'re giving the user some instructions, newline: \\n')
|
||||
.setMinLength(6)
|
||||
.setMaxLength(2048)
|
||||
.setDescription('Message to be included in your tag, newline: \\n')
|
||||
.setMaxLength(1990)
|
||||
.setRequired(true))
|
||||
.addBooleanOption(x=>x
|
||||
.setName('embed')
|
||||
.setDescription('Toggle this option if you want your message to be inside the embed or not')
|
||||
.setName('toggle-embed')
|
||||
.setDescription('Message will be sent in an embed description if enabled')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('delete')
|
||||
.setDescription('Delete a tag')
|
||||
.setDescription('Delete an existing tag')
|
||||
.addStringOption(x=>x
|
||||
.setName('name')
|
||||
.setName('tag-name')
|
||||
.setDescription('Name of the tag to be deleted')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)))
|
||||
.addSubcommand(x=>x
|
||||
.setName('edit')
|
||||
.setDescription('Edit an existing tag')
|
||||
.setName('modify')
|
||||
.setDescription('Modify an existing tag')
|
||||
.addStringOption(x=>x
|
||||
.setName('name')
|
||||
.setDescription('Name of the tag to be edited')
|
||||
.setName('tag-name')
|
||||
.setDescription('Name of the tag to be modified')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true))
|
||||
.addStringOption(x=>x
|
||||
.setName('new-message')
|
||||
.setDescription('Replace the current tag\'s message with a new one, newline: \\n')
|
||||
.setRequired(true))
|
||||
.addBooleanOption(x=>x
|
||||
.setName('embed')
|
||||
.setDescription('Toggle this option on an existing tag to be updated with embed or not')
|
||||
.setMaxLength(1990)
|
||||
.setRequired(true))))
|
||||
}
|
||||
|
@ -2,18 +2,19 @@ import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
export default class Unpunish {
|
||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
if (!MessageTool.isStaff(interaction.member as Discord.GuildMember)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||
const punishment = (await client.punishments._content.find({})).find(x=>x._id === interaction.options.getInteger('case_id', true));
|
||||
if (!punishment) return interaction.reply({content: 'Invalid Case ID', ephemeral: true});
|
||||
if (punishment.expired) return interaction.reply('This case has been overwritten by another case.');
|
||||
const punishment = await client.punishments.findCase(interaction.options.getInteger('case_id', true));
|
||||
if (!punishment) return interaction.reply({content: 'Case ID is not found in database.', ephemeral: true});
|
||||
if (['unban', 'unmute', 'punishmentOverride'].includes(punishment.dataValues.type)) return interaction.reply({content: 'This case ID is immutable. (Informative case)', ephemeral: true});
|
||||
const reason = interaction.options.getString('reason') ?? 'Reason unspecified';
|
||||
await client.punishments.removePunishment(punishment.id, interaction.user.id, reason, interaction);
|
||||
Logger.forwardToConsole('log', 'UnpunishmentLog', `Case #${interaction.options.getInteger('case_id')} was used in /${interaction.commandName} for ${reason}`);
|
||||
(client.channels.cache.get(client.config.mainServer.channels.punishment_log) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColor).setTitle('Unpunishment Log').setDescription(`Case #${interaction.options.getInteger('case_id')} was used in \`/${interaction.commandName}\` for \`${reason}\``).setTimestamp()]});
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
await client.punishments.punishmentRemove(punishment.dataValues.case_id, interaction.user.id, reason, interaction);
|
||||
|
||||
Logger.console('log', 'UnpunishmentLog', `Case #${interaction.options.getInteger('case_id')} was used in /${interaction.commandName} for ${reason}`);
|
||||
(client.channels.cache.get(client.config.dcServer.channels.punishment_log) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColor).setTitle('Unpunishment Log').setDescription(`Case #${interaction.options.getInteger('case_id')} was used in \`/${interaction.commandName}\` for \`${reason}\``).setTimestamp()]});
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('unpunish')
|
||||
.setDescription('Remove the active punishment from a member')
|
||||
.addIntegerOption(x=>x
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Punish from '../funcs/Punish.js';
|
||||
export default {
|
||||
run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
import Punish from '../components/Punish.js';
|
||||
export default class Warn {
|
||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
Punish(client, interaction, 'warn');
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('warn')
|
||||
.setDescription('Warn a member')
|
||||
.addUserOption(x=>x
|
||||
@ -15,4 +15,4 @@ export default {
|
||||
.addStringOption(x=>x
|
||||
.setName('reason')
|
||||
.setDescription('Reason for the warning'))
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ function convert(status?:Discord.ClientPresenceStatus){
|
||||
else return '⚫'
|
||||
}
|
||||
|
||||
export default {
|
||||
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
export default class Whois {
|
||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||
const member = interaction.options.getMember('member') as Discord.GuildMember;
|
||||
if (member === null){
|
||||
const user = interaction.options.getUser('member') as Discord.User;
|
||||
@ -34,8 +34,8 @@ export default {
|
||||
const embed = new client.embed()
|
||||
.setColor(member.displayColor || client.config.embedColor)
|
||||
.setURL(`https://discord.com/users/${member.user.id}`)
|
||||
.setThumbnail(member.user.avatarURL({size:2048}) || member.user.defaultAvatarURL)
|
||||
.setImage(member.user.bannerURL({size:1024}) as string)
|
||||
.setThumbnail(member.avatarURL({size:2048}) || member.user.avatarURL({size:2048}) || member.user.defaultAvatarURL)
|
||||
.setImage(member.user.bannerURL({size:1024}) || null)
|
||||
.setTitle(`${title} Info: ${member.user.username}`)
|
||||
.setDescription(`<@${member.user.id}>\n\`${member.user.id}\``)
|
||||
.addFields(
|
||||
@ -62,8 +62,8 @@ export default {
|
||||
}
|
||||
interaction.reply({embeds: embedArray})
|
||||
}
|
||||
},
|
||||
data: new Discord.SlashCommandBuilder()
|
||||
}
|
||||
static data = new Discord.SlashCommandBuilder()
|
||||
.setName('whois')
|
||||
.setDescription('View your own or someone else\'s information')
|
||||
.addUserOption(x=>x
|
||||
|
44
src/components/Automod.ts
Normal file
44
src/components/Automod.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
export default class Automoderator {
|
||||
private static lockQuery:Set<Discord.Snowflake> = new Set();
|
||||
static Whitelist(message:Discord.Message, ...arr:string[]) {// Array of channel ids for automod to be disabled in (Disables prohibitedWords and advertisement, mind you.)
|
||||
return arr.includes(message.channelId);
|
||||
}
|
||||
static scanMsg(message:Discord.Message) {
|
||||
return message.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n?0-9]|[]|ing\b/g, '').split(' ').join('');
|
||||
}
|
||||
static async repeatedMessages(client:TClient, message:Discord.Message, thresholdTime:number, thresholdAmount:number, type:string, duration:string, reason:string) {
|
||||
const now = Date.now();
|
||||
|
||||
if (!client.repeatedMessages[message.author.id]) client.repeatedMessages[message.author.id] = {type: type, count:1, firstTime:now, timeout: null};
|
||||
else {
|
||||
const data = client.repeatedMessages[message.author.id];
|
||||
if (now - data.firstTime < thresholdTime) {
|
||||
// If the message is within the threshold time, increment.
|
||||
data.count++;
|
||||
if (data.count >= thresholdAmount) {
|
||||
// If the count has reached the threshold amount, punish the user like most daddy would do to their child.
|
||||
if (!this.lockQuery.has(message.author.id)) {
|
||||
this.lockQuery.add(message.author.id);
|
||||
Logger.console('log', 'Automod', `Lock acquired for ${message.author.tag} with reason: ${reason}`);
|
||||
await client.punishments.punishmentAdd('mute', {time: duration}, client.user.id, `AUTOMOD:${reason}`, message.author, message.member as Discord.GuildMember);
|
||||
setTimeout(()=>{
|
||||
this.lockQuery.delete(message.author.id);
|
||||
Logger.console('log', 'Automod', `Lock released for ${message.author.tag}`);
|
||||
}, 3500); // Wait 3.5 seconds before releasing the lock.
|
||||
}
|
||||
delete client.repeatedMessages[message.author.id];
|
||||
}
|
||||
} else {
|
||||
// If the message is outside the threshold time, reset the count and timestamp.
|
||||
data.count = 1;
|
||||
data.firstTime = now;
|
||||
}
|
||||
// Reset the timer.
|
||||
clearTimeout(data.timeout);
|
||||
data.timeout = setTimeout(()=>delete client.repeatedMessages[message.author.id], thresholdTime);
|
||||
}
|
||||
}
|
||||
}
|
58
src/components/CacheServer.ts
Normal file
58
src/components/CacheServer.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import {createClient, ErrorReply} from 'redis';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import TSClient from '../helpers/TSClient.js';
|
||||
|
||||
let Prefix = 'Cache';
|
||||
const RedisClient = createClient({
|
||||
url: (await TSClient()).redis_uri,
|
||||
database: 0,
|
||||
name: 'Daggerbot',
|
||||
socket: { keepAlive: 15000, timeout: 30000 }
|
||||
});
|
||||
|
||||
export default class CacheServer {
|
||||
protected static eventManager() {
|
||||
RedisClient
|
||||
.on('connect', ()=>Logger.console('log', Prefix, 'Connection to Redis has been established'))
|
||||
.on('error', (err:ErrorReply)=>{
|
||||
Logger.console('error', Prefix, `Encountered an error in Redis: ${err.message}`)
|
||||
setTimeout(async()=>{
|
||||
if (!RedisClient.isReady) {
|
||||
Logger.console('log', Prefix, 'Client is zombified, starting a fresh connection...');
|
||||
RedisClient.quit();
|
||||
await RedisClient.connect();
|
||||
}
|
||||
}, 1500)
|
||||
})
|
||||
}
|
||||
public static async get(key:any) {
|
||||
const cachedResult = await RedisClient.get(key);
|
||||
if (cachedResult) return JSON.parse(cachedResult);
|
||||
else return null
|
||||
}
|
||||
public static async set(key:any, value:any) {
|
||||
return await RedisClient.set(key, JSON.stringify(value));
|
||||
}
|
||||
public static async getJSON(key:any) {
|
||||
const cachedResult = await RedisClient.json.get(key);
|
||||
if (cachedResult) return cachedResult;
|
||||
else return null
|
||||
}
|
||||
public static async setJSON(key:any, value:any) {
|
||||
return await RedisClient.json.set(key, '.', value);
|
||||
}
|
||||
public static async expiry(key:any, time:number) {
|
||||
return await RedisClient.expire(key, time); // NOTE: time is in seconds, not milliseconds -- you know what you did wrong
|
||||
}
|
||||
public static async delete(key:any) {
|
||||
return await RedisClient.del(key);
|
||||
}
|
||||
public static init() {
|
||||
try {
|
||||
RedisClient.connect();
|
||||
this.eventManager();
|
||||
} catch {
|
||||
console.error('Cannot initialize RedisClient -- is Redis running?')
|
||||
}
|
||||
}
|
||||
}
|
155
src/components/CanvasGraph.ts
Normal file
155
src/components/CanvasGraph.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import {createCanvas, Canvas, CanvasRenderingContext2D} from 'canvas';
|
||||
import {Config} from '../interfaces';
|
||||
import ConfigHelper from '../helpers/ConfigHelper.js';
|
||||
export default class CanvasBuilder {
|
||||
private canvas: Canvas;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private config: Config;
|
||||
|
||||
constructor() {
|
||||
this.canvas = createCanvas(1500, 750);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.config = ConfigHelper.readConfig() as Config;
|
||||
}
|
||||
|
||||
public async generateGraph(data:number[], type:'players'|'leaderboard'):Promise<Canvas> {
|
||||
// Color layout for the graph -- The variables are named exactly what it shows in graph to make it easier to be referenced to.
|
||||
let oddHorizontal = '#555B63';
|
||||
let evenHorizontal = '#3E4245';
|
||||
let background = '#111111';
|
||||
let textColor = '#FFFFFF';
|
||||
let redLine = '#E62C3B';
|
||||
let yellowLine = '#FFEA00';
|
||||
let greenLine = '#57F287';
|
||||
|
||||
// Handle negative
|
||||
for (const [i, change] of data.entries()) if (change as number < 0) data[i] = data[i - 1] || data[i + 1] || 0;
|
||||
|
||||
const LBdataFirst = Math.ceil(Math.max(...data) * 10 ** (-Math.max(...data).toString().split('').length + 1)) * 10 ** (Math.max(...data).toString().split('').length - 1)
|
||||
const LBdataSecond = Math.ceil(Math.max(...data) * 10 ** (-Math.max(...data).toString().split('').length + 2)) * 10 ** (Math.max(...data).toString().split('').length - 2)
|
||||
|
||||
const firstTop = type === 'leaderboard' ? LBdataFirst : 16;
|
||||
const secondTop = type === 'leaderboard' ? LBdataSecond : 16;
|
||||
const textSize = 40;
|
||||
const origin = [15, 65];
|
||||
const size = [1300, 630];
|
||||
const nodeWidth = size[0] / (data.length - 1);
|
||||
this.ctx.fillStyle = background;
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Grey horizontal lines
|
||||
this.ctx.lineWidth = 5;
|
||||
|
||||
const intervalCandidates:[number, number, number][] = [];
|
||||
for (let i = 4; i < 10; i++) {
|
||||
const interval = firstTop / i;
|
||||
if (Number.isInteger(interval)) intervalCandidates.push([interval, i, i * Math.max(interval.toString().split('').filter(x=>x === '0').length / interval.toString().length, 0.3) * (['1', '2', '4', '5', '6', '8'].includes(interval.toString()[0]) ? 1.5 : 0.67)]);
|
||||
}
|
||||
const chosenInterval = intervalCandidates.sort((a,b)=>b[2]-a[2])[0];
|
||||
let prevY:number[] = [];
|
||||
this.ctx.strokeStyle = oddHorizontal;
|
||||
|
||||
if (type === 'leaderboard') for (let i = 0; i <= chosenInterval[1]; i++) {
|
||||
const y = origin[1] + size[1] - (i * (chosenInterval[0] / secondTop) * size[1]);
|
||||
if (y < origin[1]) continue;
|
||||
const even = ((i + 1) % 2) === 0;
|
||||
if (even) this.ctx.strokeStyle = evenHorizontal;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.lineTo(origin[0], y);
|
||||
this.ctx.lineTo(origin[0] + size[0], y);
|
||||
this.ctx.stroke();
|
||||
this.ctx.closePath();
|
||||
if (even) this.ctx.strokeStyle = oddHorizontal;
|
||||
prevY = [y, i * chosenInterval[0]];
|
||||
}
|
||||
else for (let i = 0; i < data.length; i++) {
|
||||
const y = origin[1] + size[1] - (i * (chosenInterval[0] / secondTop) * size[1]);
|
||||
if (y < origin[1]) continue;
|
||||
const even = ((i + 1) % 2) === 0;
|
||||
if (even) this.ctx.strokeStyle = evenHorizontal;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.lineTo(origin[0], y);
|
||||
this.ctx.lineTo(origin[0] + size[0], y);
|
||||
this.ctx.stroke();
|
||||
this.ctx.closePath();
|
||||
if (even) this.ctx.strokeStyle = oddHorizontal;
|
||||
prevY.push(y, i * chosenInterval[0]);
|
||||
}
|
||||
|
||||
// 30 day/minute mark
|
||||
this.ctx.setLineDash([8, 16]);
|
||||
this.ctx.beginPath();
|
||||
const lastStart = origin[0] + (nodeWidth * (data.length - (type === 'players' ? 60 : 30)));
|
||||
this.ctx.lineTo(lastStart, origin[1]);
|
||||
this.ctx.lineTo(lastStart, origin[1] + size[1]);
|
||||
this.ctx.stroke();
|
||||
this.ctx.closePath();
|
||||
this.ctx.setLineDash([]);
|
||||
|
||||
// Draw points
|
||||
const isLeaderboard =()=>type === 'leaderboard' ? this.config.embedColor as string : null;
|
||||
this.ctx.strokeStyle = isLeaderboard();
|
||||
this.ctx.fillStyle = isLeaderboard();
|
||||
this.ctx.lineWidth = 5;
|
||||
|
||||
const gradient = this.ctx.createLinearGradient(0, origin[1], 0, origin[1] + size[1]);
|
||||
gradient.addColorStop(1 / 16, redLine);
|
||||
gradient.addColorStop(5 / 16, yellowLine);
|
||||
gradient.addColorStop(12 / 16, greenLine);
|
||||
|
||||
let lastCoordinates:number[] = [];
|
||||
for (let [i, currentValue] of data.entries()) {
|
||||
if (currentValue < 0) currentValue = 0;
|
||||
const X = i * nodeWidth + origin[0];
|
||||
const Y = ((1 - (currentValue / secondTop)) * size[1]) + origin[1];
|
||||
const nextValue = data[i + 1];
|
||||
const previousValue = data[i - 1];
|
||||
this.ctx.strokeStyle = type === 'players' ? gradient : null;
|
||||
this.ctx.beginPath();
|
||||
|
||||
if (lastCoordinates.length) this.ctx.moveTo(lastCoordinates[0], lastCoordinates[1]);
|
||||
// If the line being drawn is straight line, continue until it makes a slope.
|
||||
if (Y === lastCoordinates[1]) {
|
||||
let NewX = X;
|
||||
for (let j = i+1; j <= data.length; j++) {
|
||||
if (data[j] === currentValue) NewX += nodeWidth;
|
||||
else break;
|
||||
}
|
||||
this.ctx.lineTo(NewX, Y);
|
||||
} else this.ctx.lineTo(X, Y);
|
||||
lastCoordinates = [X, Y];
|
||||
this.ctx.stroke();
|
||||
this.ctx.closePath();
|
||||
|
||||
if (currentValue !== previousValue || currentValue !== nextValue) {
|
||||
// Ball. What else?
|
||||
this.ctx.fillStyle = type === 'players' ? gradient : null;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(X, Y, this.ctx.lineWidth * 1.2, 0, 2 * Math.PI);
|
||||
this.ctx.closePath();
|
||||
this.ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw text
|
||||
this.ctx.font = '400 ' + textSize + 'px sans-serif';
|
||||
this.ctx.fillStyle = textColor;
|
||||
|
||||
// Highest value
|
||||
this.ctx.fillText(type === 'leaderboard'
|
||||
? prevY[1].toLocaleString('en-US')
|
||||
: prevY.at(-1).toLocaleString('en-US'), origin[0] + size[0] + textSize / 2, origin[1] + (textSize / 3)
|
||||
)
|
||||
|
||||
// Lowest value
|
||||
this.ctx.fillText(type === 'leaderboard' ? '0 msgs' : '0', origin[0] + size[0] + textSize / 2, origin[1] + size[1] + (textSize / 3));
|
||||
|
||||
// 30 day (minute for /mp players)
|
||||
this.ctx.fillText(type === 'leaderboard' ? '30 days ago' : '30 mins ago', lastStart, origin[1] - (textSize / 2));
|
||||
|
||||
// Time
|
||||
this.ctx.fillText('time ->', origin[0] + (textSize / 2), origin[1] + size[1] + (textSize));
|
||||
|
||||
return this.canvas;
|
||||
}
|
||||
}
|
25
src/components/DatabaseServer.ts
Normal file
25
src/components/DatabaseServer.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {Sequelize} from 'sequelize';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import TSClient from '../helpers/TSClient.js';
|
||||
|
||||
const postgresUri = (await TSClient()).postgres_uri;
|
||||
export default class DatabaseServer {
|
||||
public static seq:Sequelize = new Sequelize(postgresUri, {dialect: 'postgres', logging: false, ssl: false})
|
||||
public static async init() {
|
||||
try {
|
||||
await this.seq.authenticate();
|
||||
this.healthCheck();
|
||||
} catch {
|
||||
Logger.console('error', 'Database', 'Cannot initialize Sequelize -- is PostgreSQL running?');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
private static async healthCheck() {
|
||||
try {
|
||||
await this.seq.query('SELECT 1');
|
||||
Logger.console('log', 'Database', 'Connection to PostgreSQL has been established');
|
||||
} catch {
|
||||
Logger.console('error', 'Database', 'Connection to PostgreSQL has been lost');
|
||||
}
|
||||
}
|
||||
}
|
33
src/components/HookManager.ts
Normal file
33
src/components/HookManager.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import ConfigHelper from '../helpers/ConfigHelper.js';
|
||||
const config = ConfigHelper.readConfig();
|
||||
type ChannelList = keyof typeof config.dcServer.channels;
|
||||
export default class HookMgr {
|
||||
private client:TClient;
|
||||
private channel:ChannelList;
|
||||
private webhookId:Discord.Snowflake;
|
||||
|
||||
constructor(client:TClient, channel:ChannelList, webhookId:Discord.Snowflake) {
|
||||
this.client = client;
|
||||
this.channel = channel;
|
||||
this.webhookId = webhookId;
|
||||
}
|
||||
|
||||
protected async channelFetch(client:TClient, channel:ChannelList) {
|
||||
return await client.channels.fetch(config.dcServer.channels[channel]) as Discord.TextChannel;
|
||||
}
|
||||
protected async fetch(client:TClient, channel:ChannelList, webhookId:Discord.Snowflake) {
|
||||
const hookInstance = await (await this.channelFetch(client, channel)).fetchWebhooks().then(x=>x.find(y=>y.id===webhookId));
|
||||
if (!hookInstance) throw new Error('[HookManager] Webhook not found.');
|
||||
return hookInstance;
|
||||
}
|
||||
async send(message:string|Discord.MessagePayload|Discord.WebhookMessageCreateOptions) {
|
||||
const hook = await this.fetch(this.client, this.channel, this.webhookId);
|
||||
return hook.send(message).catch(err=>(this.client.channels.resolve(config.dcServer.channels.errors) as Discord.TextChannel).send(`Failed to send a webhook message in #${this.channel}:\n\`\`\`\n${err.message}\n\`\`\``));
|
||||
}
|
||||
async edit(messageId:Discord.Snowflake, message:string|Discord.MessagePayload|Discord.WebhookMessageEditOptions) {
|
||||
const hook = await this.fetch(this.client, this.channel, this.webhookId);
|
||||
return hook.editMessage(messageId, message).catch(err=>(this.client.channels.resolve(config.dcServer.channels.errors) as Discord.TextChannel).send(`Failed to edit a webhook message in #${this.channel}:\n\`\`\`\n${err.message}\n\`\`\``));
|
||||
}
|
||||
}
|
18
src/components/Punish.ts
Normal file
18
src/components/Punish.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default async(client:TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>, type: string)=>{
|
||||
if (!MessageTool.isStaff(interaction.member)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||
|
||||
const time = interaction.options.getString('time') ?? undefined;
|
||||
const reason = interaction.options.getString('reason') ?? 'Reason unspecified';
|
||||
const GuildMember = interaction.options.getMember('member') ?? undefined;
|
||||
const User = interaction.options.getUser('member', true);
|
||||
|
||||
if (interaction.user.id === User.id) return interaction.reply(`You cannot ${type} yourself.`);
|
||||
if (!GuildMember && !['unban', 'ban'].includes(type)) return interaction.reply(`You cannot ${type} someone who is not in the server.`);
|
||||
if (User.bot) return interaction.reply(`You cannot ${type} a bot!`);
|
||||
|
||||
await interaction.deferReply();
|
||||
await client.punishments.punishmentAdd(type, {time, interaction}, interaction.user.id, reason, User, GuildMember);
|
||||
}
|
@ -1,35 +1,25 @@
|
||||
{
|
||||
"configName": "Daggerbot",
|
||||
"embedColor": "#FFFFFF",
|
||||
"embedColor": "#0052cf",
|
||||
"embedColorBackup": "#0052cf",
|
||||
"embedColorGreen": "#57f287",
|
||||
"embedColorOrange": "#cc5210",
|
||||
"embedColorYellow": "#ffea00",
|
||||
"embedColorRed": "#e62c3b",
|
||||
"embedColorInvis": "#2f3136",
|
||||
"embedColorBCA": "#ff69b4",
|
||||
"embedColorXmas": "#FFFFFF",
|
||||
"embedColorXmas": "#ffffff",
|
||||
"LRSstart": 1661236321433,
|
||||
"whitelistedServers": [
|
||||
"929807948748832798", "468835415093411861", "1058183358267543552", "549114074273677314"
|
||||
"929807948748832798", "468835415093411861"
|
||||
],
|
||||
"MPStatsLocation": {
|
||||
"mainServer": {
|
||||
"channel": "543494084363288637",
|
||||
"message": "1023699243183112192"
|
||||
},
|
||||
"secondServer": {
|
||||
"channel": "543494084363288637",
|
||||
"message": "1149141188079779900"
|
||||
}
|
||||
},
|
||||
"botSwitches": {
|
||||
"dailyMsgsBackup": true,
|
||||
"registerCommands": false,
|
||||
"registerCommands": true,
|
||||
"commands": true,
|
||||
"logs": true,
|
||||
"mpSys": true,
|
||||
"buttonRoles": true,
|
||||
"automod": true,
|
||||
"mpstats": true,
|
||||
"autores": true
|
||||
},
|
||||
"botPresence": {
|
||||
@ -38,13 +28,12 @@
|
||||
],
|
||||
"status": "online"
|
||||
},
|
||||
"eval": true,
|
||||
"whitelist": [
|
||||
"190407856527376384",
|
||||
"633345781780185099",
|
||||
"215497515934416896",
|
||||
"141304507249197057",
|
||||
"309373272594579456"
|
||||
"309373272594579456",
|
||||
"301350210926280704"
|
||||
],
|
||||
"contribList": [
|
||||
"190407856527376384",
|
||||
@ -54,7 +43,7 @@
|
||||
"178941218510602240",
|
||||
"700641965787709520"
|
||||
],
|
||||
"mainServer": {
|
||||
"dcServer": {
|
||||
"id": "468835415093411861",
|
||||
"staffRoles": [
|
||||
"admin",
|
||||
@ -79,7 +68,6 @@
|
||||
"vtcmember": "802282391652663338"
|
||||
},
|
||||
"channels": {
|
||||
"console": "1011318687065710663",
|
||||
"errors": "1009754872188506192",
|
||||
"thismeanswar": "1091300529696673792",
|
||||
"bot_suggestions": "1040018521746325586",
|
||||
@ -88,10 +76,9 @@
|
||||
"welcome": "621134751897616406",
|
||||
"botcommands": "468888722210029588",
|
||||
"bankick_log": "1048341961901363352",
|
||||
"fs_server_log": "1104632399771488317",
|
||||
"punishment_log": "1102751034754998302",
|
||||
"dcmod_chat": "742324777934520350",
|
||||
"mf_chat": "1149238561934151690"
|
||||
"mpmod_chat": "516344221452599306"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Discord, { AuditLogEvent } from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
async run(client:TClient, member:Discord.GuildMember){
|
||||
if (member.guild?.id != client.config.mainServer.id) return;
|
||||
export default class GuildBanAdd {
|
||||
static async run(client:TClient, member:Discord.GuildMember){
|
||||
if (member.guild?.id != client.config.dcServer.id) return;
|
||||
const banLog = (await member.guild.fetchAuditLogs({ limit: 1, type: AuditLogEvent.MemberBanAdd })).entries.first();
|
||||
if (!banLog) return console.log(`Member was banned from ${member.guild.name} but no audit log for this member.`)
|
||||
const {executor, target, reason } = banLog;
|
||||
@ -11,8 +11,8 @@ export default {
|
||||
{name: '🔹 Moderator', value: `<@${executor.id}>\n\`${executor.id}\``},
|
||||
{name: '🔹 Reason', value: `${reason === null ? 'Reason unspecified': reason}`}
|
||||
);
|
||||
if (!await client.userLevels._content.findById(member.user.id)) embed.setFooter({text:'Rank data has been wiped.'});
|
||||
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [embed]})
|
||||
if (!await client.userLevels.fetchUser(member.user.id)) embed.setFooter({text: 'Rank data has been wiped.'});
|
||||
(client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [embed]})
|
||||
} else console.log(`User was banned from "${member.guild.name}" but no audit log could be fetched.`)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import Discord, { AuditLogEvent } from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
async run(client:TClient, member:Discord.GuildMember){
|
||||
if (member.guild?.id != client.config.mainServer.id) return;
|
||||
export default class GuildBanRemove {
|
||||
static async run(client:TClient, member:Discord.GuildMember){
|
||||
if (member.guild?.id != client.config.dcServer.id) return;
|
||||
const unbanLog = (await member.guild.fetchAuditLogs({limit: 1, type: AuditLogEvent.MemberBanRemove})).entries.first();
|
||||
if (!unbanLog) return console.log(`User was unbanned from ${member.guild.name} but no audit log for this user.`)
|
||||
const { executor, target, reason } = unbanLog;
|
||||
if (target.id === member.user.id) (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [
|
||||
if (target.id === member.user.id) (client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [
|
||||
new client.embed().setColor(client.config.embedColorGreen).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048})).setTitle(`Member Unbanned: ${target.username}`).setDescription(`🔹 **User**\n<@${target.id}>\n\`${target.id}\``).addFields(
|
||||
{name: '🔹 Moderator', value: `<@${executor.id}>\n\`${executor.id}\``},
|
||||
{name: '🔹 Reason', value: `${reason === null ? 'Reason unspecified.': reason}`}
|
||||
|
@ -1,9 +1,8 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async run(client:TClient, member:Discord.GuildMember){
|
||||
if (member.partial || member.guild?.id != client.config.mainServer.id) return;
|
||||
export default class GuildMemberAdd {
|
||||
static async run(client:TClient, member:Discord.GuildMember){
|
||||
if (member.partial || member.guild?.id != client.config.dcServer.id) return;
|
||||
const index = member.guild.memberCount;
|
||||
const suffix = (index=>{
|
||||
const numbers = index.toString().split('').reverse(); // eg 1850 --> [0,5,8,1]
|
||||
@ -18,21 +17,16 @@ export default {
|
||||
let isBot = 'Bot';
|
||||
if (!member.user.bot) isBot = 'Member';
|
||||
if (!client.config.botSwitches.logs) return;
|
||||
(client.channels.resolve(client.config.mainServer.channels.welcome) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColor).setThumbnail(member.user.displayAvatarURL({size: 2048}) || member.user.defaultAvatarURL).setTitle(`Welcome to Daggerwin, ${member.user.username}!`).setFooter({text: `${index}${suffix} member`})]})
|
||||
(client.channels.resolve(client.config.dcServer.channels.welcome) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColor).setThumbnail(member.user.displayAvatarURL({size: 2048}) || member.user.defaultAvatarURL).setTitle(`Welcome to Daggerwin, ${member.user.username}!`).setFooter({text: `${index}${suffix} member`})]})
|
||||
const newInvites = await member.guild.invites.fetch();
|
||||
const usedInvite = newInvites.find((inv:Discord.Invite)=>client.invites.get(inv.code)?.uses < inv.uses);
|
||||
newInvites.forEach((inv:Discord.Invite)=>client.invites.set(inv.code,{uses: inv.uses, creator: inv.inviterId, channel: inv.channel.name}));
|
||||
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [
|
||||
(client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [
|
||||
new client.embed().setColor(client.config.embedColorGreen).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048})).setTitle(`${isBot} Joined: ${member.user.username}`).setDescription(`<@${member.user.id}>\n\`${member.user.id}\``).setFooter({text: `Total members: ${index}${suffix}`}).addFields(
|
||||
{name: '🔹 Account Creation Date', value: `<t:${Math.round(member.user.createdTimestamp/1000)}>\n<t:${Math.round(member.user.createdTimestamp/1000)}:R>`},
|
||||
{name: '🔹 Invite Data:', value: usedInvite ? `Invite: \`${usedInvite.code}\`\nCreated by: **${usedInvite.inviter?.username}**\nChannel: **#${usedInvite.channel.name}**` : 'No invite data could be fetched.'}
|
||||
)]});
|
||||
if (await client.punishments._content.findOne({'member': member.user.id, type: 'mute', expired: undefined})){
|
||||
(client.channels.resolve(client.config.mainServer.channels.dcmod_chat) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColorYellow).setTitle('Case evasion detected').setDescription(MessageTool.concatMessage(
|
||||
`**${member.user.username}** (\`${member.user.id}\`) has been detected for case evasion.`,
|
||||
'Timeout has been automatically added. (25 days)'
|
||||
)).setTimestamp()]});
|
||||
await client.punishments.addPunishment('mute', {time: '25d'}, client.user.id, '[AUTOMOD] Case evasion', member.user, member)
|
||||
}
|
||||
|
||||
await client.punishments.caseEvasionCheck(member);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
async run(client:TClient, member:Discord.GuildMember){
|
||||
export default class GuildMemberRemove {
|
||||
static async run(client:TClient, member:Discord.GuildMember){
|
||||
if (!client.config.botSwitches.logs) return;
|
||||
if (!member.joinedTimestamp || member.guild?.id != client.config.mainServer.id) return;
|
||||
if (client.guilds.cache.get(client.config.mainServer.id).bans.cache.has(member.id)) return await client.userLevels._content.findByIdAndDelete(member.id);
|
||||
if (!member.joinedTimestamp || member.guild?.id != client.config.dcServer.id) return;
|
||||
if (client.guilds.cache.get(client.config.dcServer.id).bans.cache.has(member.id)) return await client.userLevels.deleteUser(member.id);
|
||||
let isBot = 'Bot';
|
||||
if (!member.user.bot) isBot = 'Member';
|
||||
const levelData = await client.userLevels._content.findById(member.id);
|
||||
const levelData = await client.userLevels.fetchUser(member.id);
|
||||
const embed = new client.embed().setColor(client.config.embedColorRed).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048}) as string).setTitle(`${isBot} Left: ${member.user.username}`).setDescription(`<@${member.user.id}>\n\`${member.user.id}\``).addFields(
|
||||
{name: '🔹 Account Creation Date', value: `<t:${Math.round(member.user.createdTimestamp/1000)}>\n<t:${Math.round(member.user.createdTimestamp/1000)}:R>`},
|
||||
{name: '🔹 Server Join Date', value: `<t:${Math.round(member.joinedTimestamp/1000)}>\n<t:${Math.round(member.joinedTimestamp/1000)}:R>`},
|
||||
{name: `🔹 Roles: ${member.roles.cache.size - 1}`, value: `${member.roles.cache.size > 1 ? member.roles.cache.filter((x)=>x.id !== member.guild.roles.everyone.id).sort((a,b)=>b.position - a.position).map(x=>x).join(member.roles.cache.size > 4 ? ' ' : '\n').slice(0,1024) : 'No roles'}`, inline: true}
|
||||
);
|
||||
if (levelData && levelData.messages > 1) embed.addFields({name: '🔹 Total messages', value: levelData.messages.toLocaleString('en-US'), inline: true});
|
||||
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds:[embed]});
|
||||
await client.userLevels._content.findByIdAndDelete(member.id)
|
||||
if (levelData && levelData.dataValues.messages > 1) embed.addFields({name: '🔹 Total messages', value: levelData.dataValues.messages.toLocaleString('en-US'), inline: true});
|
||||
(client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [embed]});
|
||||
await client.userLevels.deleteUser(member.id);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
run(client:TClient, oldMember:Discord.GuildMember, newMember:Discord.GuildMember){
|
||||
if (oldMember.guild.id != client.config.mainServer.id) return;
|
||||
export default class GuildMemberUpdate {
|
||||
static run(client:TClient, oldMember:Discord.GuildMember, newMember:Discord.GuildMember){
|
||||
if (oldMember.guild.id != client.config.dcServer.id) return;
|
||||
if (!client.config.botSwitches.logs) return;
|
||||
const channel = (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel);
|
||||
const channel = (client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel);
|
||||
if (oldMember.nickname != newMember.nickname){
|
||||
const embed = new client.embed().setColor(client.config.embedColor).setTimestamp().setThumbnail(newMember.displayAvatarURL({size: 2048})).setTitle(`Nickname updated: ${newMember.user.username}`).setDescription(`<@${newMember.user.id}>\n\`${newMember.user.id}\``)
|
||||
oldMember.nickname === null ? '' : embed.addFields({name: '🔹 Old nickname', value: `\`\`\`${oldMember.nickname}\`\`\``})
|
||||
|
@ -1,13 +1,14 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
|
||||
export default {
|
||||
async run(client:TClient, interaction:Discord.BaseInteraction){
|
||||
export default class InteractionCreate {
|
||||
static async run(client:TClient, interaction:Discord.BaseInteraction){
|
||||
if (!interaction.inGuild() || !interaction.inCachedGuild()) return;
|
||||
const logPrefix = 'Interaction';
|
||||
|
||||
if (interaction.isChatInputCommand()){
|
||||
const commandFile = client.commands.get(interaction.commandName);
|
||||
Logger.forwardToConsole('log', 'InteractionLog', `${interaction.user.username} used /${interaction.commandName} ${interaction.options.getSubcommandGroup(false) ?? ''} ${interaction.options.getSubcommand(false) ?? ''} in #${interaction.channel.name}`);
|
||||
Logger.console('log', logPrefix, `${interaction.user.username} used /${interaction.commandName} ${interaction.options.getSubcommandGroup(false) ?? ''} ${interaction.options.getSubcommand(false) ?? ''} in #${interaction.channel.name}`.replace(/\s\s+/g, ' ').trim());
|
||||
if (!client.config.botSwitches.commands && !client.config.whitelist.includes(interaction.user.id)) return interaction.reply({content: `I am currently operating in development mode.\nPlease notify <@${client.config.whitelist[0]}> if this is a mistake.`, ephemeral: true});
|
||||
if (commandFile){
|
||||
try{
|
||||
@ -28,28 +29,20 @@ export default {
|
||||
} else if (interaction.isButton()){
|
||||
if (interaction.customId.startsWith('reaction-') && client.config.botSwitches.buttonRoles){
|
||||
const RoleID = interaction.customId.replace('reaction-','');
|
||||
// Note: This is just a temporary "permanent" fix for the issue of people having both roles and less work for the mods.
|
||||
let buttonRoleBlocked = 'Cannot have both roles! - Button Role';
|
||||
if (interaction.member.roles.cache.has('1149139369433776269') && RoleID === '1149139583729160325') {
|
||||
interaction.member.roles.add('1149139583729160325', buttonRoleBlocked);
|
||||
interaction.member.roles.remove('1149139369433776269', buttonRoleBlocked);
|
||||
} else if (interaction.member.roles.cache.has('1149139583729160325') && RoleID === '1149139369433776269') {
|
||||
interaction.member.roles.add('1149139369433776269', buttonRoleBlocked);
|
||||
interaction.member.roles.remove('1149139583729160325', buttonRoleBlocked);
|
||||
}
|
||||
|
||||
if (interaction.member.roles.cache.has(RoleID)){
|
||||
interaction.member.roles.remove(RoleID, 'Button Role');
|
||||
interaction.reply({content: `You have been removed from <@&${RoleID}>`, ephemeral: true})
|
||||
} else {
|
||||
interaction.member.roles.add(RoleID, 'Button Role');
|
||||
interaction.reply({content: `You have been added to <@&${RoleID}>`, ephemeral: true})
|
||||
}
|
||||
} else if (interaction.customId.includes('deleteEmbed')) {
|
||||
if (!client.config.whitelist.includes(interaction.user.id)) return interaction.reply({content: '*Only whitelisted people can delete this embed.*', ephemeral: true});
|
||||
interaction.message.edit({content: '*Deleted.*', embeds: [], components: []});
|
||||
Logger.forwardToConsole('log', 'InteractionLog', `Embed has been deleted at ${interaction.message.url}`);
|
||||
} else Logger.forwardToConsole('log', 'InteractionLog', `Button has been pressed at ${interaction.message.url}`);
|
||||
let roleConflictMsg = 'Cannot have both roles! - Button Role';
|
||||
const WestFarm = '1149139369433776269';
|
||||
const EastFarm = '1149139583729160325';
|
||||
if (interaction.member.roles.cache.has(WestFarm) && RoleID === EastFarm) interaction.member.roles.set([EastFarm], roleConflictMsg);
|
||||
else if (interaction.member.roles.cache.has(EastFarm) && RoleID === WestFarm) interaction.member.roles.set([WestFarm], roleConflictMsg);
|
||||
|
||||
if (interaction.member.roles.cache.has(RoleID)) interaction.member.roles.remove(RoleID, 'Button Role').then(()=>interaction.reply({content: `You have been removed from <@&${RoleID}>`, ephemeral: true}));
|
||||
else interaction.member.roles.add(RoleID, 'Button Role').then(()=>interaction.reply({content: `You have been added to <@&${RoleID}>`, ephemeral: true}));
|
||||
} else if (interaction.customId.includes('deleteEvalEmbed')) {
|
||||
if (!client.config.whitelist.includes(interaction.user.id)) return interaction.reply({content: 'You are not whitelisted, therefore you cannot delete this message.', ephemeral: true});
|
||||
interaction.message.delete();
|
||||
Logger.console('log', logPrefix, `Eval embed has been deleted in #${interaction.message.channel.name} by ${interaction.member.displayName}`);
|
||||
} else Logger.console('log', logPrefix, `Button has been pressed at ${interaction.message.url}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
async run(client:TClient, invite: Discord.Invite){
|
||||
export default class InviteCreate {
|
||||
static async run(client:TClient, invite:Discord.Invite){
|
||||
if (!invite.guild) return;
|
||||
(await (invite.guild as Discord.Guild).invites.fetch()).forEach(inv=>client.invites.set(inv.code,{uses: inv.code, creator: inv.inviterId, channel: inv.channel.name}))
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
run(client:TClient, invite: Discord.Invite){
|
||||
export default class InviteDelete {
|
||||
static run(client:TClient, invite:Discord.Invite){
|
||||
client.invites.delete(invite.code)
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,36 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Response from '../funcs/ResponseModule.js';
|
||||
import CmdTrigger from '../funcs/CmdModule.js';
|
||||
import Response from '../modules/ResponseModule.js';
|
||||
import CmdTrigger from '../modules/CmdModule.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import Automoderator from '../funcs/Automod.js';
|
||||
import ConfigHelper from '../helpers/ConfigHelper.js';
|
||||
import Automoderator from '../components/Automod.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default {
|
||||
async run(client:TClient, message:Discord.Message){
|
||||
export default class MessageCreate {
|
||||
static async run(client:TClient, message:Discord.Message){
|
||||
if (message.author.bot) return;
|
||||
if (!message.inGuild()) return (client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({content: `<:fish_unamused:1083675172407623711> ${MessageTool.formatMention(client.config.whitelist[0], 'user')}\n**${message.author.username}** (\`${message.author.id}\`) tried to send me a DM, their message is:\`\`\`${message.content}\`\`\``, allowedMentions: {parse: ['users']}});
|
||||
if (!message.inGuild()) return (client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({content: `<:fish_unamused:1083675172407623711> ${MessageTool.formatMention(client.config.whitelist[0], 'user')}\n**${message.author.username}** (\`${message.author.id}\`) tried to send me a DM, their message is:\`\`\`${message.content}\`\`\``, allowedMentions: {parse: ['users']}});
|
||||
let automodded: boolean;
|
||||
|
||||
if (client.config.botSwitches.automod && !message.member.roles.cache.has(client.config.mainServer.roles.admin) && message.guildId === client.config.mainServer.id){
|
||||
if (client.config.botSwitches.automod && !message.member.roles.cache.has(client.config.dcServer.roles.dcmod) && !message.member.roles.cache.has(client.config.dcServer.roles.admin) && message.guildId === client.config.dcServer.id){
|
||||
const automodFailReason = 'msg got possibly deleted by another bot.';
|
||||
if (await client.bannedWords._content.findById(Automoderator.scanMsg(message))/* && !Whitelist.includes(message.channelId) */){
|
||||
if (await client.prohibitedWords.findWord(Automoderator.scanMsg(message))/* && !Whitelist.includes(message.channelId) */) {
|
||||
automodded = true;
|
||||
message.delete().catch(()=>Logger.forwardToConsole('log', 'AUTOMOD-BANNEDWORDS', automodFailReason));
|
||||
message.delete().catch(()=>Logger.console('log', 'AUTOMOD:PROHIBITEDWORDS', automodFailReason));
|
||||
message.channel.send('That word isn\'t allowed here.').then(x=>setTimeout(()=>x.delete(), 10000));
|
||||
await Automoderator.repeatedMessages(client, message, 30000, 3, 'bw', '30m', 'Constant swears');
|
||||
} else if (message.content.toLowerCase().includes('discord.gg/') && !MessageTool.isStaff(message.member as Discord.GuildMember)){
|
||||
await Automoderator.repeatedMessages(client, message, 30000, 3, 'bw', '30m', 'Prohibited word spam');
|
||||
} else if (message.content.toLowerCase().includes('discord.gg/') && !MessageTool.isStaff(message.member as Discord.GuildMember)) {
|
||||
const validInvite = await client.fetchInvite(message.content.split(' ').find(x=>x.includes('discord.gg/'))).catch(()=>null);
|
||||
if (validInvite && validInvite.guild?.id !== client.config.mainServer.id){
|
||||
if (validInvite && validInvite.guild?.id !== client.config.dcServer.id) {
|
||||
automodded = true;
|
||||
message.delete().catch(()=>Logger.forwardToConsole('log', 'AUTOMOD-ADVERT', automodFailReason));
|
||||
message.delete().catch(()=>Logger.console('log', 'AUTOMOD:ADVERTISEMENT', automodFailReason));
|
||||
message.channel.send('Please don\'t advertise other Discord servers.').then(x=>setTimeout(()=>x.delete(), 15000));
|
||||
await Automoderator.repeatedMessages(client, message, 60000, 2, 'adv', '1h', 'Discord Advertisement');
|
||||
await Automoderator.repeatedMessages(client, message, 60000, 2, 'adv', '1h', 'Discord advertisement');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.guildId === client.config.mainServer.id && !automodded) client.userLevels.incrementUser(message.author.id)
|
||||
if (message.guildId === client.config.dcServer.id && !automodded) client.userLevels.messageIncremental(message.author.id);
|
||||
// Mop gifs from banned channels without Monster having to mop them.
|
||||
const bannedChannels = [
|
||||
'516344221452599306', // #mp-moderators
|
||||
@ -48,7 +49,7 @@ export default {
|
||||
const outgoingArrays = {
|
||||
guildBoost: ['Thanks for boosting our server!', 'Thanks for the boost!', 'We appreciate the boost!', `Thank you for the kind boost, <@${message.author.id}>!`],
|
||||
}
|
||||
const GeneralChatID = '468835415093411863';
|
||||
const GeneralChatID = ConfigHelper.isDevMode() ? '1160707096493441056' : '468835415093411863';
|
||||
Response.create(client, message, GeneralChatID, 'morning');
|
||||
Response.create(client, message, GeneralChatID, 'afternoon');
|
||||
Response.create(client, message, GeneralChatID, 'evening');
|
||||
@ -57,15 +58,36 @@ export default {
|
||||
CmdTrigger.registerCmds(client, message, 'wepanikfrfr');
|
||||
CmdTrigger.MFPwTrigger(message, 'farmpw');
|
||||
|
||||
if (message.type === 8 && message.channelId === GeneralChatID) message.channel.send({content: outgoingArrays.guildBoost[Math.floor(Math.random() * outgoingArrays.guildBoost.length)], allowedMentions: {parse: ['users']}})
|
||||
let picStorage = {
|
||||
cantRead: 'https://tenor.com/view/aristocats-george-pen-cap-meticulous-gif-5330931',
|
||||
amAlive: 'https://tenor.com/view/i-still-feel-alive-living-existing-active-singing-gif-14630579',
|
||||
deadChat: 'https://cdn.discordapp.com/attachments/925589318276382720/1011333656167579849/F57G5ZS.png',
|
||||
};
|
||||
let ModsGoGetThisPerson = [// I find this variable amusing but also can't think of anything better so not changing it.
|
||||
{
|
||||
user: 'nawdic',
|
||||
img: 'https://c.tenor.com/JSj9ie_MD9kAAAAC/kopfsch%C3%BCtteln-an-kopf-fassen-oh-no.gif',
|
||||
title: '*Nawdic has done an oopsie*',
|
||||
},
|
||||
{
|
||||
user: 'monster',
|
||||
img: 'https://media.tenor.com/ZIzIjb_wvEoAAAAC/face-palm.gif',
|
||||
title: '*Monster has broken something*',
|
||||
}
|
||||
];
|
||||
|
||||
if (message.type === Discord.MessageType.GuildBoost && message.channelId === GeneralChatID) message.channel.send({content: outgoingArrays.guildBoost[Math.floor(Math.random() * outgoingArrays.guildBoost.length)], allowedMentions: {parse: ['users']}})
|
||||
if (message.mentions.members.has('309373272594579456') && !MessageTool.isStaff(message.member)) message.reply('Please don\'t tag Daggerwin, read rule 14 in <#468846117405196289>');
|
||||
if (message.mentions.members.has('215497515934416896') && !MessageTool.isStaff(message.member) && message.type != 19) message.reply('Please don\'t tag Monster unless it\'s important!');
|
||||
if (message.mentions.members.has('215497515934416896') && !MessageTool.isStaff(message.member) && message.type != Discord.MessageType.Reply) message.reply('Please don\'t tag Monster unless it\'s important!');
|
||||
if (incomingArrays.password.some(e=>message.content.toLowerCase().includes(e))) message.reply('Password and other details can be found in <#543494084363288637>');
|
||||
if (incomingArrays.cantRead.some(e=>message.content.toLowerCase().includes(e))) message.reply('https://tenor.com/view/aristocats-george-pen-cap-meticulous-gif-5330931');
|
||||
if (message.content.toLowerCase().includes('is daggerbot working')) message.reply('https://tenor.com/view/i-still-feel-alive-living-existing-active-singing-gif-14630579');
|
||||
if (incomingArrays.deadChat.some(e=>message.content.toLowerCase().includes(e))) message.reply('https://cdn.discordapp.com/attachments/925589318276382720/1011333656167579849/F57G5ZS.png');
|
||||
if (Automoderator.scanMsg(message).includes('nawdic') && incomingArrays.theyBrokeIt.some(e=>Automoderator.scanMsg(message).includes(e)) && MessageTool.isStaff(message.member) && message.channelId !== '516344221452599306') message.reply({embeds: [new client.embed().setTitle('*Nawdic has done an oopsie*').setImage('https://c.tenor.com/JSj9ie_MD9kAAAAC/kopfsch%C3%BCtteln-an-kopf-fassen-oh-no.gif').setColor(client.config.embedColor)]});
|
||||
if (Automoderator.scanMsg(message).includes('monster') && incomingArrays.theyBrokeIt.some(e=>Automoderator.scanMsg(message).includes(e)) && MessageTool.isStaff(message.member) && message.channelId !== '516344221452599306') message.reply({embeds: [new client.embed().setTitle('*Monster has broken something*').setImage('https://media.tenor.com/ZIzIjb_wvEoAAAAC/face-palm.gif').setColor(client.config.embedColor)]});
|
||||
if (incomingArrays.cantRead.some(e=>message.content.toLowerCase().includes(e))) message.reply(picStorage.cantRead);
|
||||
if (message.content.toLowerCase().includes('is daggerbot working')) message.reply(picStorage.amAlive);
|
||||
if (incomingArrays.deadChat.some(e=>message.content.toLowerCase().includes(e))) message.reply(picStorage.deadChat);
|
||||
|
||||
for (const thisPerson of ModsGoGetThisPerson) {
|
||||
if (incomingArrays.theyBrokeIt.some(x=>Automoderator.scanMsg(message).includes(x) && Automoderator.scanMsg(message).includes(thisPerson.user)) && MessageTool.isStaff(message.member) && message.channelId !== client.config.dcServer.channels.mpmod_chat)
|
||||
message.reply({embeds: [new client.embed().setTitle(thisPerson.title).setImage(thisPerson.img).setColor(client.config.embedColor)]});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,12 @@ import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import {escapeCodeBlock} from 'discord.js';
|
||||
export default {
|
||||
run(client:TClient, msg:Discord.Message){
|
||||
export default class MessageDelete {
|
||||
static run(client:TClient, msg:Discord.Message){
|
||||
if (!client.config.botSwitches.logs) return;
|
||||
const disabledChannels = ['548032776830582794', '541677709487505408', '949380187668242483']
|
||||
if (msg.guild?.id != client.config.mainServer.id || msg.partial || msg.author.bot || disabledChannels.includes(msg.channelId)) return;
|
||||
if (Discord.DiscordAPIError.name === '10008') return Logger.forwardToConsole('log', 'MsgDelete', 'Caught an unexpected error returned by Discord API. (Unknown Message)');
|
||||
if (msg.guild?.id != client.config.dcServer.id || msg.partial || msg.author.bot || disabledChannels.includes(msg.channelId)) return;
|
||||
if (Discord.DiscordAPIError.name === '10008') return Logger.console('log', 'MsgDelete', 'Caught an unexpected error returned by Discord API. (Unknown Message)');
|
||||
const embed = new client.embed().setColor(client.config.embedColorRed).setTimestamp().setAuthor({name: `Author: ${msg.author.username} (${msg.author.id})`, iconURL: `${msg.author.displayAvatarURL()}`}).setTitle('Message deleted').setDescription(`<@${msg.author.id}>\n\`${msg.author.id}\``);
|
||||
if (msg.content.length != 0) embed.addFields({name: 'Content', value: `\`\`\`\n${escapeCodeBlock(msg.content.slice(0,1000))}\n\`\`\``});
|
||||
embed.addFields(
|
||||
@ -16,6 +16,6 @@ export default {
|
||||
)
|
||||
const attachments: Array<string> = [];
|
||||
msg.attachments.forEach(x=>attachments.push(x.url));
|
||||
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [embed], files: attachments})
|
||||
(client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [embed], files: attachments})
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
run(client:TClient, messages:Discord.Collection<string, Discord.Message<boolean>>, channel:Discord.GuildTextBasedChannel){
|
||||
if (!client.config.botSwitches.logs || channel.guildId != client.config.mainServer.id) return;
|
||||
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColorRed).setTimestamp().setTitle(`${messages.size} messages were purged`).setDescription(`\`\`\`${messages.map(msgs=>`${msgs.author?.username}: ${msgs.content}`).reverse().join('\n').slice(0,3900)}\`\`\``).addFields({name: 'Channel', value: `<#${messages.first().channel.id}>`})]})
|
||||
export default class MessageDeleteBulk {
|
||||
static run(client:TClient, messages:Discord.Collection<string, Discord.Message<boolean>>, channel:Discord.GuildTextBasedChannel){
|
||||
if (!client.config.botSwitches.logs || channel.guildId != client.config.dcServer.id) return;
|
||||
(client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColorRed).setTimestamp().setTitle(`${messages.size} messages were purged`).setDescription(`\`\`\`${messages.map(msgs=>`${msgs.author?.username}: ${msgs.content}`).reverse().join('\n').slice(0,3900)}\`\`\``).addFields({name: 'Channel', value: `<#${messages.first().channel.id}>`})]})
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
run(client:TClient, reaction:Discord.MessageReaction, user:Discord.User){
|
||||
export default class MessageReactionAdd {
|
||||
static run(client:TClient, reaction:Discord.MessageReaction, user:Discord.User){
|
||||
if (!client.config.botSwitches.logs) return;
|
||||
if (reaction.message.guildId != client.config.mainServer.id || reaction.message.partial) return;
|
||||
if (reaction.message.guildId != client.config.dcServer.id || reaction.message.partial) return;
|
||||
const ReactedFirst = reaction.users.cache.first();
|
||||
if (ReactedFirst.id != user.id) return;
|
||||
if (reaction.emoji.name === '🖕') return (client.channels.cache.get(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColorYellow).setTimestamp().setAuthor({name: `Author: ${ReactedFirst.username} (${ReactedFirst.id})`, iconURL: `${ReactedFirst.displayAvatarURL()}`}).setTitle('Message reaction').setDescription(`<@${ReactedFirst.id}>\nAdded a reaction to the message.\n**Emoji**\n${reaction.emoji.name}\n**Channel**\n<#${reaction.message.channelId}>`).setFooter({text: 'Possibly this member, bot fetches who reacted first.'})], components: [new Discord.ActionRowBuilder<Discord.ButtonBuilder>().addComponents(new Discord.ButtonBuilder().setStyle(5).setURL(`${reaction.message.url}`).setLabel('Jump to message'))]});
|
||||
if (reaction.emoji.name === '🖕') return (client.channels.cache.get(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColorYellow).setTimestamp().setAuthor({name: `Author: ${ReactedFirst.username} (${ReactedFirst.id})`, iconURL: `${ReactedFirst.displayAvatarURL()}`}).setTitle('Message reaction').setDescription(`<@${ReactedFirst.id}>\nAdded a reaction to the message.\n**Emoji**\n${reaction.emoji.name}\n**Channel**\n<#${reaction.message.channelId}>`).setFooter({text: 'Possibly this member, bot fetches who reacted first.'})], components: [new Discord.ActionRowBuilder<Discord.ButtonBuilder>().addComponents(new Discord.ButtonBuilder().setStyle(5).setURL(`${reaction.message.url}`).setLabel('Jump to message'))]});
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
export default {
|
||||
run(client:TClient, reaction:Discord.MessageReaction, user:Discord.User){
|
||||
if (!client.config.botSwitches.logs || reaction.message.guildId != client.config.mainServer.id || reaction.message.partial) return;
|
||||
if (reaction.emoji.name === '🖕') return (client.channels.cache.get(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColorRed).setTimestamp().setAuthor({name: `Author: ${user.username} (${user.id})`, iconURL: `${user.displayAvatarURL()}`}).setTitle('Message reaction').setDescription(`<@${user.id}>\nRemoved a reaction from the message.\n**Emoji**\n${reaction.emoji.name}\n**Channel**\n<#${reaction.message.channelId}>`)]})
|
||||
export default class MessageReactionRemove {
|
||||
static run(client:TClient, reaction:Discord.MessageReaction, user:Discord.User){
|
||||
if (!client.config.botSwitches.logs || reaction.message.guildId != client.config.dcServer.id || reaction.message.partial) return;
|
||||
if (reaction.emoji.name === '🖕') return (client.channels.cache.get(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColorRed).setTimestamp().setAuthor({name: `Author: ${user.username} (${user.id})`, iconURL: `${user.displayAvatarURL()}`}).setTitle('Message reaction').setDescription(`<@${user.id}>\nRemoved a reaction from the message.\n**Emoji**\n${reaction.emoji.name}\n**Channel**\n<#${reaction.message.channelId}>`)]})
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,12 @@ import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import {escapeCodeBlock} from 'discord.js';
|
||||
export default {
|
||||
async run(client:TClient, oldMsg:Discord.Message, newMsg:Discord.Message){
|
||||
export default class MessageUpdate {
|
||||
static async run(client:TClient, oldMsg:Discord.Message, newMsg:Discord.Message){
|
||||
if (!client.config.botSwitches.logs) return;
|
||||
if (oldMsg.guild?.id != client.config.mainServer.id || oldMsg.author === null || oldMsg?.author.bot || oldMsg.partial || newMsg.partial || !newMsg.member || ['548032776830582794', '541677709487505408', '949380187668242483'].includes(newMsg.channelId)) return;
|
||||
if (await client.bannedWords._content.findOne({_id:newMsg.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n]/g, ' ').split(' ')}) && (!MessageTool.isStaff(newMsg.member))) newMsg.delete();
|
||||
if (oldMsg.guild?.id != client.config.dcServer.id || oldMsg.author === null || oldMsg?.author.bot || oldMsg.partial || newMsg.partial || !newMsg.member || ['548032776830582794', '541677709487505408', '949380187668242483'].includes(newMsg.channelId)) return;
|
||||
if (await client.prohibitedWords.findWord(newMsg.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n?0-9]|[]|ing\b/g, '').split(' ').join('')) && (!MessageTool.isStaff(newMsg.member))) newMsg.delete();
|
||||
if (newMsg.content === oldMsg.content) return;
|
||||
(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColor).setTimestamp().setAuthor({name: `Author: ${oldMsg.author.username} (${oldMsg.author.id})`, iconURL: `${oldMsg.author.displayAvatarURL()}`}).setTitle('Message edited').setDescription(`<@${oldMsg.author.id}>\nOld content:\n\`\`\`\n${oldMsg.content.length < 1 ? '(Attachment)' : escapeCodeBlock(oldMsg.content.slice(0,2048))}\n\`\`\`\nNew content:\n\`\`\`\n${escapeCodeBlock(newMsg.content.slice(0,2048))}\`\`\`\nChannel: <#${oldMsg.channelId}>`)], components: [new Discord.ActionRowBuilder<Discord.ButtonBuilder>().addComponents(new Discord.ButtonBuilder().setStyle(5).setURL(`${oldMsg.url}`).setLabel('Jump to message'))]});
|
||||
(client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [new client.embed().setColor(client.config.embedColor).setTimestamp().setAuthor({name: `Author: ${oldMsg.author.username} (${oldMsg.author.id})`, iconURL: `${oldMsg.author.displayAvatarURL()}`}).setTitle('Message edited').setDescription(`<@${oldMsg.author.id}>\nOld content:\n\`\`\`\n${oldMsg.content.length < 1 ? '(Attachment)' : escapeCodeBlock(oldMsg.content.slice(0,2048))}\n\`\`\`\nNew content:\n\`\`\`\n${escapeCodeBlock(newMsg.content.slice(0,2048))}\`\`\`\nChannel: <#${oldMsg.channelId}>`)], components: [new Discord.ActionRowBuilder<Discord.ButtonBuilder>().addComponents(new Discord.ButtonBuilder().setStyle(5).setURL(`${oldMsg.url}`).setLabel('Jump to message'))]});
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import ansi from 'ansi-colors';
|
||||
export default {
|
||||
async run(client:TClient){
|
||||
export default class Ready {
|
||||
static async run(client:TClient){
|
||||
const botSwitches = Object.entries(client.config.botSwitches).map(([k, v])=>`${ansi.yellow(k)}${ansi.black(':')} ${v}`).join('\n').replace(/true/g, ansi.green('true')).replace(/false/g, ansi.red('false'));
|
||||
|
||||
await client.guilds.fetch(client.config.mainServer.id).then(async guild=>{
|
||||
await client.guilds.fetch(client.config.dcServer.id).then(async guild=>{
|
||||
const logsArray = [client.config.dcServer.channels.logs, client.config.dcServer.channels.bankick_log];
|
||||
for (const channelID of logsArray) {
|
||||
const channel = await client.channels.fetch(channelID) as Discord.TextChannel;
|
||||
if (channel && channel.type === Discord.ChannelType.GuildText) await channel.messages.fetch({limit: 15});
|
||||
}
|
||||
|
||||
await guild.members.fetch();
|
||||
setInterval(()=>{
|
||||
client.user.setPresence(client.config.botPresence);
|
||||
@ -16,12 +22,12 @@ export default {
|
||||
console.log('Total commands: '+client.registry.length) //Debugging reasons.
|
||||
client.config.whitelistedServers.forEach(guildId=>(client.guilds.cache.get(guildId) as Discord.Guild).commands.set(client.registry).catch((e:Error)=>{
|
||||
console.log(`Couldn't register slash commands for ${guildId} because`, e.stack);
|
||||
(client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel).send(`Cannot register slash commands for **${client.guilds.cache.get(guildId).name}** (\`${guildId}\`):\n\`\`\`${e.message}\`\`\``)
|
||||
(client.channels.resolve(client.config.dcServer.channels.errors) as Discord.TextChannel).send(`Cannot register slash commands for **${client.guilds.cache.get(guildId).name}** (\`${guildId}\`):\n\`\`\`${e.message}\`\`\``)
|
||||
}))
|
||||
}
|
||||
console.log(`${client.user.username} has logged into Discord API`);
|
||||
console.log(client.config.botSwitches, client.config.whitelistedServers);
|
||||
(client.channels.resolve(client.config.mainServer.channels.bot_status) as Discord.TextChannel).send({content: `**${client.user.username}** is active`, embeds:[new client.embed().setColor(client.config.embedColor).setDescription(`**\`\`\`ansi\n${botSwitches}\n\`\`\`**`)]});
|
||||
(client.channels.resolve(client.config.dcServer.channels.bot_status) as Discord.TextChannel).send({content: `**${client.user.username}** is active`, embeds:[new client.embed().setColor(client.config.embedColor).setDescription(`**\`\`\`ansi\n${botSwitches}\n\`\`\`**`)]});
|
||||
console.timeEnd('Startup')
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
|
||||
export default class Automoderator {
|
||||
static Whitelist(message:Discord.Message, ...arr:string[]){// Array of channel ids for automod to be disabled in (Disables bannedWords and advertisement, mind you.)
|
||||
return arr.includes(message.channelId);
|
||||
}
|
||||
static scanMsg(message:Discord.Message){
|
||||
return message.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n?0-9]|ing\b/g, '').split(' ')
|
||||
}
|
||||
static async repeatedMessages(client:TClient, message:Discord.Message, thresholdTime:number, thresholdAmount:number, type:string, muteTime:string, muteReason:string){
|
||||
if (client.repeatedMessages[message.author.id]){
|
||||
// Add message to the list
|
||||
client.repeatedMessages[message.author.id].data.set(message.createdTimestamp, {type, channel: message.channelId});
|
||||
|
||||
// Reset the timeout
|
||||
clearTimeout(client.repeatedMessages[message.author.id].timeout);
|
||||
client.repeatedMessages[message.author.id].timeout = setTimeout(()=>delete client.repeatedMessages[message.author.id], thresholdTime);
|
||||
|
||||
// Message sent after (now - threshold), so purge those that were sent earlier
|
||||
client.repeatedMessages[message.author.id].data = client.repeatedMessages[message.author.id].data.filter((_,i)=>i >= Date.now() - thresholdTime);
|
||||
|
||||
// A spammed message is one that has been sent within the threshold parameters
|
||||
const spammedMessage = client.repeatedMessages[message.author.id].data.find(x=>{
|
||||
return client.repeatedMessages[message.author.id].data.filter(y=>x.type===y.type).size >= thresholdAmount;
|
||||
});
|
||||
if (spammedMessage){
|
||||
delete client.repeatedMessages[message.author.id];
|
||||
await client.punishments.addPunishment('mute', {time: muteTime}, (client.user as Discord.User).id, `[AUTOMOD] ${muteReason}`, message.author, message.member as Discord.GuildMember);
|
||||
}
|
||||
} else {
|
||||
client.repeatedMessages[message.author.id] = {data: new client.collection(), timeout: setTimeout(()=>delete client.repeatedMessages[message.author.id], thresholdTime)};
|
||||
client.repeatedMessages[message.author.id].data.set(message.createdTimestamp, {type, channel: message.channelId});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import {createClient, ErrorReply} from 'redis';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import TSClient from '../helpers/TSClient.js';
|
||||
|
||||
let Prefix = 'Cache';
|
||||
const RedisClient = createClient({
|
||||
url: (await TSClient.Token()).redis_uri,
|
||||
database: 0,
|
||||
name: 'Daggerbot',
|
||||
socket: {
|
||||
keepAlive: 15000,
|
||||
timeout: 30000
|
||||
}
|
||||
});
|
||||
|
||||
export default class CacheServer {
|
||||
protected static eventManager() {
|
||||
RedisClient
|
||||
.on('connect', ()=>Logger.forwardToConsole('log', Prefix, 'Connection to Redis has been established'))
|
||||
.on('error', (err:ErrorReply)=>{
|
||||
Logger.forwardToConsole('error', Prefix, `Encountered an error in Redis: ${err.message}`)
|
||||
setTimeout(async()=>{
|
||||
if (!RedisClient.isReady) {
|
||||
Logger.forwardToConsole('log', Prefix, 'Client is zombified, starting a fresh connection...');
|
||||
RedisClient.quit();
|
||||
await RedisClient.connect();
|
||||
}
|
||||
}, 1500)
|
||||
})
|
||||
}
|
||||
static async get(key:any) {
|
||||
const cachedResult = await RedisClient.get(key);
|
||||
if (cachedResult) return JSON.parse(cachedResult);
|
||||
else return null
|
||||
}
|
||||
static async set(key:any, value:any) {
|
||||
return await RedisClient.set(key, JSON.stringify(value));
|
||||
}
|
||||
static async expiry(key:any, time:number) {
|
||||
return await RedisClient.expire(key, time); // NOTE: time is in seconds, not milliseconds -- you know what you did wrong
|
||||
}
|
||||
static async delete(key:any) {
|
||||
return await RedisClient.del(key);
|
||||
}
|
||||
static init() {
|
||||
RedisClient.connect();
|
||||
this.eventManager();
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
|
||||
export default class CmdTrigger {
|
||||
private static readonly prefix = '!!_';
|
||||
private static SenseTrigger(message:Discord.Message, trigger:string): boolean {
|
||||
return message.content.toLowerCase().startsWith(this.prefix+trigger)
|
||||
}
|
||||
static registerCmds(client:TClient, message:Discord.Message, trigger:string) {
|
||||
if (this.SenseTrigger(message, trigger) && client.config.whitelist.includes(message.author.id)) {
|
||||
(client.guilds.cache.get(message.guildId) as Discord.Guild).commands.set(client.registry)
|
||||
.then(()=>message.reply('How did you manage to lose the commands??? Anyways, it\'s re-registered now.'))
|
||||
.catch((e:Error)=>message.reply(`Failed to deploy slash commands:\n\`\`\`${e.message}\`\`\``));
|
||||
}
|
||||
}
|
||||
static MFPwTrigger(message:Discord.Message, trigger:string) {
|
||||
if (this.SenseTrigger(message, trigger)) {
|
||||
let farmPwText = 'The farm password is ';
|
||||
if (message.channelId === '1149138133514981386') return message.reply(farmPwText += '`westfarm`')
|
||||
else if (message.channelId === '1149138202662293555') return message.reply(farmPwText += '`eastfarm`')
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import mongoose from 'mongoose';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import TSClient from '../helpers/TSClient.js';
|
||||
|
||||
const connection:mongoose.Connection = mongoose.connection;
|
||||
export default class DatabaseServer {
|
||||
protected static eventManager() {
|
||||
let dbPrefix = 'Database';
|
||||
connection
|
||||
.on('connected', ()=>Logger.forwardToConsole('log', dbPrefix, 'Connection to MongoDB has been established'))
|
||||
.on('disconnected', ()=>Logger.forwardToConsole('log', dbPrefix, 'Connection to MongoDB has been lost'))
|
||||
.on('close', ()=>Logger.forwardToConsole('log', dbPrefix, 'MongoDB has closed the connection'))
|
||||
.on('all', ()=>Logger.forwardToConsole('log', dbPrefix, 'Successfully established a connection to all members'))
|
||||
.on('fullsetup', ()=>Logger.forwardToConsole('log', dbPrefix, 'Successfully established a connection to Primary server & atleast one member'))
|
||||
.on('error', (err:mongoose.Error)=>Logger.forwardToConsole('error', dbPrefix, `Encountered an error in MongoDB: ${err.message}`))
|
||||
}
|
||||
protected static async connect() {
|
||||
connection.set('strictQuery', true);
|
||||
connection.openUri((await TSClient.Token()).mongodb_uri, {
|
||||
replicaSet: 'toastyy',
|
||||
autoIndex: true,
|
||||
authMechanism: 'SCRAM-SHA-256',
|
||||
authSource: 'admin',
|
||||
serverSelectionTimeoutMS: 15000,
|
||||
waitQueueTimeoutMS: 50000,
|
||||
socketTimeoutMS: 30000,
|
||||
tls: false,
|
||||
family: 4
|
||||
})
|
||||
}
|
||||
static init() {
|
||||
this.connect();
|
||||
this.eventManager();
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import ConfigHelper from '../helpers/ConfigHelper.js';
|
||||
const config = ConfigHelper.readConfig();
|
||||
type ChannelList = keyof typeof config.mainServer.channels;
|
||||
export default class HookMgr {
|
||||
protected static async channelFetch(client:TClient, channel:ChannelList) {
|
||||
return await client.channels.fetch(config.mainServer.channels[channel]) as Discord.TextChannel;
|
||||
}
|
||||
protected static async fetch(client:TClient, channel:ChannelList, webhookId:Discord.Snowflake) {
|
||||
const hookInstance = await (await this.channelFetch(client, channel)).fetchWebhooks().then(x=>x.find(y=>y.id===webhookId));
|
||||
if (!hookInstance) throw new Error('[HookManager] Webhook not found.');
|
||||
return hookInstance;
|
||||
}
|
||||
static async send(client:TClient, channel:ChannelList, webhookId:Discord.Snowflake, message:string|Discord.MessagePayload|Discord.WebhookMessageCreateOptions) {
|
||||
const hook = await this.fetch(client, channel, webhookId);
|
||||
return hook.send(message).catch(err=>(client.channels.resolve(config.mainServer.channels.errors) as Discord.TextChannel).send(`Failed to send a webhook message in #${channel}:\n\`\`\`\n${err.message}\n\`\`\``));
|
||||
}
|
||||
static async edit(client:TClient, channel:ChannelList, webhookId:Discord.Snowflake, messageId:Discord.Snowflake, message:string|Discord.MessagePayload|Discord.WebhookMessageEditOptions) {
|
||||
const hook = await this.fetch(client, channel, webhookId);
|
||||
return hook.editMessage(messageId, message).catch(err=>(client.channels.resolve(config.mainServer.channels.errors) as Discord.TextChannel).send(`Failed to edit a webhook message in #${channel}:\n\`\`\`\n${err.message}\n\`\`\``));
|
||||
}
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
interface TServer {
|
||||
ip: string
|
||||
code: string
|
||||
}
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import FormatPlayer from '../helpers/FormatPlayer.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import HookMgr from './HookManager.js';
|
||||
import {writeFileSync, readFileSync} from 'node:fs';
|
||||
import {FSPlayer, FSData, FSCareerSavegame} from '../typings/interfaces';
|
||||
|
||||
export default async(client:TClient, Channel:string, Message:string, Server:TServer, ServerName:string)=>{
|
||||
let playerData:Array<string> = [];
|
||||
let dataUnavailable = 'Unavailable'
|
||||
const msg = await (client.channels.resolve(Channel) as Discord.TextChannel).messages.fetch(Message);
|
||||
const serverErrorEmbed = new client.embed().setColor(client.config.embedColorRed).setTitle('Host did not respond back in time');
|
||||
const genericEmbed = new client.embed();
|
||||
const refreshIntervalText = 'Refreshes every 35 seconds.';
|
||||
let sessionInit = {signal: AbortSignal.timeout(8500),headers:{'User-Agent':`${client.user.username} - MPModule/undici`}};
|
||||
|
||||
try {
|
||||
const hitDSS = await fetch(Server.ip+'/feed/dedicated-server-stats.json?code='+Server.code, sessionInit).then(r=>r.json() as Promise<FSData>);
|
||||
const hitCSG = await fetch(Server.ip+'/feed/dedicated-server-savegame.html?code='+Server.code+'&file=careerSavegame', sessionInit).then(async r=>(new client.fxp.XMLParser({ignoreAttributes: false, attributeNamePrefix: ''}).parse(await r.text()) as any).careerSavegame as FSCareerSavegame);
|
||||
|
||||
if (!hitDSS ?? !hitCSG){
|
||||
if (hitDSS && !hitDSS.slots) return Logger.forwardToConsole('log', 'MPModule', `DSS failed with unknown slots table for ${client.MPServerCache[ServerName].name}`);
|
||||
else return msg.edit({embeds: [serverErrorEmbed]});
|
||||
}
|
||||
|
||||
// Truncate unnecessary parts of the name for the MPServerCache
|
||||
// This is a mess, but it works.
|
||||
for (const filter of ['Official Daggerwin Game Server', 'Daggerwin Multifarm']) {
|
||||
if (hitDSS.server?.name === undefined) return;
|
||||
if (hitDSS.server?.name.includes(filter)) client.MPServerCache[ServerName].name = ['Daggerwin', 'DagMF'][['Official Daggerwin Game Server', 'Daggerwin Multifarm'].indexOf(filter)];
|
||||
}
|
||||
|
||||
//Timescale formatting
|
||||
function formatTimescale(number:number,digits:number,icon:string){
|
||||
var n = Number(number);
|
||||
return n.toLocaleString(undefined, {minimumFractionDigits: digits})+icon
|
||||
}
|
||||
|
||||
// Join/Leave log
|
||||
function playerLogEmbed(player:FSPlayer,joinLog:boolean){
|
||||
const logEmbed = new client.embed().setDescription(`**${player.name}${FormatPlayer.decoratePlayerIcons(player)}** ${joinLog ? 'joined' : 'left'} **${client.MPServerCache[ServerName].name}** at <t:${Math.round(Date.now()/1000)}:t>`);
|
||||
if (joinLog) return logEmbed.setColor(client.config.embedColorGreen);
|
||||
else if (player.uptime > 0) return logEmbed.setColor(client.config.embedColorRed).setFooter({text:`Farmed for ${FormatPlayer.uptimeFormat(player.uptime)}`});
|
||||
else return logEmbed.setColor(client.config.embedColorRed);
|
||||
}
|
||||
|
||||
const serverLog = client.channels.resolve(client.config.mainServer.channels.fs_server_log) as Discord.TextChannel;
|
||||
const playersOnServer = hitDSS.slots?.players.filter(x=>x.isUsed);
|
||||
const playersInCache = client.MPServerCache[ServerName].players;
|
||||
if (!playersOnServer ?? playersOnServer === undefined) return Logger.forwardToConsole('log', 'MPModule', 'Array is empty, ignoring...'); // For the love of god, stop throwing errors everytime.
|
||||
playersOnServer.forEach(player=>playerData.push(`**${player.name}${FormatPlayer.decoratePlayerIcons(player)}**\nFarming for ${FormatPlayer.uptimeFormat(player.uptime)}`));
|
||||
|
||||
// Player leaving
|
||||
for (const player of playersInCache.filter(x=>!playersOnServer.some(y=>y.name === x.name))){
|
||||
if (player.uptime > 0) serverLog.send({embeds:[playerLogEmbed(player,false)]});
|
||||
} // Player joining
|
||||
let playerObject;
|
||||
if (!playersInCache.length && client.uptime > 32010) playerObject = playersOnServer;
|
||||
if (playerObject) for (const player of playerObject) serverLog.send({embeds:[playerLogEmbed(player,true)]});
|
||||
else if (playersInCache.length) playerObject = playersOnServer.filter(x=>!playersInCache.some(y=>y.name === x.name));
|
||||
|
||||
if (client.MPServerCache[ServerName].name === null) return;
|
||||
const Database:Array<number> = JSON.parse(readFileSync(`src/database/${client.MPServerCache[ServerName].name}PlayerData.json`,{encoding:'utf8',flag:'r+'}));
|
||||
Database.push(hitDSS.slots?.used);
|
||||
writeFileSync(`src/database/${client.MPServerCache[ServerName].name}PlayerData.json`, JSON.stringify(Database));
|
||||
client.MPServerCache[ServerName].players = playersOnServer;
|
||||
|
||||
if (hitDSS.server.name.length < 1) {
|
||||
msg.edit({content: 'This embed will resume when server is back online.', embeds: [genericEmbed.setColor(client.config.embedColorRed).setTitle('The server seems to be offline.')]});
|
||||
client.MPServerCache[ServerName].status = 'offline'
|
||||
} else {
|
||||
client.MPServerCache[ServerName].status = 'online';
|
||||
const serverDetails = new client.embed().setColor(client.config.embedColor).setTitle('Server details').setFields(
|
||||
{name: 'Current map', value: hitDSS?.server?.mapName === undefined ? dataUnavailable : hitDSS.server.mapName, inline: true},
|
||||
{name: 'Server version', value: hitDSS?.server?.version === undefined ? dataUnavailable : hitDSS.server.version, inline: true},
|
||||
{name: 'In-game Time', value: `${('0'+Math.floor((hitDSS.server.dayTime/3600/1000))).slice(-2)}:${('0'+Math.floor((hitDSS.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true},
|
||||
{name: 'Slot Usage', value: isNaN(Number(hitCSG?.slotSystem?.slotUsage)) === true ? dataUnavailable : Number(hitCSG.slotSystem?.slotUsage).toLocaleString('en-us'), inline: true},
|
||||
{name: 'Autosave Interval', value: isNaN(Number(hitCSG?.settings?.autoSaveInterval)) === true ? dataUnavailable : Number(hitCSG.settings?.autoSaveInterval).toFixed(0)+' mins', inline:true},
|
||||
{name: 'Timescale', value: isNaN(Number(hitCSG?.settings?.timeScale)) === true ? dataUnavailable : formatTimescale(Number(hitCSG.settings?.timeScale), 0, 'x'), inline: true}
|
||||
);
|
||||
const playersEmbed = new client.embed().setColor(client.config.embedColor).setTitle(hitDSS.server.name).setDescription(hitDSS.slots.used < 1 ? '*No players online*' : playerData.join('\n\n')).setAuthor({name:`${hitDSS.slots.used}/${hitDSS.slots.capacity}`});
|
||||
msg.edit({content:refreshIntervalText,embeds:[serverDetails, playersEmbed]});
|
||||
}
|
||||
|
||||
// #multifarm_chat webhook
|
||||
const growthModeTextMap = {
|
||||
'1': 'Yes',
|
||||
'2': 'No',
|
||||
'3': 'Growth paused'
|
||||
}
|
||||
const growthModeText = growthModeTextMap[hitCSG?.settings.growthMode] ?? dataUnavailable;
|
||||
|
||||
function genericMapping<T>(map: Record<string, T>, key: string, defaultValue: T): T {
|
||||
return map[key] ?? defaultValue;
|
||||
}
|
||||
const genericTextMap = {
|
||||
'false': 'Off',
|
||||
'true': 'On'
|
||||
}
|
||||
|
||||
const fuelUsageTextMap = {
|
||||
'1': 'Low',
|
||||
'2': 'Normal',
|
||||
'3': 'High'
|
||||
}
|
||||
const fuelUsageText = fuelUsageTextMap[hitCSG?.settings.fuelUsage] ?? dataUnavailable;
|
||||
|
||||
const dirtIntervalTextMap = {
|
||||
'1': 'Off',
|
||||
'2': 'Slow',
|
||||
'3': 'Normal',
|
||||
'4': 'Fast'
|
||||
}
|
||||
const dirtIntervalText = dirtIntervalTextMap[hitCSG?.settings.dirtInterval] ?? dataUnavailable;
|
||||
// Edit the embed in #multifarm_chat
|
||||
HookMgr.edit(client, 'mf_chat', '1159998634604109897', '1160098458997370941', {
|
||||
content: refreshIntervalText,
|
||||
embeds: [new client.embed().setColor(client.config.embedColor).setTitle('Savegame Settings').addFields(
|
||||
{name: 'Seasonal Growth', value: growthModeText, inline: true},
|
||||
{name: 'Crop Destruction', value: genericMapping(genericTextMap, hitCSG?.settings.fruitDestruction, dataUnavailable), inline: true},
|
||||
{name: 'Periodic Plowing', value: genericMapping(genericTextMap, hitCSG?.settings.plowingRequiredEnabled, dataUnavailable), inline: true},
|
||||
{name: 'Stones', value: genericMapping(genericTextMap, hitCSG?.settings.stonesEnabled, dataUnavailable), inline: true},
|
||||
{name: 'Lime', value: genericMapping(genericTextMap, hitCSG?.settings.limeRequired, dataUnavailable), inline: true},
|
||||
{name: 'Weeds', value: genericMapping(genericTextMap, hitCSG?.settings.weedsEnabled, dataUnavailable), inline: true},
|
||||
{name: 'Fuel Usage', value: fuelUsageText, inline: true},
|
||||
{name: 'Dirt Interval', value: dirtIntervalText, inline: true},
|
||||
).setFooter({text: 'Last updated'}).setTimestamp()]
|
||||
});
|
||||
} catch(err) {
|
||||
msg.edit({content: err.message, embeds: [serverErrorEmbed]});
|
||||
Logger.forwardToConsole('log', 'MPModule', `Failed to make a request for ${ServerName}: ${err.message}`);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
export default async(client:TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>, type: string)=>{
|
||||
if (!MessageTool.isStaff(interaction.member as Discord.GuildMember)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||
|
||||
const time = interaction.options.getString('time') ?? undefined;
|
||||
const reason = interaction.options.getString('reason') ?? 'Reason unspecified';
|
||||
const GuildMember = interaction.options.getMember('member') ?? undefined;
|
||||
const User = interaction.options.getUser('member', true);
|
||||
|
||||
Logger.forwardToConsole('log', 'PunishmentLog', `${GuildMember?.user?.username ?? User?.username ?? 'No user data'} ${time ? ['warn', 'kick'].includes(type) ? 'and no duration set' : `and ${time} (duration)` : ''} was used in /${interaction.commandName} for ${reason}`);
|
||||
(client.channels.cache.get(client.config.mainServer.channels.punishment_log) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColor).setAuthor({name: interaction?.user?.username, iconURL: interaction?.user?.displayAvatarURL({size:2048})}).setTitle('Punishment Log').setDescription(`${GuildMember?.user?.username ?? User?.username ?? 'No user data'} ${time ? ['warn', 'kick'].includes(client.punishments.type) ? 'and no duration set' : `and ${time} (duration)` : ''} was used in \`/${interaction.commandName}\` for \`${reason}\``).setTimestamp()]});
|
||||
if (interaction.user.id === User.id) return interaction.reply(`You cannot ${type} yourself.`);
|
||||
if (!GuildMember && !['unban', 'ban'].includes(type)) return interaction.reply(`You cannot ${type} someone who is not in the server.`);
|
||||
if (User.bot) return interaction.reply(`You cannot ${type} a bot!`);
|
||||
|
||||
await interaction.deferReply();
|
||||
await client.punishments.addPunishment(type, {time, interaction}, interaction.user.id, reason, User, GuildMember);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import {TextChannel} from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import TSClient from '../helpers/TSClient.js';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
import CacheServer from './CacheServer.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
|
||||
export default async(client:TClient, YTChannelID:string, YTChannelName:string, DiscordChannelID:string, DiscordRoleID:string)=>{
|
||||
let Data: any;
|
||||
let cacheExpiry: number = 7200; // Invalidate cache after sitting in Redis for 2 hours
|
||||
try {
|
||||
await fetch(`https://youtube.googleapis.com/youtube/v3/activities?part=snippet&channelId=${YTChannelID}&maxResults=2&key=${(await TSClient.Token()).youtube}`, {
|
||||
signal: AbortSignal.timeout(10000),
|
||||
headers: {'User-Agent':'Daggerbot - Notification/undici'},
|
||||
}).then(async json=>Data = await json.json());
|
||||
} catch (err) {
|
||||
Logger.forwardToConsole('log', 'YTModule', `Failed to fetch "${YTChannelName}" from YouTube`);
|
||||
}
|
||||
if (!Data) return;
|
||||
const getVideoId = (index:number)=>Data.items[index].snippet.thumbnails.default.url.split('/')[4];
|
||||
const videoUrl = `https://www.youtube.com/watch?v=${getVideoId(0)}`;
|
||||
const cacheKey = `YTCache:${YTChannelID}`;
|
||||
const cachedVideoId = await CacheServer.get(cacheKey);
|
||||
if (!cachedVideoId) {
|
||||
await CacheServer.set(cacheKey, getVideoId(0)).then(async()=>await CacheServer.expiry(cacheKey, cacheExpiry));
|
||||
return;
|
||||
}
|
||||
if (getVideoId(1) === cachedVideoId) {
|
||||
await CacheServer.delete(cacheKey).then(async()=>{
|
||||
await CacheServer.set(cacheKey, getVideoId(0)).then(async()=>await CacheServer.expiry(cacheKey, cacheExpiry))
|
||||
});
|
||||
|
||||
(client.channels.resolve(DiscordChannelID) as TextChannel).send({
|
||||
content: `${MessageTool.formatMention(DiscordRoleID, 'role')}\n**${YTChannelName}** just uploaded a video!\n${videoUrl}`,
|
||||
allowedMentions: {parse:['roles']},
|
||||
});
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import {parentPort} from 'node:worker_threads';
|
||||
import {execSync} from 'node:child_process';
|
||||
|
||||
async function commitHashes() {
|
||||
const localHash = execSync('git rev-parse HEAD').toString().trim().slice(0, 7);
|
||||
const remoteHash = execSync('git ls-remote origin HEAD').toString().split('\t')[0].slice(0, 7);
|
||||
return { localHash, remoteHash };
|
||||
}
|
||||
|
||||
commitHashes().then(data=>parentPort.postMessage(data))
|
@ -1,5 +1,5 @@
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {Config} from '../typings/interfaces';
|
||||
import {Config} from '../interfaces';
|
||||
export default class ConfigHelper {
|
||||
static loadConfig() {
|
||||
let importconfig:Config;
|
||||
@ -12,7 +12,6 @@ export default class ConfigHelper {
|
||||
}
|
||||
return importconfig;
|
||||
}
|
||||
static readConfig() {
|
||||
return JSON.parse(readFileSync(process.argv[2] ?? 'src/config.json', 'utf8')) as Config;
|
||||
}
|
||||
static readConfig =()=>JSON.parse(readFileSync(process.argv[2] ?? 'src/config.json', 'utf8')) as Config;
|
||||
static isDevMode =()=>this.readConfig().configName.includes('Beta');
|
||||
}
|
||||
|
30
src/helpers/FAQHelper.ts
Normal file
30
src/helpers/FAQHelper.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import Discord from 'discord.js';
|
||||
import MessageTool from './MessageTool.js';
|
||||
import ConfigHelper from './ConfigHelper.js';
|
||||
const config = ConfigHelper.readConfig();
|
||||
export default class FAQHelper {
|
||||
private static readonly errorMsg:string = 'Failed to send the message, please report to **Toast** if it continues.';
|
||||
private static ansiCodeblock=(...lines:string[])=>MessageTool.concatMessage('```ansi', ...lines, '```')
|
||||
public static async reply(interaction:Discord.ChatInputCommandInteraction, title:string|null, message:string, image:string|null, useEmbed:boolean=false) {
|
||||
if (useEmbed) return interaction.reply({embeds: [MessageTool.embedStruct(config.embedColor, title, message, image)]}).catch(err=>interaction.reply(this.errorMsg+'\n'+err))
|
||||
else return interaction.reply(message).catch(err=>interaction.reply(this.errorMsg+'\n'+err))
|
||||
}
|
||||
public static CDN=(filename:string)=>'https://cdn.toast-server.net/daggerwin/'+filename+'.png';
|
||||
public static youCanGetRole=(role:string, roleEmoji:string)=>`You can get the ${MessageTool.formatMention(config.dcServer.roles[role], 'role')} role from <#802283932430106624> by clicking :${roleEmoji}: button on a webhook's message.`;
|
||||
public static readonly verifyGameFiles = this.ansiCodeblock(
|
||||
'[34m[1mSteam[0m (Top panel)',
|
||||
'1. Go to your game library and right click on Farming Simulator 22',
|
||||
'2. Click on Properties and navigate to "Installed Files"',
|
||||
'3. Click on "Verify integrity of game files"',
|
||||
'4. Steam will scan your game installation directory and will re-download anything that is corrupted or tampered with.',
|
||||
'',
|
||||
'[37m[1mEpic Games[0m (Bottom panel)',
|
||||
'1. Go to your game library and click on 3 dots (...)',
|
||||
'2. Click on Manage and click on "Verify"',
|
||||
'3. Epic Launcher will scan your game installation directory and will re-download anything that is corrupted or tampered with.'
|
||||
)
|
||||
public static readonly linkMapping = {
|
||||
ballyspring: 'https://www.farming-simulator.com/mod.php?mod_id=270745',
|
||||
staffTicket: 'https://discord.com/channels/468835415093411861/942173932339986472/1054128182468546631',
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import Discord from 'discord.js';
|
||||
import MessageTool from './MessageTool.js';
|
||||
import ConfigHelper from './ConfigHelper.js';
|
||||
const config = ConfigHelper.readConfig();
|
||||
export default class FAQStore {
|
||||
protected static readonly errorMsg:string = 'Failed to send the message, please report to **Toast** if it continues.';
|
||||
static async reply(interaction:Discord.ChatInputCommandInteraction, title:string|null, message:string, image:string|null, useEmbed:boolean=false) {
|
||||
if (useEmbed) return interaction.reply({embeds: [MessageTool.embedStruct(config.embedColor, title, message, image)]}).catch(err=>interaction.reply(this.errorMsg+'\n'+err))
|
||||
else return interaction.reply(message).catch(err=>interaction.reply(this.errorMsg+'\n'+err))
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export default (bytes:number, decimals:number = 2)=>{
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return parseFloat((bytes / Math.pow(1024, i)).toFixed(decimals < 0 ? 0 : decimals))+ ' ' +['Bytes', 'KB', 'MB', 'GB', 'TB'][i]
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export default (number:number)=>{
|
||||
const suffixes = ['th', 'st', 'nd', 'rd'];
|
||||
const suffix = number % 100;
|
||||
return number + (suffixes[(suffix - 20) % 10] || suffixes[suffix] || suffixes[0]);
|
||||
}
|
@ -1,22 +1,26 @@
|
||||
import {FSPlayer} from '../typings/interfaces';
|
||||
|
||||
import {FSPlayer} from '../interfaces';
|
||||
export default class FormatPlayer {
|
||||
static uptimeFormat(playTime: number){
|
||||
var Hours = 0;
|
||||
playTime = Math.floor(Number(playTime));
|
||||
if(playTime >= 60){
|
||||
var Hours = Math.floor(Number(playTime)/60);
|
||||
var Minutes = (Number(playTime)-(Hours*60));
|
||||
} else Minutes = Number(playTime)
|
||||
if(Hours >= 24){
|
||||
var Days = Math.floor(Number(Hours)/24);
|
||||
var Hours = (Hours-(Days*24));
|
||||
} return (Days > 0 ? Days+' d ':'')+(Hours > 0 ? Hours+' h ':'')+(Minutes > 0 ? Minutes+' m':'')
|
||||
static convertUptime(playTime:number) {
|
||||
let Minutes:number;
|
||||
let Hours:number;
|
||||
let Days:number;
|
||||
|
||||
playTime = Math.floor(playTime);
|
||||
if (playTime >= 60) {
|
||||
Hours = Math.floor(playTime/60);
|
||||
Minutes = playTime-Hours*60;
|
||||
} else Minutes = playTime
|
||||
|
||||
if (Hours >= 24) {
|
||||
Days = Math.floor(Hours/24);
|
||||
Hours = Hours-Days*24;
|
||||
}
|
||||
|
||||
return (Days > 0 ? Days+' d ':'')+(Hours > 0 ? Hours+' h ':'')+(Minutes > 0 ? Minutes+' m':'')
|
||||
}
|
||||
static decoratePlayerIcons(player:FSPlayer){
|
||||
static decoratePlayerIcons(player:FSPlayer) {
|
||||
let decorator = player.isAdmin ? ':detective:' : '';
|
||||
decorator += player.name.includes('Toast') ? '<:toast:1132681026662056079>' : '';
|
||||
decorator += player.name.includes('Daggerwin') ? '<:Daggerwin:549283056079339520>' : ''; // Probably useless lol, but we'll see.
|
||||
return decorator
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
interface formatTimeOpt {
|
||||
longNames: boolean,
|
||||
commas: boolean
|
||||
}
|
||||
|
||||
export default (integer:number, accuracy:number = 1, options?:formatTimeOpt)=>{
|
||||
let achievedAccuracy = 0;
|
||||
let text:any = '';
|
||||
for (const timeName of [
|
||||
{name: 'year', length: 31536000000},
|
||||
{name: 'month', length: 2592000000},
|
||||
{name: 'week', length: 604800000},
|
||||
{name: 'day', length: 86400000},
|
||||
{name: 'hour', length: 3600000},
|
||||
{name: 'minute', length: 60000},
|
||||
{name: 'second', length: 1000}
|
||||
]){
|
||||
if (achievedAccuracy < accuracy){
|
||||
const fullTimelengths = Math.floor(integer/timeName.length);
|
||||
if (fullTimelengths === 0) continue;
|
||||
achievedAccuracy++;
|
||||
text += fullTimelengths + (options?.longNames ? (' '+timeName.name+(fullTimelengths === 1 ? '' : 's')) : timeName.name.slice(0, timeName.name === 'month' ? 2 : 1)) + (options?.commas ? ', ' : ' ');
|
||||
integer -= fullTimelengths*timeName.length;
|
||||
} else break;
|
||||
}
|
||||
if (text.length === 0) text = integer + (options?.longNames ? ' milliseconds' : 'ms') + (options?.commas ? ', ' : '');
|
||||
if (options?.commas){
|
||||
text = text.slice(0, -2);
|
||||
if (options?.longNames){
|
||||
text = text.split('');
|
||||
text[text.lastIndexOf(',')] = ' and';
|
||||
text = text.join('');
|
||||
}
|
||||
} return text.trim();
|
||||
}
|
50
src/helpers/Formatters.ts
Normal file
50
src/helpers/Formatters.ts
Normal file
@ -0,0 +1,50 @@
|
||||
export default class Formatters {
|
||||
public static timeFormat(int:number, accuracy:number = 1, opts?:{longNames?:boolean, commas?:boolean}) {
|
||||
let achievedAccuracy:number = 0;
|
||||
let txt:string = '';
|
||||
for (const timeName of [
|
||||
{name: 'year', length: 31536000000},
|
||||
{name: 'month', length: 2592000000},
|
||||
{name: 'week', length: 604800000},
|
||||
{name: 'day', length: 86400000},
|
||||
{name: 'hour', length: 3600000},
|
||||
{name: 'minute', length: 60000},
|
||||
{name: 'second', length: 1000}
|
||||
]) {
|
||||
if (achievedAccuracy < accuracy) {
|
||||
const fullTimeLen = Math.floor(int/timeName.length);
|
||||
if (fullTimeLen === 0) continue;
|
||||
achievedAccuracy++;
|
||||
txt += fullTimeLen + (opts?.longNames ? (' '+timeName.name+(fullTimeLen === 1 ? '' : 's')) : timeName.name.slice(0, timeName.name === 'month' ? 2 : 1)) + (opts?.commas ? ', ' : ' ');
|
||||
int -= fullTimeLen*timeName.length;
|
||||
} else break;
|
||||
}
|
||||
if (txt.length === 0) txt = int + (opts?.longNames ? ' milliseconds' : 'ms') + (opts?.commas ? ', ' : '');
|
||||
if (opts?.commas) {
|
||||
txt = txt.slice(0, -2);
|
||||
if (opts?.longNames) {
|
||||
const txtArr = txt.split('');
|
||||
txtArr[txt.lastIndexOf(',')] = ' and';
|
||||
txt = txtArr.join('');
|
||||
}
|
||||
} return txt.trim();
|
||||
}
|
||||
public static DayOfTheYear(num:number) {
|
||||
const suffixes = ['th', 'st', 'nd', 'rd'];
|
||||
const suffix = num % 100;
|
||||
return num +(suffixes[(suffix-20)%10]||suffixes[suffix]||suffixes[0])
|
||||
}
|
||||
public static byteFormat(bytes:number) {
|
||||
if (bytes === null ?? bytes === undefined ?? bytes <= 0) return '0 Bytes';
|
||||
let scaleCount = 0;
|
||||
let dataInitials = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
while (bytes >= 1024 && scaleCount < dataInitials.length -1) {
|
||||
bytes /= 1024;
|
||||
scaleCount++;
|
||||
}
|
||||
if (scaleCount >= dataInitials.length) scaleCount = dataInitials.length -1;
|
||||
let compactedBytes = bytes.toFixed(2).replace(/\.?0+$/, '').replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
compactedBytes += ' '+dataInitials[scaleCount];
|
||||
return compactedBytes.trim();
|
||||
}
|
||||
}
|
17
src/helpers/GitHub.ts
Normal file
17
src/helpers/GitHub.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {Octokit} from '@octokit/rest';
|
||||
import {createTokenAuth} from '@octokit/auth-token';
|
||||
import TSClient from './TSClient.js';
|
||||
import git from 'simple-git';
|
||||
const summonAuth = createTokenAuth((await TSClient()).octokit);
|
||||
const octokit = new Octokit({auth: await summonAuth().token, timeZone: 'Australia/NSW', userAgent: 'Daggerbot-TS'});
|
||||
export default class GitHub {
|
||||
private static repositoryConfig = {owner: 'AnxietyisReal', repo: 'Daggerbot-TS', ref: 'HEAD'};
|
||||
public static async RemoteRepository() {
|
||||
const {data} = await octokit.repos.getCommit(this.repositoryConfig);
|
||||
return data;
|
||||
}
|
||||
public static async LocalRepository() {
|
||||
const {latest} = await git().log({maxCount: 1});
|
||||
return latest;
|
||||
}
|
||||
}
|
@ -1,13 +1,5 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default class Logger {
|
||||
static logTime() {
|
||||
return `[${dayjs().format('DD/MM/YY HH:mm:ss')}]`;
|
||||
}
|
||||
static logPrefix(prefix:string) {
|
||||
return `[${prefix}]`;
|
||||
}
|
||||
static forwardToConsole(logType:'log'|'error', prefix:string, message:string|any) {
|
||||
console[logType](this.logTime(), this.logPrefix(prefix), message);
|
||||
}
|
||||
public static console =(logType:'log'|'error', prefix:string, message:any)=>console[logType](`[${dayjs().format('DD/MM/YY HH:mm:ss')}]`, `[${prefix}]`, message);
|
||||
}
|
||||
|
@ -1,26 +1,17 @@
|
||||
import Discord from 'discord.js';
|
||||
import ConfigHelper from './ConfigHelper.js';
|
||||
const config = ConfigHelper.readConfig();
|
||||
type RoleKeys = keyof typeof config.mainServer.roles;
|
||||
type RoleKeys = keyof typeof config.dcServer.roles;
|
||||
|
||||
export default class MessageTool {
|
||||
static embedStruct(color:Discord.ColorResolvable, title:string, description?:string|null, image?:string|null){
|
||||
static embedStruct(color:Discord.ColorResolvable, title:string, description?:string|null, image?:string|null) {
|
||||
const embed = new Discord.EmbedBuilder().setColor(color).setTitle(title);
|
||||
if (description) embed.setDescription(description);
|
||||
if (image) embed.setImage(image);
|
||||
return embed
|
||||
}
|
||||
static concatMessage(...messages:string[]){
|
||||
return messages.join('\n')
|
||||
}
|
||||
static formatMention(mention:string, type:'user'|'channel'|'role'){
|
||||
return `<@${type === 'role' ? '&' : type === 'channel' ? '#' : ''}${mention}>`
|
||||
}
|
||||
static isStaff(guildMember:Discord.GuildMember){
|
||||
return config.mainServer.staffRoles.map((x:string)=>config.mainServer.roles[x]).some((x:string)=>guildMember.roles.cache.has(x));
|
||||
}
|
||||
static youNeedRole(interaction:Discord.CommandInteraction, role:RoleKeys){
|
||||
return interaction.reply(`This command is restricted to ${this.formatMention(config.mainServer.roles[role], 'role')}`);
|
||||
}
|
||||
static concatMessage =(...messages:string[])=>messages.join('\n');
|
||||
static formatMention =(mention:string, type:'user'|'channel'|'role')=>`<${type === 'role' ? '@&' : type === 'channel' ? '#' : '@'}${mention}>`;
|
||||
static isStaff =(guildMember:Discord.GuildMember)=>config.dcServer.staffRoles.map((x:string)=>config.dcServer.roles[x]).some((x:string)=>guildMember.roles.cache.has(x));
|
||||
static youNeedRole =(interaction:Discord.CommandInteraction, role:RoleKeys)=>interaction.reply(`You do not have ${this.formatMention(config.dcServer.roles[role], 'role')} role to use this command.`);
|
||||
}
|
||||
// I want to come up with better name instead of calling this file "MessageTool", but I am super bad at naming things.
|
||||
|
@ -1,12 +1,10 @@
|
||||
import {FSData} from 'src/typings/interfaces';
|
||||
import {FSData} from '../interfaces';
|
||||
|
||||
export default (serverEndpoint:FSData)=>{
|
||||
const getAmount =(type:string)=>serverEndpoint.vehicles.filter(v=>v.type === 'pallet').map(v=>v.fills).flat().map(t=>t.type).filter(t=>t===type).length;
|
||||
let palletTypeName = serverEndpoint.vehicles.filter(v=>v.type === 'pallet').map(v=>v.fills).flat().map(t=>t.type).filter((t,i,a)=>a.indexOf(t)===i).map(t=>({
|
||||
[t]:{
|
||||
name: t.toLowerCase().slice(0,1).toUpperCase()+t.toLowerCase().slice(1),
|
||||
size: getAmount(t.toUpperCase())
|
||||
},
|
||||
})).reduce((a,b)=>({...a,...b}));
|
||||
return palletTypeName;
|
||||
export default function(data:FSData) {
|
||||
const pallets = data.vehicles.filter(x=>x.category === 'PALLETS');
|
||||
const counts = pallets.reduce((acc, name)=>{
|
||||
acc[name.name] = (acc[name.name] ?? 0) + 1;
|
||||
return acc;
|
||||
}, {} as {[key:string]:number});
|
||||
return counts;
|
||||
}
|
||||
|
19
src/helpers/RanIntoHumor.ts
Normal file
19
src/helpers/RanIntoHumor.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export default ()=>{
|
||||
const ranIntoSomething = [
|
||||
'tree', 'rock',
|
||||
'wall', 'fence',
|
||||
'sign', 'car',
|
||||
'bike', 'pedestrian',
|
||||
'dog', 'cat',
|
||||
'cow', 'sheep',
|
||||
'bench', 'table',
|
||||
'chair', 'house',
|
||||
'building', 'skyscraper',
|
||||
'statue', 'lamp post',
|
||||
'traffic light', 'bridge',
|
||||
'fountain', 'dumpster',
|
||||
'mailbox', 'parking meter',
|
||||
'bus', 'truck'
|
||||
] as string[];
|
||||
return ranIntoSomething[Math.floor(Math.random()*ranIntoSomething.length)];
|
||||
}
|
@ -1,13 +1,3 @@
|
||||
interface TokenService_API {
|
||||
main: string,
|
||||
octokit: string,
|
||||
youtube: string,
|
||||
mongodb_uri: string,
|
||||
redis_uri: string
|
||||
}
|
||||
import TokenService from '@toast/tokenservice-client';
|
||||
|
||||
export default class TSClient {
|
||||
static async Token() {
|
||||
return await fetch('http://192.168.68.18/daggerbot').then(x=>x.json()) as Promise<TokenService_API>
|
||||
}
|
||||
}
|
||||
export default async()=>new TokenService('daggerbotbeta').connect();
|
||||
|
@ -1,6 +1,4 @@
|
||||
export default class UsernameHelper {
|
||||
static stripName(text: string){
|
||||
const dirSlash = process.platform === 'linux' ? '\/' : '\\';
|
||||
return text.replace(/(?<=\/Users\/|\/media\/)[^/]+/g, match=>'・'.repeat(match.length)).split(dirSlash).join(dirSlash);
|
||||
}
|
||||
export default (text:string)=>{
|
||||
const dirSlash = process.platform === 'linux' ? '\/' : '\\';
|
||||
return text?.replace(/(?<=\/Users\/|\/media\/)[^/]+/g, match=>'・'.repeat(match.length)).split(dirSlash).join(dirSlash);
|
||||
}
|
||||
|
71
src/index.ts
71
src/index.ts
@ -3,55 +3,60 @@ import TClient from './client.js';
|
||||
const client = new TClient;
|
||||
client.init();
|
||||
import Logger from './helpers/Logger.js';
|
||||
import YTModule from './funcs/YTModule.js';
|
||||
import MPModule from './funcs/MPModule.js';
|
||||
import YTModule from './modules/YTModule.js';
|
||||
import MPModule, {refreshTimerSecs} from './modules/MPModule.js';
|
||||
import UsernameHelper from './helpers/UsernameHelper.js';
|
||||
import {Punishment} from './typings/interfaces';
|
||||
import {writeFileSync, readFileSync} from 'node:fs';
|
||||
import {Punishment} from './interfaces';
|
||||
import {readFileSync} from 'node:fs';
|
||||
|
||||
// Error handler
|
||||
function DZ(error:Error, type:string){// Yes, I may have shiternet but I don't need to wake up to like a hundred messages or so.
|
||||
function _(error:Error, type:string) {
|
||||
if (JSON.parse(readFileSync('src/errorBlocklist.json', 'utf8')).includes(error.message)) return;
|
||||
console.error(error);
|
||||
(client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel | null)?.send({embeds: [new client.embed().setColor('#560000').setTitle('Error caught!').setFooter({text: 'Error type: ' + type}).setDescription(`**Error:**\n\`\`\`${error.message}\`\`\`**Stack:**\n\`\`\`${`${UsernameHelper.stripName(error.stack)}`.slice(0, 2500)}\`\`\``)]})
|
||||
(client.channels.resolve(client.config.dcServer.channels.errors) as Discord.TextChannel | null)?.send({embeds: [new client.embed().setColor('#560000').setTitle('Error caught!').setFooter({text: 'Error type: ' + type}).setDescription(`**Error:**\n\`\`\`${error.message}\`\`\`**Stack:**\n\`\`\`${`${UsernameHelper(error.stack)}`.slice(0, 2500)}\`\`\``)]})
|
||||
}
|
||||
process.on('unhandledRejection', (error: Error)=>DZ(error, 'unhandledRejection'));
|
||||
process.on('uncaughtException', (error: Error)=>DZ(error, 'uncaughtException'));
|
||||
process.on('error', (error: Error)=>DZ(error, 'nodeError'));
|
||||
client.on('error', (error: Error)=>DZ(error, 'clientError'));
|
||||
process.on('unhandledRejection', (error: Error)=>_(error, 'unhandledRejection'));
|
||||
process.on('uncaughtException', (error: Error)=>_(error, 'uncaughtException'));
|
||||
process.on('error', (error: Error)=>_(error, 'processError'));
|
||||
client.on('error', (error: Error)=>_(error, 'clientError'));
|
||||
|
||||
// YouTube Upload notification and MP loop
|
||||
if (client.config.botSwitches.mpstats) setInterval(async()=>{
|
||||
const serverlake = (await client.MPServer.findInCache(client.config.mainServer.id));
|
||||
for await (const [locName, locArea] of Object.entries(client.config.MPStatsLocation)) await MPModule(client, locArea.channel, locArea.message, serverlake[locName], locName)
|
||||
}, 35000); // 35 seconds
|
||||
setInterval(async()=>{
|
||||
YTModule(client, 'UCQ8k8yTDLITldfWYKDs3xFg', 'Daggerwin', '528967918772551702', '1155760735612305408'); // 528967918772551702 = #videos-and-streams; 1155760735612305408 = YT Upload Ping;
|
||||
YTModule(client, 'UCguI73--UraJpso4NizXNzA', 'Machinery Restorer', '767444045520961567', '1155760735612305408') // 767444045520961567 = #machinery-restorer; ^^
|
||||
}, 300000); // 5 minutes
|
||||
// Interval timers for modules
|
||||
setInterval(async()=>await MPModule(client), refreshTimerSecs); // Second param got moved to inside MPModule function to reduce the amount of failure rates.
|
||||
setInterval(()=>YTModule(client), 180000); // 3 minutes
|
||||
|
||||
// Event loop for punishments and daily msgs
|
||||
setInterval(async()=>{
|
||||
const now = Date.now();
|
||||
|
||||
const punishments = await client.punishments.findInCache();
|
||||
punishments.filter((x:Punishment)=>x.endTime && x.endTime<= now && !x.expired).forEach(async (punishment:Punishment)=>{
|
||||
Logger.forwardToConsole('log', 'Punishment', `${punishment.member}\'s ${punishment.type} should expire now`);
|
||||
Logger.forwardToConsole('log', 'Punishment', await client.punishments.removePunishment(punishment._id, client.user.id, 'Time\'s up!'));
|
||||
punishments.filter((x:Punishment)=>x.endTime && x.endTime <= now && !x.expired).forEach(async (punishment:Punishment)=>{
|
||||
Logger.console('log', 'Punishment', `${punishment.member}\'s ${punishment.type} should expire now`);
|
||||
Logger.console('log', 'Punishment', await client.punishments.punishmentRemove(punishment.case_id, client.user.id, 'Time\'s up!'));
|
||||
});
|
||||
|
||||
const formattedDate = Math.floor((now - client.config.LRSstart)/1000/60/60/24);
|
||||
const dailyMsgs = JSON.parse(readFileSync('./src/database/dailyMsgs.json', 'utf8'))
|
||||
if (client.config.botSwitches.dailyMsgsBackup && !dailyMsgs.some((x:Array<number>)=>x[0] === formattedDate)){
|
||||
client.userLevels.resetAllData(); // reset all data on 1st of January every year
|
||||
const dailyMsgs = await client.dailyMsgs.fetchDays();
|
||||
if (client.config.botSwitches.dailyMsgsBackup && !dailyMsgs.some(x=>x[0] === formattedDate)) {
|
||||
if (!dailyMsgs.find(x=>x.dataValues.day === formattedDate)) {
|
||||
let total = (await client.userLevels.fetchEveryone()).reduce((a,b)=>a + b.messages, 0); // Sum of all users
|
||||
const yesterday = dailyMsgs.find(x=>x.day === formattedDate - 1)
|
||||
if (total < yesterday?.total) total = yesterday.total; // Messages went down.
|
||||
await client.dailyMsgs.newDay(formattedDate, total);
|
||||
Logger.console('log', 'DailyMsgs', `Pushed [${formattedDate}, ${total}]`)
|
||||
|
||||
let total = (await client.userLevels._content.find({})).reduce((a,b)=>a + b.messages, 0); // sum of all users
|
||||
const yesterday = dailyMsgs.find((x:Array<number>)=>x[0] === formattedDate - 1);
|
||||
if (total < yesterday) total = yesterday // messages went down.
|
||||
dailyMsgs.push([formattedDate, total]);
|
||||
writeFileSync('./src/database/dailyMsgs.json', JSON.stringify(dailyMsgs))
|
||||
Logger.forwardToConsole('log', 'DailyMsgs', `Pushed [${formattedDate}, ${total}]`)
|
||||
client.guilds.cache.get(client.config.mainServer.id).commands.fetch().then(commands=>(client.channels.resolve(client.config.mainServer.channels.logs) as Discord.TextChannel).send(`:pencil: Pushed \`[${formattedDate}, ${total}]\` to </rank leaderboard:${commands.find(x=>x.name === 'rank').id}>`));
|
||||
(client.channels.resolve(client.config.mainServer.channels.thismeanswar) as Discord.TextChannel).send({files:['./src/database/dailyMsgs.json']}).catch(fileErr=>console.log(fileErr))
|
||||
// Send notification to #bot-logs that the data has been pushed to database.
|
||||
const commands = await client.guilds.cache.get(client.config.dcServer.id)?.commands.fetch();
|
||||
if (commands) (client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [
|
||||
new client.embed().setDescription(`Pushed the following\ndata to </rank leaderboard:${commands.find(x=>x.name === 'rank').id}>`).setFields(
|
||||
{name: 'Day', value: formattedDate.toString(), inline: true},
|
||||
{name: 'Messages', value: Intl.NumberFormat('en-us').format(total).toString(), inline: true}
|
||||
).setColor(client.config.embedColor)
|
||||
]});
|
||||
else Logger.console('log', 'DailyMsgs', 'Rank command not found, cannot send notification in channel')
|
||||
}
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
if (client.config.botSwitches.dailyMsgsBackup) client.userLevels.initSelfdestruct()
|
||||
// Initiate the nuke on userLevels and dailyMsgs tables
|
||||
// Also don't ask why it's outside the interval
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Discord from 'discord.js';
|
||||
import {ColorResolvable, PresenceData} from 'discord.js';
|
||||
|
||||
export interface Punishment {
|
||||
_id: number;
|
||||
case_id: number;
|
||||
type: string;
|
||||
member: string;
|
||||
moderator: string;
|
||||
@ -27,9 +27,9 @@ export interface FSData {
|
||||
slots: {
|
||||
capacity: number,
|
||||
used: number,
|
||||
players: Array<FSPlayer>
|
||||
players: FSPlayer[]
|
||||
},
|
||||
vehicles: Array<FSVehicle>
|
||||
vehicles: FSVehicle[]
|
||||
}
|
||||
interface FSVehicle {
|
||||
name: string,
|
||||
@ -38,7 +38,7 @@ interface FSVehicle {
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
fills: Array<FSVehicleFill>,
|
||||
fills: FSVehicleFill[],
|
||||
controller: string
|
||||
}
|
||||
interface FSVehicleFill {
|
||||
@ -46,7 +46,7 @@ interface FSVehicleFill {
|
||||
level: number
|
||||
}
|
||||
export interface FSPlayer {
|
||||
isUsed: boolean,
|
||||
isUsed?: boolean,
|
||||
isAdmin: boolean,
|
||||
uptime: number,
|
||||
name: string
|
||||
@ -108,42 +108,31 @@ export interface FSCareerSavegame {
|
||||
}
|
||||
export interface Config {
|
||||
configName: string,
|
||||
embedColor: Discord.ColorResolvable,
|
||||
embedColorGreen: Discord.ColorResolvable,
|
||||
embedColorOrange: Discord.ColorResolvable,
|
||||
embedColorYellow: Discord.ColorResolvable,
|
||||
embedColorRed: Discord.ColorResolvable,
|
||||
embedColorBCA: Discord.ColorResolvable,
|
||||
embedColorXmas: Discord.ColorResolvable,
|
||||
embedColor: ColorResolvable,
|
||||
embedColorGreen: ColorResolvable,
|
||||
embedColorYellow: ColorResolvable,
|
||||
embedColorRed: ColorResolvable,
|
||||
embedColorInvis: ColorResolvable,
|
||||
embedColorBCA: ColorResolvable,
|
||||
embedColorXmas: ColorResolvable,
|
||||
LRSstart: number,
|
||||
whitelistedServers: Array<string>,
|
||||
MPStatsLocation: {
|
||||
mainServer: {
|
||||
channel: string
|
||||
message: string
|
||||
},
|
||||
secondServer: {
|
||||
channel: string
|
||||
message: string
|
||||
}
|
||||
},
|
||||
whitelistedServers: string[],
|
||||
botSwitches: {
|
||||
dailyMsgsBackup: boolean,
|
||||
registerCommands: boolean,
|
||||
commands: boolean,
|
||||
logs: boolean,
|
||||
mpSys: boolean,
|
||||
buttonRoles: boolean,
|
||||
automod: boolean,
|
||||
mpstats: boolean,
|
||||
autores: boolean
|
||||
},
|
||||
botPresence: Discord.PresenceData,
|
||||
eval: boolean,
|
||||
whitelist: Array<string>,
|
||||
contribList: Array<string>,
|
||||
mainServer: {
|
||||
botPresence: PresenceData,
|
||||
whitelist: string[],
|
||||
contribList: string[],
|
||||
dcServer: {
|
||||
id: string,
|
||||
staffRoles: Array<string>,
|
||||
staffRoles: string[],
|
||||
roles: {
|
||||
admin: string,
|
||||
bottech: string,
|
||||
@ -158,7 +147,6 @@ export interface Config {
|
||||
vtcmember: string
|
||||
},
|
||||
channels: {
|
||||
console: string,
|
||||
errors: string,
|
||||
thismeanswar: string,
|
||||
bot_suggestions: string,
|
||||
@ -167,10 +155,9 @@ export interface Config {
|
||||
welcome: string,
|
||||
botcommands: string,
|
||||
bankick_log: string,
|
||||
fs_server_log: string,
|
||||
punishment_log: string,
|
||||
dcmod_chat: string,
|
||||
mf_chat: string
|
||||
mpmod_chat: string
|
||||
}
|
||||
}
|
||||
}
|
9
src/models/IMPORTS.ts
Normal file
9
src/models/IMPORTS.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export * from './MPServer.js';
|
||||
export * from './dailyMsgs.js';
|
||||
export * from './bonkCount.js';
|
||||
export * from './userLevels.js';
|
||||
export * from './punishments.js';
|
||||
export * from './prohibitedWords.js';
|
||||
export * from './suggestions.js';
|
||||
export * from './tagSystem.js';
|
||||
export * from './ytChannels.js';
|
@ -1,45 +1,131 @@
|
||||
import TClient from '../client.js';
|
||||
import mongoose from 'mongoose';
|
||||
import CacheServer from '../funcs/CacheServer.js';
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
import CacheServer from '../components/CacheServer.js';
|
||||
|
||||
const Schema = mongoose.model('mpserver', new mongoose.Schema({
|
||||
_id: {type: String, required:true},
|
||||
mainServer: {required:true, type: new mongoose.Schema({
|
||||
ip: {type: String, required:true},
|
||||
code: {type: String, required:true}
|
||||
}, {versionKey: false})},
|
||||
secondServer: {required:true, type: new mongoose.Schema({
|
||||
ip: {type: String, required:true},
|
||||
code: {type: String, required:true}
|
||||
}, {versionKey: false})},
|
||||
}, {versionKey: false}));
|
||||
class MPServer extends Model {
|
||||
declare public serverName: string;
|
||||
declare public isActive: boolean;
|
||||
declare public ip: string;
|
||||
declare public code: string;
|
||||
declare public playerData: number[];
|
||||
}
|
||||
|
||||
export default class MPServer extends Schema {
|
||||
client: TClient;
|
||||
_content: typeof Schema;
|
||||
constructor(client:TClient){
|
||||
super();
|
||||
this.client = client;
|
||||
this._content = Schema;
|
||||
}
|
||||
async findInCache(query:any): Promise<any> {
|
||||
const cacheKey = `MPServer:${query}`;
|
||||
const cachedResult = await CacheServer.get(cacheKey);
|
||||
let result;
|
||||
if (cachedResult) {
|
||||
try {
|
||||
result = cachedResult;
|
||||
} catch (error) {
|
||||
console.error('Error parsing cached result:', error);
|
||||
result = await this._content.findById(query);
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 1800);
|
||||
export interface IServer {
|
||||
serverName: string
|
||||
isActive: boolean
|
||||
ip: string
|
||||
code: string
|
||||
playerData: number[]
|
||||
}
|
||||
|
||||
const cacheKey = 'MPServer';
|
||||
|
||||
export class MPServerSvc {
|
||||
private model: typeof MPServer;
|
||||
|
||||
constructor() {
|
||||
this.model = MPServer;
|
||||
this.model.init({
|
||||
serverName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false
|
||||
},
|
||||
ip: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
playerData: {
|
||||
type: DataTypes.ARRAY(DataTypes.INTEGER),
|
||||
allowNull: true,
|
||||
}
|
||||
}, {
|
||||
tableName: 'mpserver',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq,
|
||||
})
|
||||
this.model.sync();
|
||||
}
|
||||
async getServers() {
|
||||
return await this.model.findAll();
|
||||
}
|
||||
async fetchPlayerData(serverName:string) {
|
||||
const findServerByName = await this.model.findOne({where: {serverName: serverName}});
|
||||
if (findServerByName) return findServerByName.dataValues.playerData;
|
||||
else return [];
|
||||
}
|
||||
async addServer(serverName:string, ip:string, code:string) {
|
||||
const findServerByName = await this.model.findOne({where: {serverName: serverName}});
|
||||
if (findServerByName) {
|
||||
(await findServerByName.update({serverName: serverName, ip: ip, code: code})).save();
|
||||
await CacheServer.delete(cacheKey).then(async()=>await this.findInCache());
|
||||
} else {
|
||||
result = await this._content.findById(query);
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 1800);
|
||||
await this.model.create({
|
||||
serverName: serverName,
|
||||
isActive: true,
|
||||
ip: ip,
|
||||
code: code,
|
||||
playerData: []
|
||||
});
|
||||
await CacheServer.delete(cacheKey).then(async()=>await this.findInCache());
|
||||
}
|
||||
}
|
||||
async removeServer(serverName:string) {
|
||||
const findServerByName = await this.model.findOne({where: {serverName: serverName}});
|
||||
if (findServerByName) {
|
||||
await this.model.destroy({where: {serverName: serverName}});
|
||||
await CacheServer.delete(cacheKey).then(async()=>await this.findInCache());
|
||||
}
|
||||
}
|
||||
async toggleServerUsability(serverName:string, isActive:boolean) {
|
||||
const findServerByName = await this.model.findOne({where: {serverName: serverName}});
|
||||
if (findServerByName) {
|
||||
this.model.update({isActive: isActive}, {where: {serverName: serverName}}).then(async flagUpdated=>{
|
||||
if (flagUpdated) {
|
||||
await CacheServer.delete(cacheKey).then(async()=>await this.findInCache());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else return false;
|
||||
}
|
||||
async incrementPlayerCount(serverName:string, playerCount:number) {
|
||||
const findServerByName = await this.model.findOne({where: {serverName: serverName}});
|
||||
if (findServerByName) {
|
||||
let PD = findServerByName.dataValues.playerData;
|
||||
if (PD.length > 256) PD = [];
|
||||
PD.push(playerCount);
|
||||
const updatePD = await this.model.update({playerData: PD}, {where: {serverName: serverName}});
|
||||
if (updatePD) true;
|
||||
else return false;
|
||||
} else return false;
|
||||
}
|
||||
async findInCache(): Promise<IServer[]> {
|
||||
const cachedResult = await CacheServer.getJSON(cacheKey);
|
||||
let result;
|
||||
if (cachedResult) result = cachedResult;
|
||||
else {
|
||||
result = await this.model.findAll();
|
||||
CacheServer.setJSON(cacheKey, result).then(()=>CacheServer.expiry(cacheKey, 1800));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async getServerData(serverName:string):Promise<IServer> {
|
||||
return new Promise(async resolve=>{
|
||||
const serverInfo = await this.findInCache();
|
||||
|
||||
for (let i = 0; i < serverInfo.length; i++) {
|
||||
if (serverInfo[i].serverName === serverName) resolve(serverInfo[i]);
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
import TClient from '../client.js';
|
||||
import mongoose from 'mongoose';
|
||||
import CacheServer from '../funcs/CacheServer.js';
|
||||
|
||||
const Schema = mongoose.model('bannedWords', new mongoose.Schema({
|
||||
_id: {type: String, required:true}
|
||||
}, {versionKey: false}));
|
||||
|
||||
export default class bannedWords extends Schema {
|
||||
client: TClient;
|
||||
_content: typeof Schema;
|
||||
constructor(client:TClient){
|
||||
super();
|
||||
this.client = client;
|
||||
this._content = Schema;
|
||||
}
|
||||
async findInCache(): Promise<any> {
|
||||
const cacheKey = 'bannedWords';
|
||||
const cachedResult = await CacheServer.get(cacheKey);
|
||||
let result;
|
||||
if (cachedResult) {
|
||||
try {
|
||||
result = cachedResult;
|
||||
} catch (error) {
|
||||
console.error('Error parsing cached result:', error);
|
||||
result = await this._content.find();
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 180);
|
||||
}
|
||||
} else {
|
||||
result = await this._content.find();
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 180);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,23 +1,43 @@
|
||||
import TClient from '../client.js';
|
||||
import mongoose from 'mongoose';
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
|
||||
const Schema = mongoose.model('bonkCount', new mongoose.Schema({
|
||||
_id: {type: String, required:true},
|
||||
value: {type: Number, required:true}
|
||||
}, {versionKey: false}));
|
||||
class bonkCount extends Model {
|
||||
declare public id: string;
|
||||
declare public count: number;
|
||||
}
|
||||
|
||||
export default class bonkCount extends Schema {
|
||||
client: TClient;
|
||||
_content: typeof Schema;
|
||||
constructor(client:TClient){
|
||||
super();
|
||||
this.client = client;
|
||||
this._content = Schema;
|
||||
export class BonkCountSvc {
|
||||
private model: typeof bonkCount;
|
||||
|
||||
constructor(){
|
||||
this.model = bonkCount;
|
||||
this.model.init({
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
primaryKey: true
|
||||
},
|
||||
count: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
}
|
||||
}, {
|
||||
tableName: 'bonkcount',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq
|
||||
});
|
||||
this.model.sync();
|
||||
}
|
||||
async _incrementUser(userid: string){
|
||||
const amount = await this._content.findById(userid)
|
||||
if (amount) await this._content.findByIdAndUpdate(userid, {value: amount.value + 1})
|
||||
else await this._content.create({_id: userid, value: 1})
|
||||
async hitCountIncremental(userId:string) {
|
||||
const getUser = await this.model.findByPk(userId);
|
||||
if (getUser) getUser.increment('count');
|
||||
else await this.model.create({id: userId, count: 1});
|
||||
return this;
|
||||
}
|
||||
async fetchUser(userId:string) {
|
||||
const getUser = await this.model.findByPk(userId);
|
||||
if (getUser) return getUser.dataValues;
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
|
54
src/models/dailyMsgs.ts
Normal file
54
src/models/dailyMsgs.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
|
||||
class dailyMsgs extends Model {
|
||||
declare public day: number;
|
||||
declare public total: number;
|
||||
}
|
||||
|
||||
export class DailyMsgsSvc {
|
||||
private model: typeof dailyMsgs;
|
||||
|
||||
constructor() {
|
||||
this.model = dailyMsgs;
|
||||
this.model.init({
|
||||
day: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
unique: true,
|
||||
primaryKey: true
|
||||
},
|
||||
total: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
}
|
||||
}, {
|
||||
tableName: 'dailymsgs',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq
|
||||
})
|
||||
this.model.sync();
|
||||
}
|
||||
async nukeDays() {
|
||||
return await this.model.destroy({truncate: true})
|
||||
// Drop a nuclear bomb on the table.
|
||||
}
|
||||
async fetchDays() {
|
||||
return await this.model.findAll();
|
||||
// Fetch every rows from database.
|
||||
}
|
||||
async fetchSpecificDay(dayId:number) {
|
||||
return await this.model.findOne({where: {day: dayId}});
|
||||
// Fetch a specific row from database by id column.
|
||||
}
|
||||
async newDay(formattedDate:number, total:number) {
|
||||
if (await this.model.findOne({where: {day: formattedDate}})) return console.log('This day already exists!')
|
||||
return await this.model.create({day: formattedDate, total: total});
|
||||
// Save previous day's total messages into database when a new day starts.
|
||||
}
|
||||
async updateDay(formattedDate:number, total:number) {
|
||||
return await this.model.update({total: total}, {where: {day: formattedDate}});
|
||||
// THIS IS FOR DEVELOPMENT PURPOSES ONLY, NOT TO BE USED IN LIVE ENVIRONMENT!
|
||||
}
|
||||
}
|
60
src/models/prohibitedWords.ts
Normal file
60
src/models/prohibitedWords.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
import {get} from 'node:https';
|
||||
|
||||
class prohibitedWords extends Model {
|
||||
declare public word: string;
|
||||
}
|
||||
|
||||
export class ProhibitedWordsSvc {
|
||||
private model: typeof prohibitedWords;
|
||||
|
||||
constructor() {
|
||||
this.model = prohibitedWords;
|
||||
this.model.init({
|
||||
word: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'prohibitedwords',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq
|
||||
})
|
||||
this.model.sync();
|
||||
}
|
||||
async findWord(word:string) {
|
||||
return await this.model.findByPk(word);
|
||||
}
|
||||
async importWords(file:string) {
|
||||
const jsonData = await new Promise<string>((resolve, reject)=>{
|
||||
get(file, res=>{
|
||||
let data = '';
|
||||
res.on('data', chunk=>data += chunk);
|
||||
res.on('end', ()=>resolve(data));
|
||||
res.on('error', reject);
|
||||
})
|
||||
});
|
||||
|
||||
const data = JSON.parse(jsonData);
|
||||
const dataMapping = data.map((x:string)=>({word: x}));
|
||||
try {
|
||||
await this.model.bulkCreate(dataMapping, {ignoreDuplicates: true});
|
||||
return true;
|
||||
} catch(err) {
|
||||
throw new Error(`Failed to insert words into Postgres database: ${err.message}`)
|
||||
}
|
||||
}
|
||||
async getAllWords() {
|
||||
return await this.model.findAll();
|
||||
}
|
||||
async insertWord(word:string) {
|
||||
return await this.model.create({word: word})
|
||||
}
|
||||
async removeWord(word:string) {
|
||||
return await this.model.destroy({where: {word: word}})
|
||||
}
|
||||
}
|
@ -1,171 +1,280 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import mongoose from 'mongoose';
|
||||
import CacheServer from '../funcs/CacheServer.js';
|
||||
import ms from 'ms';
|
||||
import FormatTime from '../helpers/FormatTime.js';
|
||||
import {Punishment} from '../typings/interfaces';
|
||||
import {Punishment} from '../interfaces';
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
import CacheServer from '../components/CacheServer.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import Formatters from '../helpers/Formatters.js';
|
||||
import {readFileSync, existsSync} from 'node:fs';
|
||||
|
||||
const Schema = mongoose.model('punishments', new mongoose.Schema({
|
||||
_id: {type: Number, required: true},
|
||||
type: {type: String, required: true},
|
||||
member: {type: String, required: true},
|
||||
moderator: {type: String, required: true},
|
||||
expired: {type: Boolean},
|
||||
time: {type: Number, required: true},
|
||||
reason: {type: String, required: true},
|
||||
endTime: {type: Number},
|
||||
cancels: {type: Number},
|
||||
duration: {type: Number}
|
||||
}, {versionKey: false}));
|
||||
class punishments extends Model {
|
||||
declare public case_id: number;
|
||||
declare public type: string;
|
||||
declare public member: string;
|
||||
declare public moderator: string;
|
||||
declare public expired: boolean;
|
||||
declare public time: number;
|
||||
declare public reason: string;
|
||||
declare public endTime: number;
|
||||
declare public cancels: number;
|
||||
declare public duration: number;
|
||||
}
|
||||
|
||||
export default class punishments extends Schema {
|
||||
client: TClient;
|
||||
_content: typeof Schema;
|
||||
constructor(client:TClient){
|
||||
super();
|
||||
export class PunishmentsSvc {
|
||||
private client: TClient;
|
||||
private model: typeof punishments;
|
||||
|
||||
constructor(client:TClient) {
|
||||
this.client = client;
|
||||
this._content = Schema;
|
||||
}
|
||||
async findInCache(): Promise<any> {
|
||||
const cacheKey = 'punishments';
|
||||
const cachedResult = await CacheServer.get(cacheKey);
|
||||
let result;
|
||||
if (cachedResult) {
|
||||
try {
|
||||
result = cachedResult;
|
||||
} catch (error) {
|
||||
console.error('Error parsing cached result:', error);
|
||||
result = await this._content.find();
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 15);
|
||||
this.model = punishments;
|
||||
this.model.init({
|
||||
case_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
unique: true,
|
||||
primaryKey: true
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
member: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
moderator: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
expired: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: true
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
},
|
||||
reason: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
endTime: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true
|
||||
},
|
||||
cancels: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
},
|
||||
duration: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true
|
||||
}
|
||||
} else {
|
||||
result = await this._content.find();
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 15);
|
||||
}, {
|
||||
tableName: 'punishments',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq
|
||||
});
|
||||
this.model.sync();
|
||||
}
|
||||
async migrate() {
|
||||
let file:string = 'src/punishments.json';
|
||||
if (!existsSync(file)) return Error(`File not found, have you tried checking if it exists? (${file})`);
|
||||
|
||||
await this.model.bulkCreate(JSON.parse(readFileSync(file, 'utf8')).map(x=>({
|
||||
case_id: x._id,
|
||||
type: x.type,
|
||||
member: x.member,
|
||||
moderator: x.moderator,
|
||||
expired: x.expired,
|
||||
time: x.time ? Number(x.time.$numberLong) : undefined,
|
||||
reason: x.reason,
|
||||
endTime: x.endTime ? Number(x.endTime.$numberLong) : undefined,
|
||||
cancels: x.cancels,
|
||||
duration: x.duration ? (typeof x.duration === 'object' ? Number(x.duration.$numberLong) : x.duration) : undefined
|
||||
})));
|
||||
}
|
||||
async updateReason(caseId:number, reason:string) {
|
||||
const findCase = this.findCase(caseId);
|
||||
if (findCase) return this.model.update({reason: reason}, {where: {case_id: caseId}});
|
||||
}
|
||||
async findCase(caseId:number) {
|
||||
return this.model.findOne({where: {case_id: caseId}});
|
||||
}
|
||||
async findByCancels(caseId:number) {
|
||||
return this.model.findOne({where: {cancels: caseId}})
|
||||
}
|
||||
async getAllCases() {
|
||||
return this.model.findAll();
|
||||
}
|
||||
async generateCaseId() {
|
||||
const result = await this.model.findAll();
|
||||
return Math.max(...result.map((x:Punishment)=>x.case_id), 0) + 1;
|
||||
}
|
||||
async caseEvasionCheck(member:Discord.GuildMember) {
|
||||
if (await this.model.findOne({where: {member: member.id, type: 'mute', expired: undefined}})) {
|
||||
(this.client.channels.cache.get(this.client.config.dcServer.channels.dcmod_chat) as Discord.TextChannel).send({embeds: [new this.client.embed().setColor(this.client.config.embedColorYellow).setTitle('Case evasion detected').setDescription(MessageTool.concatMessage(
|
||||
`**${member.user.username}** (\`${member.user.id}\`) has been detected for case evasion.`,
|
||||
'Timeout has been automatically added. (25 days)'
|
||||
)).setTimestamp()]});
|
||||
await this.punishmentAdd('mute', {time: '25d'}, this.client.user.id, '[AUTOMOD] Case evasion', member.user, member)
|
||||
}
|
||||
}
|
||||
async findInCache():Promise<any> {
|
||||
const cacheKey = 'punishments';
|
||||
const cachedResult = await CacheServer.getJSON(cacheKey);
|
||||
let result;
|
||||
if (cachedResult) result = cachedResult;
|
||||
else {
|
||||
result = await this.model.findAll();
|
||||
CacheServer.setJSON(cacheKey, result).then(()=>CacheServer.expiry(cacheKey, 20));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
createId = async()=>Math.max(...(await this._content.find()).map(x=>x.id), 0) + 1;
|
||||
async makeModlogEntry(punishment:Punishment){
|
||||
// Format data into an embed
|
||||
const channel = ['kick', 'ban'].includes(punishment.type) ? this.client.config.mainServer.channels.bankick_log : this.client.config.mainServer.channels.logs;
|
||||
const embed = new this.client.embed().setTitle(`${punishment.type[0].toUpperCase() + punishment.type.slice(1)} | Case #${punishment._id}`)
|
||||
async createModlog(punishment:Punishment) {
|
||||
const channel = ['kick', 'ban', 'softban'].includes(punishment.type) ? this.client.config.dcServer.channels.bankick_log : this.client.config.dcServer.channels.logs;
|
||||
const embed = new this.client.embed()
|
||||
.setColor(this.client.config.embedColor)
|
||||
.setTitle(`${punishment.type[0].toUpperCase() + punishment.type.slice(1)} | Case #${punishment.case_id}`)
|
||||
.addFields(
|
||||
{name: '🔹 User', value: `<@${punishment.member}>\n\`${punishment.member}\``, inline: true},
|
||||
{name: '🔹 Moderator', value: `<@${punishment.moderator}>\n\`${punishment.moderator}\``, inline: true},
|
||||
{name: '\u200b', value: '\u200b', inline: true},
|
||||
{name: '🔹 Reason', value: `\`${punishment.reason}\``, inline: true})
|
||||
.setColor(this.client.config.embedColor).setTimestamp(punishment.time)
|
||||
if (punishment.duration) embed.addFields({name: '🔹 Duration', value: `${FormatTime(punishment.duration, 100)}`, inline: true}, {name: '\u200b', value: '\u200b', inline: true})
|
||||
{name: '🔹 Reason', value: `\`${punishment.reason}\``, inline: true}
|
||||
).setTimestamp(punishment.time);
|
||||
if (punishment.duration) embed.addFields({name: '🔹 Duration', value: `${Formatters.timeFormat(punishment.duration, 4, {longNames: false, commas: true})}`, inline: true}, {name: '\u200b', value: '\u200b', inline: true});
|
||||
if (punishment.cancels) {
|
||||
const cancels = await this._content.findById(punishment.cancels);
|
||||
embed.addFields({name: '🔹 Overwrites', value: `This case overwrites Case #${cancels.id}\n\`${cancels.reason}\``})
|
||||
const cancels = await this.model.findOne({where: {case_id: punishment.cancels}})
|
||||
embed.addFields({name: '🔹 Overwrites', value: `This case invalidates Case #${cancels.dataValues.case_id}\n\`${cancels.dataValues.reason}\``});
|
||||
}
|
||||
// Send it off to specific Discord channel.
|
||||
(this.client.channels.cache.get(channel) as Discord.TextChannel).send({embeds:[embed]});
|
||||
(this.client.channels.cache.get(channel) as Discord.TextChannel).send({embeds: [embed]});
|
||||
}
|
||||
getTense(type:string){// Get past tense form of punishment type, grammar yes
|
||||
getPastTense(type:string) {
|
||||
return {
|
||||
ban: 'banned',
|
||||
softban: 'softbanned',
|
||||
kick: 'kicked',
|
||||
mute: 'muted',
|
||||
warn: 'warned'
|
||||
}[type]
|
||||
}[type];
|
||||
}
|
||||
async addPunishment(type:string, options:{time?:string,interaction?:Discord.ChatInputCommandInteraction<'cached'>},moderator:string,reason:string,User:Discord.User,GuildMember?:Discord.GuildMember){
|
||||
const {time,interaction} = options;
|
||||
async punishmentAdd(type:string, options:{time?:string, interaction?:Discord.ChatInputCommandInteraction}, moderator:string, reason: string, user:Discord.User, guildUser?:Discord.GuildMember) {
|
||||
const {time, interaction} = options;
|
||||
const now = Date.now();
|
||||
const guild = this.client.guilds.cache.get(this.client.config.mainServer.id) as Discord.Guild;
|
||||
const punData:Punishment={type, _id: await this.createId(), member:User.id, reason, moderator, time:now}
|
||||
const guild = this.client.guilds.cache.get(this.client.config.dcServer.id) as Discord.Guild;
|
||||
const punishment:Punishment = {type, case_id: await this.generateCaseId(), member: user.id, reason, moderator, time: now};
|
||||
const inOrFromBoolean = ['warn', 'mute'].includes(type) ? 'in' : 'from';
|
||||
const auditLogReason = `${reason || 'Reason unspecified'} | Case #${punData._id}`;
|
||||
const auditLogReason = `${reason ?? 'Reason unspecified'} | Case #${punishment.case_id}`;
|
||||
const embed = new this.client.embed()
|
||||
.setColor(this.client.config.embedColor)
|
||||
.setTitle(`Case #${punData._id}: ${type[0].toUpperCase()+type.slice(1)}`)
|
||||
.setDescription(`${User.username}\n<@${User.id}>\n(\`${User.id}\`)`)
|
||||
.addFields({name: 'Reason', value: reason})
|
||||
let punResult;
|
||||
let timeInMillis;
|
||||
let DM;
|
||||
.setTitle(`${type[0].toUpperCase() + type.slice(1)} | Case #${punishment.case_id}`)
|
||||
.setDescription(`${user.username}\n<@${user.id}>\n\`${user.id}\``)
|
||||
.addFields({name: 'Reason', value: `\`${reason}\``});
|
||||
let punishmentResult:any;
|
||||
let millisecondTime:number;
|
||||
|
||||
if (type == 'mute') timeInMillis = time ? ms(time) : 2419140000; // Timeouts have a limit of 4 weeks
|
||||
else timeInMillis = time ? ms(time) : null;
|
||||
if (type === 'mute') millisecondTime = time ? ms(time) : 2419200000; // Timeouts have a maximum duration of 4 weeks (28 days)
|
||||
else millisecondTime = time ? ms(time) : null;
|
||||
|
||||
const durationText = timeInMillis ? ` for ${FormatTime(timeInMillis, 4, {longNames:true,commas:true})}` : '';
|
||||
if (time) embed.addFields({name: 'Duration', value: durationText});
|
||||
|
||||
if (GuildMember){
|
||||
try{
|
||||
DM=await GuildMember.send(`You've been ${this.getTense(type)} ${inOrFromBoolean} ${guild.name}${durationText} for \`${reason}\` (Case #${punData._id})`);
|
||||
}catch(err){
|
||||
embed.setFooter({text: 'Failed to DM a member.'})
|
||||
const durText = millisecondTime ? ` for ${Formatters.timeFormat(millisecondTime, 4, {longNames: true, commas: true})}` : '';
|
||||
if (time) embed.addFields({name: 'Duration', value: durText});
|
||||
|
||||
if (guildUser) {
|
||||
try {
|
||||
await guildUser.send(`You've been ${this.getPastTense(type)} ${inOrFromBoolean} **${guild.name}**${durText}\n\`${reason}\` (Case #${punishment.case_id})`)
|
||||
} catch {
|
||||
embed.setFooter({text: 'Unable to DM a member'})
|
||||
}
|
||||
}
|
||||
|
||||
if (['ban', 'softban'].includes(type)){
|
||||
const banned = await guild.bans.fetch(User.id).catch(()=>undefined);
|
||||
if (!banned) punResult = await guild.bans.create(User.id, {reason: auditLogReason, deleteMessageSeconds: 172800}).catch((err:Error)=>err.message)
|
||||
else punResult = 'User is already banned.';
|
||||
}
|
||||
else if (type == 'kick') punResult = await GuildMember?.kick(auditLogReason).catch((err:Error)=>err.message);
|
||||
else if (type == 'mute') punResult = await GuildMember?.timeout(timeInMillis, auditLogReason).catch((err:Error)=>err.message);
|
||||
if (type == 'softban' && typeof punResult != 'string') punResult = await guild.bans.remove(User.id, auditLogReason).catch((err:Error)=>err.message);
|
||||
if (['ban', 'softban'].includes(type)) {
|
||||
const alreadyBanned = await guild.bans.fetch(user.id).catch(()=>null); // 172800 seconds is 48 hours, just for future reference
|
||||
if (!alreadyBanned) punishmentResult = await guild.bans.create(user.id, {reason: auditLogReason, deleteMessageSeconds: 172800}).catch((err:Error)=>err.message);
|
||||
else punishmentResult = 'This user already exists in the guild\'s ban list.';
|
||||
} else if (type === 'kick') punishmentResult = await guildUser?.kick(auditLogReason).catch((err:Error)=>err.message);
|
||||
else if (type === 'mute') punishmentResult = await guildUser?.timeout(millisecondTime, auditLogReason).catch((err:Error)=>err.message);
|
||||
|
||||
if (timeInMillis && ['mute','ban'].includes(type)){
|
||||
punData.endTime = now + timeInMillis;
|
||||
punData.duration = timeInMillis;
|
||||
if (type === 'softban' && typeof punishmentResult !== 'string') punishmentResult = await guild.bans.remove(user.id, auditLogReason).catch((err:Error)=>err.message);
|
||||
|
||||
if (millisecondTime && ['ban', 'mute'].includes(type)) {
|
||||
punishment.endTime = now + millisecondTime;
|
||||
punishment.duration = millisecondTime;
|
||||
}
|
||||
|
||||
if (typeof punResult == 'string'){// Unsuccessful punishment
|
||||
if (DM) DM.delete();
|
||||
if (interaction) return interaction.editReply(punResult);
|
||||
else return punResult;
|
||||
if (typeof punishmentResult === 'string') { // Punishment was unsuccessful
|
||||
if (interaction) return interaction.editReply(punishmentResult);
|
||||
else return punishmentResult;
|
||||
} else {
|
||||
await this.makeModlogEntry(punData);
|
||||
await this._content.create(punData);
|
||||
const checkIfExists = await this.model.findOne({where: {case_id: punishment.case_id}});
|
||||
if (checkIfExists) this.model.update({expired: punishment.expired, time: punishment.time, endTime: punishment.endTime}, {where: {case_id: punishment.case_id}})
|
||||
else await this.model.create({
|
||||
case_id: punishment.case_id,
|
||||
type: punishment.type,
|
||||
member: punishment.member,
|
||||
moderator: punishment.moderator,
|
||||
expired: punishment.expired,
|
||||
time: punishment.time,
|
||||
reason: punishment.reason,
|
||||
endTime: punishment.endTime,
|
||||
cancels: punishment.cancels,
|
||||
duration: punishment.duration
|
||||
});
|
||||
await this.createModlog(punishment);
|
||||
|
||||
if (interaction) return interaction.editReply({embeds:[embed]});
|
||||
else return punResult;
|
||||
if (interaction) return interaction.editReply({embeds: [embed]});
|
||||
else return punishmentResult;
|
||||
}
|
||||
}
|
||||
async removePunishment(caseId:number,moderator:string,reason:string,interaction?:Discord.ChatInputCommandInteraction<'cached'>){
|
||||
async punishmentRemove(caseId:number, moderator:string, reason:string, interaction?:Discord.ChatInputCommandInteraction) {
|
||||
const now = Date.now();
|
||||
const _id = await this.createId();
|
||||
const punishment = await this._content.findById(caseId);
|
||||
if (!punishment) return 'Punishment not found.';
|
||||
const guild = this.client.guilds.cache.get(this.client.config.mainServer.id) as Discord.Guild;
|
||||
const auditLogReason = `${reason || 'Reason unspecified'} | Case #${punishment.id}`;
|
||||
const User = await this.client.users.fetch(punishment.member);
|
||||
const GuildMember = await guild.members.fetch(punishment.member).catch(()=>null);
|
||||
const ID = await this.generateCaseId();
|
||||
const punishment = await this.model.findByPk(caseId);
|
||||
if (!punishment) return 'Case not found in database.';
|
||||
const guild = this.client.guilds.cache.get(this.client.config.dcServer.id) as Discord.Guild;
|
||||
const auditLogReason = `${reason ?? 'Reason unspecified'} | Case #${ID}`;
|
||||
const user = await this.client.users.fetch(punishment.member);
|
||||
const guildUser = await guild.members.fetch(punishment.member).catch(()=>null);
|
||||
|
||||
let removePunishmentData:Punishment={type:`un${punishment.type}`, _id, cancels:punishment.id, member:punishment.member, reason, moderator, time:now};
|
||||
let removePunishmentResult;
|
||||
let removePunishmentData:Punishment = {type: `un${punishment.type}`, case_id: ID, cancels: punishment.case_id, member: punishment.member, reason, moderator, time: now};
|
||||
let removePunishmentResult:any;
|
||||
|
||||
if (punishment.type == 'ban') removePunishmentResult = guild.bans.remove(punishment.member, auditLogReason).catch((err:Error)=>err.message);
|
||||
else if (punishment.type == 'mute'){
|
||||
if (GuildMember){
|
||||
removePunishmentResult = GuildMember.timeout(null, auditLogReason).catch((err:Error)=>err.message);
|
||||
GuildMember.send(`You've been unmuted in ${guild.name}.`).catch((err:Error)=>console.log(err.message));
|
||||
} else await this._content.findByIdAndUpdate(caseId,{expired:true},{new:true});
|
||||
if (punishment.type === 'ban') removePunishmentResult = await guild.bans.remove(punishment.member, auditLogReason).catch((err:Error)=>err.message);
|
||||
else if (punishment.type === 'mute') {
|
||||
if (guildUser) {
|
||||
removePunishmentResult = await guildUser.timeout(null, auditLogReason).catch((err:Error)=>err.message);
|
||||
guildUser.send(`You've been unmuted in **${guild.name}**.`).catch(()=>null);
|
||||
} else this.model.update({expired: true}, {where: {case_id: caseId}});
|
||||
} else removePunishmentData.type = 'punishmentOverride';
|
||||
|
||||
if (typeof removePunishmentResult == 'string'){//Unsuccessful punishment
|
||||
if (interaction) return interaction.reply(removePunishmentResult);
|
||||
if (typeof removePunishmentResult === 'string') {// Punishment was unsuccessful
|
||||
if (interaction) return interaction.editReply(removePunishmentResult);
|
||||
else return removePunishmentResult;
|
||||
} else {
|
||||
await this._content.findByIdAndUpdate(caseId,{expired:true},{new:true});
|
||||
await this._content.create(removePunishmentData);
|
||||
await this.makeModlogEntry(removePunishmentData);
|
||||
this.model.update({expired: true}, {where: {case_id: caseId}}).then(()=>
|
||||
this.model.create({
|
||||
case_id: removePunishmentData.case_id,
|
||||
type: removePunishmentData.type,
|
||||
member: removePunishmentData.member,
|
||||
moderator: removePunishmentData.moderator,
|
||||
expired: removePunishmentData.expired,
|
||||
time: removePunishmentData.time,
|
||||
reason: removePunishmentData.reason,
|
||||
endTime: removePunishmentData.endTime,
|
||||
cancels: removePunishmentData.cancels,
|
||||
duration: removePunishmentData.duration
|
||||
})
|
||||
);
|
||||
await this.createModlog(removePunishmentData);
|
||||
|
||||
if (interaction) return interaction.reply({embeds:[new this.client.embed().setColor(this.client.config.embedColor)
|
||||
.setTitle(`Case #${removePunishmentData._id}: ${removePunishmentData.type[0].toUpperCase()+removePunishmentData.type.slice(1)}`)
|
||||
.setDescription(`${User.username}\n<@${User.id}>\n(\`${User.id}\`)`)
|
||||
.addFields({name: 'Reason', value: reason},{name: 'Overwrites', value: `Case #${punishment.id}`})
|
||||
if (interaction) return interaction.reply({embeds: [new this.client.embed()
|
||||
.setColor(this.client.config.embedColor)
|
||||
.setTitle(`${removePunishmentData.type[0].toUpperCase() + removePunishmentData.type.slice(1)} | Case #${removePunishmentData.case_id}`)
|
||||
.setDescription(`${user.username}\n<@${user.id}>\n\`${user.id}\``)
|
||||
.addFields({name: 'Reason', value: reason}, {name: 'Overwrites', value: `Case #${punishment.case_id}`})
|
||||
]});
|
||||
else return `Successfully un${this.getTense(removePunishmentData.type.replace('un', ''))} ${User.username} (\`${User.id}\`) for ${reason}`
|
||||
else return `Successfully un${this.getPastTense(removePunishmentData.type.replace('un', ''))} ${user.username} (\`${user.id}\`) for ${reason}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
import TClient from '../client.js';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const Schema = mongoose.model('suggestion', new mongoose.Schema({
|
||||
_id: {type: String, required:true},
|
||||
idea: {type: String, required:true},
|
||||
user: {required:true, type: new mongoose.Schema({
|
||||
name: {type: String},
|
||||
_id: {type: String}
|
||||
}, {versionKey: false})},
|
||||
state: {type: String, required:true}
|
||||
}, {versionKey: false}));
|
||||
|
||||
export default class suggestion extends Schema {
|
||||
client: TClient;
|
||||
_content: typeof Schema;
|
||||
constructor(client:TClient){
|
||||
super();
|
||||
this.client = client;
|
||||
this._content = Schema;
|
||||
}
|
||||
}
|
55
src/models/suggestions.ts
Normal file
55
src/models/suggestions.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
|
||||
class suggestions extends Model {
|
||||
declare public id: number;
|
||||
declare public suggestion: string;
|
||||
declare public userid: string;
|
||||
declare public status: string;
|
||||
}
|
||||
|
||||
export class SuggestionsSvc {
|
||||
private model: typeof suggestions;
|
||||
|
||||
constructor() {
|
||||
this.model = suggestions;
|
||||
this.model.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
unique: true,
|
||||
primaryKey: true
|
||||
},
|
||||
suggestion: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
userid: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
tableName: 'suggestions',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq
|
||||
})
|
||||
this.model.sync();
|
||||
}
|
||||
async fetchById(id:number) {
|
||||
return await this.model.findByPk(id);
|
||||
}
|
||||
async updateStatus(id:number, status:string) {
|
||||
return await this.model.update({status: status}, {where: {id: id}})
|
||||
}
|
||||
async create(userid:string, description:string) {
|
||||
return this.model.create({userid: userid, suggestion: description, status: 'Pending'})
|
||||
}
|
||||
async delete(id:number) {
|
||||
return this.model.destroy({where: {id: id}});
|
||||
}
|
||||
}
|
@ -1,51 +1,100 @@
|
||||
import TClient from '../client.js';
|
||||
import mongoose from 'mongoose';
|
||||
import CacheServer from '../funcs/CacheServer.js';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import CacheServer from '../components/CacheServer.js';
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
import {readFileSync, existsSync} from 'node:fs';
|
||||
import {ChatInputCommandInteraction, Snowflake} from 'discord.js';
|
||||
|
||||
const Schema = mongoose.model('tags', new mongoose.Schema({
|
||||
_id: {type: String, required:true},
|
||||
message: {type: String, required:true},
|
||||
embedBool: {type: Boolean, required:true},
|
||||
user: {required:true, type: new mongoose.Schema({
|
||||
name: {type: String, required:true},
|
||||
_id: {type: String, required:true}
|
||||
}, {versionKey: false})}
|
||||
}, {versionKey: false}));
|
||||
class tagsystem extends Model {
|
||||
declare public tagname: string;
|
||||
declare public message: string;
|
||||
declare public embedFlag: boolean;
|
||||
declare public userid: string;
|
||||
}
|
||||
|
||||
export default class tags extends Schema {
|
||||
client: TClient;
|
||||
_content: typeof Schema;
|
||||
constructor(client:TClient){
|
||||
super();
|
||||
this.client = client;
|
||||
this._content = Schema;
|
||||
}
|
||||
async findInCache(): Promise<any> {
|
||||
const cacheKey = 'Tags';
|
||||
const cachedResult = await CacheServer.get(cacheKey);
|
||||
let result;
|
||||
if (cachedResult) {
|
||||
try {
|
||||
result = cachedResult;
|
||||
} catch (error) {
|
||||
console.error('Error parsing cached result:', error);
|
||||
result = await this._content.find();
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 240);
|
||||
interface Tags {
|
||||
tagname: string;
|
||||
message: string;
|
||||
embedFlag: boolean;
|
||||
userid: string;
|
||||
}
|
||||
|
||||
export class TagSystemSvc {
|
||||
private model: typeof tagsystem;
|
||||
|
||||
constructor() {
|
||||
this.model = tagsystem;
|
||||
this.model.init({
|
||||
tagname: {
|
||||
type: DataTypes.TEXT,
|
||||
unique: true,
|
||||
allowNull: false
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
embedFlag: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false
|
||||
},
|
||||
userid: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
} else {
|
||||
result = await this._content.find();
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 240);
|
||||
}, {
|
||||
tableName: 'tags',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq
|
||||
});
|
||||
this.model.sync();
|
||||
}
|
||||
async migrate() {
|
||||
let file:string = 'src/tags.json';
|
||||
if (!existsSync(file)) return Error(`File not found, have you tried checking if it exists? (${file})`);
|
||||
|
||||
await this.model.bulkCreate(JSON.parse(readFileSync(file, 'utf8')).map(x=>({
|
||||
tagname: x._id,
|
||||
message: x.message,
|
||||
embedFlag: x.embedBool,
|
||||
userid: x.user._id
|
||||
})));
|
||||
}
|
||||
async createTag(userid:string, tagName:string, message:string, embedFlag:boolean) {
|
||||
CacheServer.delete('tags');
|
||||
return await this.model.create({userid: userid, tagname: tagName, message: message.replace(/\\n/g, '\n'), embedFlag: embedFlag});
|
||||
}
|
||||
async deleteTag(tagname:string) {
|
||||
CacheServer.delete('tags');
|
||||
return await this.model.destroy({where: {tagname: tagname}});
|
||||
}
|
||||
async sendTag(interaction:ChatInputCommandInteraction, tagName:string, targetId:Snowflake) {
|
||||
const getTag = await this.model.findOne({where: {tagname: tagName}});
|
||||
const targetMsg = targetId ? `*This tag is directed at ${MessageTool.formatMention(targetId, 'user')}*` : '';
|
||||
const ic = interaction.client as TClient;
|
||||
const embedFormat = [
|
||||
new ic.embed().setTitle(tagName).setColor(ic.config.embedColor)
|
||||
.setAuthor({name: interaction.user.username, iconURL: interaction.user.avatarURL({size: 2048, extension: 'webp'})})
|
||||
.setDescription(getTag.dataValues.message)
|
||||
];
|
||||
if (getTag.dataValues.embedFlag) return await interaction.reply({content: targetMsg, embeds: embedFormat, allowedMentions: {parse: ['users']}});
|
||||
else return await interaction.reply({content: targetMsg+`\n**${getTag.dataValues.message}**`, allowedMentions: {parse: ['users']}});
|
||||
}
|
||||
async modifyTag(tagname:string, message:string) {
|
||||
CacheServer.delete('tags');
|
||||
return await this.model.update({message: message.replace(/\\n/g, '\n')}, {where: {tagname: tagname}});
|
||||
}
|
||||
async findInCache(): Promise<Tags[]> {
|
||||
const cacheKey = 'tags';
|
||||
const cachedResult = await CacheServer.getJSON(cacheKey);
|
||||
let result;
|
||||
if (cachedResult) result = cachedResult;
|
||||
else {
|
||||
result = await this.model.findAll();
|
||||
CacheServer.setJSON(cacheKey, result).then(()=>CacheServer.expiry(cacheKey, 240));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async updateCache(): Promise<any> {
|
||||
const cacheKey = 'Tags';
|
||||
CacheServer.delete(cacheKey);
|
||||
const result = await this._content.find();
|
||||
CacheServer.set(cacheKey, result);
|
||||
CacheServer.expiry(cacheKey, 10);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +1,109 @@
|
||||
import Discord from 'discord.js';
|
||||
import TClient from '../client.js';
|
||||
import mongoose from 'mongoose';
|
||||
import MessageTool from '../helpers/MessageTool.js';
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
import {writeFileSync} from 'node:fs';
|
||||
import cron from 'node-cron';
|
||||
import {writeFileSync, readFileSync} from 'node:fs';
|
||||
import Logger from '../helpers/Logger.js';
|
||||
|
||||
const Schema = mongoose.model('userLevels', new mongoose.Schema({
|
||||
_id: {type: String},
|
||||
messages: {type: Number, required: true},
|
||||
level: {type: Number, required: true},
|
||||
notificationPing: {type: Boolean}
|
||||
}, {versionKey: false}));
|
||||
class userLevels extends Model {
|
||||
declare public id: string;
|
||||
declare public messages: number;
|
||||
declare public level: number;
|
||||
declare public pingToggle: boolean;
|
||||
}
|
||||
|
||||
export default class userLevels extends Schema {
|
||||
client: TClient;
|
||||
_content: typeof Schema;
|
||||
constructor(client:TClient){
|
||||
super();
|
||||
export class UserLevelsSvc {
|
||||
private client: TClient;
|
||||
private model: typeof userLevels;
|
||||
|
||||
constructor(client:TClient) {
|
||||
this.client = client;
|
||||
this._content = Schema;
|
||||
this.model = userLevels;
|
||||
this.model.init({
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
primaryKey: true
|
||||
},
|
||||
messages: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
level: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
pingToggle: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: true,
|
||||
}
|
||||
}, {
|
||||
tableName: 'userlevels',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq
|
||||
});
|
||||
this.model.sync();
|
||||
}
|
||||
async resetAllData(){
|
||||
// Every 1st of January at 11:00 (Midnight in London, 11AM in Sydney)
|
||||
async fetchEveryone() {
|
||||
return await this.model.findAll();
|
||||
}
|
||||
async fetchUser(userId:string) {
|
||||
return await this.model.findByPk(userId);
|
||||
}
|
||||
async deleteUser(userId:string) {
|
||||
return await this.model.destroy({where: {id: userId}});
|
||||
}
|
||||
async messageIncremental(userId:string) {
|
||||
const data = await this.model.findByPk(userId);
|
||||
if (data) {
|
||||
await this.model.update({messages: data.dataValues.messages + 1}, {where: {id: userId}});
|
||||
if (data.messages >= this.algorithm(data.dataValues.level+2)) {
|
||||
const oldLevel = data.dataValues.level;
|
||||
while (data.messages > this.algorithm(data.dataValues.level+1)) await this.model.update({level: data.dataValues.level++}, {where: {id: userId}});
|
||||
const updatedUser = await this.model.findByPk(userId);
|
||||
Logger.console('log', 'LevelSystem', `${userId} superseded to level ${updatedUser.dataValues.level} from ${oldLevel}`);
|
||||
} else if (data.dataValues.messages >= this.algorithm(data.dataValues.level+1)) {
|
||||
await this.model.update({level: data.dataValues.level+1}, {where: {id: userId}});
|
||||
const getUser = await this.model.findByPk(userId);
|
||||
const levelUpMsg = `${getUser.pingToggle === true ? `<@${userId}>` : `**${(await this.client.users.fetch(userId)).displayName}**`} has reached level **${getUser.level}**. Well done!`;
|
||||
(this.client.channels.resolve(this.client.config.dcServer.channels.botcommands) as Discord.TextChannel).send({content: levelUpMsg, allowedMentions: {parse: ['users']}});
|
||||
}
|
||||
} else await this.model.create({id: userId, messages: 1, level: 0, pingToggle: true});
|
||||
}
|
||||
async initSelfdestruct() {
|
||||
// Every 1st of January at 11:00 (Midnight in London, Middayish in Sydney)
|
||||
cron.schedule('0 11 1 1 *', async()=>{
|
||||
Logger.forwardToConsole('log', 'Cron', 'Running job "resetAllData", this is activated every 1st of January');
|
||||
const countDataBeforeReset = await this._content.countDocuments();
|
||||
Logger.forwardToConsole('log', 'Cron:resetAllData', `Counted ${countDataBeforeReset.toLocaleString()} documents before reset`);
|
||||
await this._content.deleteMany();
|
||||
Logger.forwardToConsole('log', 'Cron:resetAllData', 'Deleted all documents, now resetting dailyMsgs');
|
||||
const dailyMsgsBak = readFileSync('src/database/dailyMsgs.json', 'utf-8');
|
||||
writeFileSync(`src/database/dailyMsgs_${new Date().getTime()}.json`, dailyMsgsBak);
|
||||
writeFileSync('src/database/dailyMsgs.json', JSON.stringify([]));
|
||||
// Send notification to mainServer's logs channel after cronjob is complete.
|
||||
(this.client.channels.resolve(this.client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new this.client.embed().setColor('#A3FFE3').setTitle('Yearly data reset has begun!').setDescription(`I have gone ahead and reset everyone's rank data. There was ${Intl.NumberFormat('en-US').format(await countDataBeforeReset)} documents in database before reset.`).setFooter({text: 'dailyMsgs has been backed up and wiped too.'}).setTimestamp()]});
|
||||
Logger.console('log', 'Cron', 'Running job "resetAllData", this is activated every 1st of January');
|
||||
const performCountBeforeReset = await this.model.count();
|
||||
Logger.console('log', 'Cron:resetAllData', `Counted ${performCountBeforeReset.toLocaleString()} documents before reset`);
|
||||
await this.client.dailyMsgs.nukeDays();
|
||||
await this.model.drop().then(async()=>await this.model.sync());
|
||||
|
||||
// Reset LRSstart to current epoch and write it to config file
|
||||
// Send notification to dcServer's logs channel after cronjob is complete.
|
||||
(this.client.channels.resolve(this.client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [new this.client.embed()
|
||||
.setColor('#A3FFE3').setTitle('Yearly data reset has begun!')
|
||||
.setDescription(MessageTool.concatMessage(
|
||||
'I have gone ahead and reset everyone\'s rank data.',
|
||||
`There was ${Intl.NumberFormat('en-US').format(performCountBeforeReset)} documents in database before reset.`
|
||||
)).setTimestamp()
|
||||
]});
|
||||
|
||||
// Reset LRSstart to current Epoch and save it to config file
|
||||
const newEpoch = new Date().getTime();
|
||||
this.client.config.LRSstart = newEpoch;
|
||||
const logText = `Resetting LRSstart to \`${newEpoch}\`, saved to config file`;
|
||||
Logger.forwardToConsole('log', 'DailyMsgs', logText);
|
||||
(this.client.channels.resolve(this.client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new this.client.embed().setColor(this.client.config.embedColorXmas).setTitle('Happy New Years! Level System is clean!').setDescription(logText).setTimestamp()]}).catch(err=>console.log(err));
|
||||
writeFileSync('./src/config.json', JSON.stringify(this.client.config, null, 2));
|
||||
Logger.forwardToConsole('log', 'Cron:resetAllData', 'Job completed');
|
||||
Logger.console('log', 'DailyMsgs', logText);
|
||||
(this.client.channels.resolve(this.client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [new this.client.embed()
|
||||
.setColor(this.client.config.embedColorXmas).setTitle('Happy New Years! Level System is clean!')
|
||||
.setDescription(logText).setTimestamp()
|
||||
]}).catch(err=>console.log(err));
|
||||
writeFileSync('src/config.json', JSON.stringify(this.client.config, null, 2));
|
||||
Logger.console('log', 'Cron:resetAllData', 'Job completed');
|
||||
})
|
||||
}
|
||||
async incrementUser(userid:string){
|
||||
const userData = await this._content.findById(userid)
|
||||
if (userData){
|
||||
await this._content.findByIdAndUpdate(userid, {messages: userData.messages + 1});
|
||||
if (userData.messages >= this.algorithm(userData.level+2)){
|
||||
while (userData.messages > this.algorithm(userData.level+1)){
|
||||
const newData = await this._content.findByIdAndUpdate(userid, {level:userData.level++}, {new: true});
|
||||
Logger.forwardToConsole('log', 'LevelSystem', `${userid} extended to level ${newData.level}`);
|
||||
}
|
||||
} else if (userData.messages >= this.algorithm(userData.level+1)) {
|
||||
const newData = await this._content.findByIdAndUpdate(userid, {level:userData.level+1}, {new: true});
|
||||
const fetchUserSchema = await this._content.findById(userid);
|
||||
(this.client.channels.resolve(this.client.config.mainServer.channels.botcommands) as Discord.TextChannel).send({content: `${fetchUserSchema.notificationPing === true ? `<@${userid}>` : `**${(await this.client.users.fetch(userid)).displayName}**`} has reached level **${newData.level}**. GG!`, allowedMentions: {parse: ['users']}});
|
||||
}
|
||||
} else await this._content.create({_id: userid, notificationPing: true, messages: 1, level: 0})
|
||||
}
|
||||
algorithm = (level:number)=>level*level*15;
|
||||
// Algorithm for determining levels. If adjusting, recommended to only change the integer at the end of equation.
|
||||
algorithm = (level:number)=>level*level*18;
|
||||
// Algorithm for determining levels. If adjusting, recommended to only change the integer at the end of equation.
|
||||
}
|
||||
|
48
src/models/ytChannels.ts
Normal file
48
src/models/ytChannels.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import DatabaseServer from '../components/DatabaseServer.js';
|
||||
import {Model, DataTypes} from 'sequelize';
|
||||
|
||||
class youtubeChannels extends Model {
|
||||
declare public ytchannel: string;
|
||||
declare public dcchannel: string;
|
||||
declare public dcrole: string;
|
||||
}
|
||||
|
||||
export class YouTubeChannelsSvc {
|
||||
private model: typeof youtubeChannels;
|
||||
|
||||
constructor() {
|
||||
this.model = youtubeChannels;
|
||||
this.model.init({
|
||||
ytchannel: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
dcchannel: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
dcrole: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
tableName: 'ytchannels',
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
sequelize: DatabaseServer.seq
|
||||
})
|
||||
this.model.sync();
|
||||
}
|
||||
async getChannels() {
|
||||
return await this.model.findAll();
|
||||
}
|
||||
async addChannel(YTChannelID:string, DCChannelID:string, DCRole:string) {
|
||||
if (await this.model.findOne({where: {ytchannel: YTChannelID}})) return false;
|
||||
await this.model.create({ytchannel: YTChannelID, dcchannel: DCChannelID, dcrole: DCRole});
|
||||
return true;
|
||||
}
|
||||
async delChannel(YTChannelID:string) {
|
||||
return await this.model.destroy({where: {ytchannel: YTChannelID}});
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user