From a3c21b13dfeaae8a7c17140a94dcd3f4ca305683 Mon Sep 17 00:00:00 2001 From: AnxietyisReal <96593068+AnxietyisReal@users.noreply.github.com> Date: Sat, 12 Nov 2022 11:58:11 +1100 Subject: [PATCH] Push part 1 --- .gitignore | 5 +- README.md | 14 +-- package.json | 20 +++- src/client.ts | 113 ++++++++++++++++++++++ src/config.json | 55 +++++++++++ src/database.js | 72 ++++++++++++++ src/database/MPPlayerData.json | 1 + src/database/bannedWords.json | 1 + src/database/bonkCount.json | 1 + src/database/dailyMsgs.json | 1 + src/database/punishments.json | 1 + src/database/userLevels.json | 1 + src/index.ts | 172 +++++++++++++++++++++++++++++++++ src/models/MPServer.ts | 26 +++++ src/timeNames.js | 30 ++++++ src/tokens.json | 6 ++ tsconfig.json | 18 ++++ 17 files changed, 528 insertions(+), 9 deletions(-) create mode 100644 src/client.ts create mode 100644 src/config.json create mode 100644 src/database.js create mode 100644 src/database/MPPlayerData.json create mode 100644 src/database/bannedWords.json create mode 100644 src/database/bonkCount.json create mode 100644 src/database/dailyMsgs.json create mode 100644 src/database/punishments.json create mode 100644 src/database/userLevels.json create mode 100644 src/index.ts create mode 100644 src/models/MPServer.ts create mode 100644 src/timeNames.js create mode 100644 src/tokens.json create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 35fc56d..5935a81 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,8 @@ node_modules/ package-lock.json .ncurc.json +# TypeScript stuff +dist/ +typings/ # Bot stuff -database/ \ No newline at end of file +database/MPDB.dat \ No newline at end of file diff --git a/README.md b/README.md index 100768c..0a26c8b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Daggerbot-TS -TypeScript version of the original JavaScript-based bot for Official Daggerwin Discord. +Original JavaScript Daggerbot from [here](https://github.com/SpaceManBuzz/DaggerBot-) converted to TypeScript. ### Todo list: -- [x] Prepare repository -- [ ] Install TypeScript dependencies and tools -- [ ] Convert JS files to TS one by one -- [ ] Migrate from [Sequelize](https://sequelize.org/) to [Prisma](https://www.prisma.io/) -- [ ] Plz work \ No newline at end of file +- [ ] Development + - [x] Prepare repository + - [x] Install TypeScript dependencies and tools + - [ ] Convert JS files to TS one by one + - [ ] Plz work +- [ ] Production + - [ ] Deploy bot \ No newline at end of file diff --git a/package.json b/package.json index 620690d..0937801 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,10 @@ { "name": "daggerbot-ts", - "version": "1.0.0", "description": "TypeScript version of the original JavaScript-based bot for Official Daggerwin Discord.", - "main": "index.js", + "main": "./src/index.ts", + "scripts": { + "start": "ts-node src/Sharding.ts" + }, "repository": { "type": "git", "url": "git+https://github.com/AnxietyisReal/Daggerbot-TS.git" @@ -22,5 +24,19 @@ ], "engines": { "node": ">=17.0.0" + }, + "dependencies": { + "axios": "1.1.3", + "canvas": "2.10.2", + "discord.js": "14.6.0", + "moment": "2.29.4", + "ms": "2.1.3", + "sequelize": "6.25.5", + "sqlite3": "5.1.2", + "xml-js": "^1.6.11" + }, + "devDependencies": { + "@types/node": "18.11.9", + "ts-node": "10.9.1" } } diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..92c300a --- /dev/null +++ b/src/client.ts @@ -0,0 +1,113 @@ +import { Client, GatewayIntentBits, Partials } from 'discord.js'; +import Discord = require('discord.js'); +import fs = require('node:fs'); +import database from './database'; +import timeNames from './timeNames.js'; +class TClient extends Client { + invites: any; + commands: any; + config: any; + tokens: any; + categoryNames: any; + commandPages: any; + helpDefaultOptions: any; + YTCache: any; + embed: any; + collection: any; + messageCollector: any; + attachmentBuilder: any; + moment: any; + memberCount_LastGuildFetchTimestamp: any; + userLevels: any; + punishments: any; + bonkCount: any; + bannedWords: any; + repeatedMessages: any; + setMaxListeners: any; + + constructor(){ + super({ + intents: [ + GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildBans, GatewayIntentBits.GuildInvites, + GatewayIntentBits.GuildPresences, GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.DirectMessages, GatewayIntentBits.MessageContent + ], + partials: [ + Partials.Channel, + Partials.Reaction, + Partials.Message + ], + allowedMentions: { repliedUser: false } + }) + this.invites = new Map(); + this.commands = new Discord.Collection(); + this.config = require('./config.json'); + this.tokens = require('./tokens.json'); + this.categoryNames; + this.commandPages = []; + this.helpDefaultOptions = { + parts: ['name', 'usage', 'shortDescription', 'alias'], + titles: ['alias'] + } + this.YTCache = { + 'UCQ8k8yTDLITldfWYKDs3xFg': undefined, // Daggerwin + 'UCguI73--UraJpso4NizXNzA': undefined // Machinery Restorer + } + this.embed = Discord.EmbedBuilder; + this.collection = Discord.Collection; + this.messageCollector = Discord.MessageCollector; + this.attachmentBuilder = Discord.AttachmentBuilder; + this.moment = require('moment'); + this.memberCount_LastGuildFetchTimestamp = 0; + this.userLevels = new database('./database/userLevels.json', 'object'); + this.bonkCount = new database('./database/bonkCount.json', 'object'); + this.punishments = new database('./database/punishments.json', 'array'); + this.bannedWords = new database('./database/bannedWords.json', 'array'); + this.repeatedMessages = {}; + this.setMaxListeners(20) + } + async init(){ + this.login(this.tokens.token_toast); + this.punishments.initLoad(); + this.bannedWords.initLoad(); + this.bonkCount.initLoad(); + this.userLevels.initLoad().intervalSave(15000).disableSaveNotifs(); + } + formatPunishmentType(punishment: any, client: any, cancels: any){ + if (punishment.type == 'removeOtherPunishment'){ + cancels ||= this.punishments._content.find(x=>x.id === punishment.cancels) + return cancels.type[0].toUpperCase()+cancels.type.slice(1)+' Removed'; + } else return punishment.type[0].toUpperCase()+punishment.type.slice(1); + } + formatTime(integer: number, accuracy = 1, options = {}){ + let achievedAccuracy = 0; + let text = ''; + const { longNames, commas } = options + for (const timeName of timeNames){ + if (achievedAccuracy < accuracy){ + const fullTimelengths = Math.floor(integer/timeName.length); + if (fullTimelengths == 0) continue; + achievedAccuracy++; + text += fullTimelengths + (longNames ? (' '+timeName.name+(fullTimelengths === 1 ? '' : 's')) : timeName.name.slice(0, timeName.name === 'month' ? 2 : 1)) + (commas ? ', ' : ' '); + integer -= fullTimelengths*timeName.length; + } else { + break; + } + } + if (text.length == 0) text = integer + (longNames ? ' milliseconds' : 'ms') + (commas ? ', ' : ''); + if (commas){ + text = text.slice(0, -2); + if (longNames){ + text = text.split(''); + text[text.lastIndexOf(',')] = ' and'; + text = text.join(''); + } + } return text.trim(); + } +} +module.exports = TClient; + +export function init() { + throw new Error('Function not implemented.'); +} diff --git a/src/config.json b/src/config.json new file mode 100644 index 0000000..2ee00e4 --- /dev/null +++ b/src/config.json @@ -0,0 +1,55 @@ +{ + "prefix": ".", + "embedColor": "#0052cf", + "embedColorGreen": "#57f287", + "embedColorYellow": "#ffea00", + "embedColorRed": "#e62c3b", + "embedColorBCA": "#ff69b4", + "LRSstart": 1661236321433, + "botSwitches": { + "commands": false, + "logs": false, + "automod": false, + "mpstats": false + }, + "eval": { + "allowed": true, + "whitelist": [ + "190407856527376384", + "615761944154210305" + ] + }, + "mainServer": { + "id": "549114074273677314", + "staffRoles": [ + "admin", + "dcmod", + "mpmanager", + "mpmod", + "vtcmanager", + "vtcstaff", + "ytmod" + ], + "roles": { + "admin": "987660171767595028", + "bottech": "904015815291056188", + "dcmod": "987660171767595028", + "mpmanager": "987660171767595028", + "mpmod": "904015815291056188", + "vtcmanager": "987660171767595028", + "vtcstaff": "904014418860453929", + "ytmod": "904014418860453929", + "mphelper": "904014418860453929", + "mpplayer": "904014418860453929", + "vtcmember": "904014418860453929" + }, + "channels": { + "console": "904192878140608563", + "errors": "904192878140608563", + "bot_status": "904192878140608563", + "logs": "904192878140608563", + "welcome": "904192878140608563", + "botcommands": "904192878140608563" + } + } +} \ No newline at end of file diff --git a/src/database.js b/src/database.js new file mode 100644 index 0000000..20016b5 --- /dev/null +++ b/src/database.js @@ -0,0 +1,72 @@ +import { resolve } from 'path'; +import { readFileSync, writeFileSync } from 'fs'; +class Database { + /** + * @param {string} dir + * @param {string} dataType + */ + constructor(dir, dataType) { + this._dataType = dataType; + this._content = dataType === 'array' ? [] : dataType === 'object' ? {} : undefined; + this._path = resolve(dir); + this._interval = undefined; + this._saveNotifs = true; + } + /** + * @param {string | number} data + * @param {any} data1 + */ + addData(data, data1) { + if (this._dataType === 'array') { + this._content.push(data); + } else if (this._dataType === 'object') { + this._content[data] = data1; + } + return this; + } + /** + * @param {string | number} key + */ + removeData(key) { + if (this._dataType === 'array') { + this._content.splice(key, 1); + } else if (this._dataType === 'object') { + delete this._content[key]; + } + return this; + } + initLoad() { + const json = readFileSync(this._path); + // @ts-ignore + const array = JSON.parse(json); + this._content = array; + console.log(this._path + ' Database Loaded'); + return this; + } + forceSave(db = this, force = false) { + const oldJson = readFileSync(db._path, { encoding: 'utf8' }); + const newJson = JSON.stringify(db._content); + if (oldJson !== newJson || force) { + writeFileSync(db._path, newJson); + if (this._saveNotifs) console.log(this._path + ' Database Saved'); + } + return db; + } + /** + * @param {any} milliseconds + */ + intervalSave(milliseconds) { + this._interval = setInterval(() => this.forceSave(this), milliseconds || 60000); + return this; + } + stopInterval() { + if (this._interval) clearInterval(this._interval); + return this; + } + disableSaveNotifs() { + this._saveNotifs = false; + console.log(this._path + ' "Database Saved" Notifications Disabled'); + return this; + } +} +export default Database; \ No newline at end of file diff --git a/src/database/MPPlayerData.json b/src/database/MPPlayerData.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/src/database/MPPlayerData.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/database/bannedWords.json b/src/database/bannedWords.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/src/database/bannedWords.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/database/bonkCount.json b/src/database/bonkCount.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src/database/bonkCount.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/database/dailyMsgs.json b/src/database/dailyMsgs.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/src/database/dailyMsgs.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/database/punishments.json b/src/database/punishments.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src/database/punishments.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/database/userLevels.json b/src/database/userLevels.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src/database/userLevels.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2e6d111 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,172 @@ +import Discord = require('discord.js'); +const TClient = require('./client'); +const client = new TClient; +client.init(); +import fs = require('node:fs'); +import ServerDB from './models/MPServer'; +import axios from 'axios'; +import xjs = require('xml-js'); + +client.on('ready', async()=>{ + client.guilds.cache.forEach(async(e: { members: { fetch: () => any; }; })=>{await e.members.fetch()}); + setInterval(async()=>{ + client.user.setPresence({activities: [{ name: 'Running under TS', type: 0 }], status: 'online'}); + // Playing: 0, Streaming (Requires YT/Twitch URL to work): 1, Listening to: 2, Watching: 3, Competing in: 5 + }, 60000); + setInterval(()=>{ + client.guilds.cache.get(client.config.mainServer.id).invites.fetch().then((invs: any[])=>{ + invs.forEach(async(inv: { code: any; uses: any; inviter: { id: any; }; })=>{ + client.invites.set(inv.code, {uses: inv.uses, creator: inv.inviter.id}) + }) + }) + }, 500000); + console.log(`${client.user.tag} has logged into Discord API and now ready for operation`) + console.log(client.config.botSwitches) + client.channels.resolve(client.config.mainServer.channels.bot_status).send(`${client.user.username} is active`); + + // Event handler + const eventFiles = fs.readdirSync('./events').filter(file=>file.endsWith('.js')); + eventFiles.forEach((file)=>{ + const event = require(`./events/${file}`); + client.on(event.name, async(...args: any)=>event.execute(client, ...args)) + }); +}) + +// Handle errors +process.on('unhandledRejection', async(error)=>{ + console.log(error) + client.channels.resolve(client.config.mainServer.channels.errors).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) +}); +process.on('uncaughtException', async(error)=>{ + console.log(error) + client.channels.resolve(client.config.mainServer.channels.errors).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) +}); +process.on('error', async(error)=>{ + console.log(error) + client.channels.resolve(client.config.mainServer.channels.errors).send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) +}); + +// Command handler +const commandFiles = fs.readdirSync('./commands').filter(file=>file.endsWith('.js')); +for (const file of commandFiles){ + const command = require(`./commands/${file}`); + client.commands.set(command.name, command) +} +// Slash command handler todo + +// Daggerwin MP loop +setInterval(async()=>{ + if (!client.config.botSwitches.mpstats) return; + const msg = await client.channels.resolve('ChannelID').messages.fetch('MessageID') + const embed = new client.embed(); + let Players = []; + let Server: any; + let CSG: any; + let xmlData = undefined; + + // Connect to DB to retrieve the Gameserver info to fetch data. + ServerDB.sync(); + const newServerId = client.config.mainServer.id + const ServerURL = await ServerDB.findOne({where: {serverId: newServerId}}) + const DBURL = ServerURL.get('ip') + const DBCode = ServerURL.get('code') + const completedURL_DSS = DBURL + '/feed/dedicated-server-stats.json?code=' + DBCode + const completedURL_CSG = DBURL + '/feed/dedicated-server-savegame.html?code=' + DBCode + '&file=careerSavegame' + + try { + Server = await axios.get(completedURL_DSS, {timeout: 4000}) + } catch (err){ + console.log(`[${client.moment().format('HH:mm:ss')}] dag mp dss fail`) + embed.setTitle('Data could not be retrieved, the host may not be responding.').setColor(client.config.embedColorRed) + msg.edit({embeds: [embed]}) + return; + } + try { + CSG = await axios.get(completedURL_CSG, {timeout: 4100}).then((xml)=>{ + xmlData = xjs.xml2js(xml.data, {compact: true, spaces: 2}).careerSavegame; + }) + } catch (err){ + console.log(`[${client.moment().format('HH:mm:ss')}] dag mp csg fail`) + } + if (xmlData == undefined){ + console.log(`[${client.moment().format('HH:mm:ss')}] dag mp csg failed to convert`) + embed.setFooter({text: 'XML Data retrieve failed. Retrying in next minute.'}) + msg.edit({embeds: [embed]}) + } + + const DB = require(`./database/MPPlayerData.json`); + DB.push(Server.data.slots.used) + fs.writeFileSync(__dirname + `./database/MPPlayerData.json`, JSON.stringify(DB)) + + // Number format function + function formatNumber(number: any, digits: any, icon: any){ + var n = Number(number) + return n.toLocaleString(undefined, {minimumFractionDigits: digits})+icon + } + var timeScale = Number(xmlData?.settings?.timeScale._text) + + if (Server.data.server.name.length == 0){ + embed.setTitle('The server seems to be offline.').setColor(client.config.embedColorRed); + msg.edit({embeds: [embed]}) + } else { + const embed1 = new client.embed().setColor(client.config.embedColor).setTitle('Server details').addFields( + {name: '| Current Map |', value: `${Server.data.server.mapName.length == 0 ? '\u200b' : Server.data.server.mapName}`, inline: true}, + {name: '| Game Version |', value: `${Server.data.server.version.length == 0 ? '0.0.0.0' : Server.data.server.version}`, inline: true}, + {name: '| In-game Time |', value: `${('0' + Math.floor((Server.data.server.dayTime/3600/1000))).slice(-2)}:${('0' + Math.floor((Server.data.server.dayTime/60/1000)%60)).slice(-2)}`, inline: true}, + {name: '| Slot Usage |', value: `${Number(xmlData?.slotSystem?._attributes?.slotUsage).toLocaleString('en-US')}`, inline: true}, + {name: '| Timescale |', value: `${formatNumber(timeScale, 0, 'x')}`, inline: true} + ); + await Server.data.slots.players.filter((x)=>x.isUsed !== false).forEach(player=>{ + Players.push(`**| ${player.name} | ${player.isAdmin ? 'admin |' : ''}**\nFarming for ${(Math.floor(player.uptime/60))} hr & ${('0' + (player.uptime % 60)).slice(-2)} min`) + }) + embed.setDescription(`${Server.data.slots.used == 0 ? '*No players online*' : Players.join('\n\n')}`).setTitle(Server.data.server.name).setColor(client.config.embedColor) + embed.setAuthor({name: `${Server.data.slots.used}/${Server.data.slots.capacity}`}); + msg.edit({embeds: [embed1, embed]}) + } +}, 60000) + +// YouTube Upload notification +setInterval(async()=>{ + client.YTLoop('UCQ8k8yTDLITldfWYKDs3xFg', 'Daggerwin', '528967918772551702'); + client.YTLoop('UCguI73--UraJpso4NizXNzA', 'Machinery Restorer', '767444045520961567') +}, 300000) + +// Event loop for punishments and daily msgs +setInterval(async()=>{ + const now = Date.now() + const lrsStart = client.config.LRSstart; + client.punishments._content.filter(x=>x.endTime<=now && !x.expired).forEach(async punishment=>{ + console.log(`${punishment.member}'s ${punishment.type} should expire now`); + const unpunishResult = await client.punishments.removePunishment(punishment.id, client.user.id, 'Time\'s up!') + console.log(unpunishResult); + }); + const formattedDate = Math.floor((now - lrsStart)/1000/60/60/24); + const dailyMsgs = require('./database/dailyMsgs.json'); + if (!dailyMsgs.some(x=>x[0] === formattedDate)){ + let total = Object.values(client.userLevels._content).reduce((a,b)=>a + b.messages, 0); // sum of all users + const yesterday = dailyMsgs.find(x=>x[0] === formattedDate - 1); + if (total < yesterday){ // messages went down. + total = yesterday + } + dailyMsgs.push([formattedDate, total]); + fs.writeFileSync(__dirname + './database/dailyMsgs.json', JSON.stringify(dailyMsgs)) + } +}, 5000) + +// Assign page number to commands +const categories = {}; +while (client.commands.some(command=>!command.hidden && !command.page)){ + const command = client.commands.find(command=>!command.hidden && !command.page); + if (!command.category) command.category = 'Misc'; + if (!categories[command.category]) categories[command.category] = {text: '', currentPage: 1} + const commandInfo = client.commandInfo(client, command, client.helpDefaultOptions); + if (categories[command.category].text.length+commandInfo.length>1024){ + categories[command.category].text = commandInfo; + categories[command.category].currentPage++; + } else { + categories[command.category].text += commandInfo; + } + command.page = categories[command.category].currentPage; +} +client.categoryNames = Object.keys(categories); +delete categories; \ No newline at end of file diff --git a/src/models/MPServer.ts b/src/models/MPServer.ts new file mode 100644 index 0000000..8caa790 --- /dev/null +++ b/src/models/MPServer.ts @@ -0,0 +1,26 @@ +import {Sequelize, DataTypes} from 'sequelize'; +var db = new Sequelize('database', 'daggerbot', 'toastsus', { + host: 'localhost', + dialect: 'sqlite', + logging: false, + storage: '../database/MPDB.dat' +}) +var ServerDB = db.define('urls', { + serverId: { + type: DataTypes.STRING, + defaultValue: 'Missing ID', + allowNull: false, + unique: true + }, + ip: { + type: DataTypes.STRING, + defaultValue: 'Missing IP', + allowNull: false + }, + code: { + type: DataTypes.STRING, + defaultValue: 'Missing Code', + allowNull: false + } +}, {timestamps: false}) +export default ServerDB; \ No newline at end of file diff --git a/src/timeNames.js b/src/timeNames.js new file mode 100644 index 0000000..ccee3e9 --- /dev/null +++ b/src/timeNames.js @@ -0,0 +1,30 @@ +export default [ + { + name: 'year', + length: 1000 * 60 * 60 * 24 * 365 + }, + { + name: 'month', + length: 1000 * 60 * 60 * 24 * 30 + }, + { + name: 'week', + length: 1000 * 60 * 60 * 24 * 7 + }, + { + name: 'day', + length: 1000 * 60 * 60 * 24 + }, + { + name: 'hour', + length: 1000 * 60 * 60 + }, + { + name: 'minute', + length: 1000 * 60 + }, + { + name: 'second', + length: 1000 + } +] \ No newline at end of file diff --git a/src/tokens.json b/src/tokens.json new file mode 100644 index 0000000..eb69a39 --- /dev/null +++ b/src/tokens.json @@ -0,0 +1,6 @@ +{ + "token_main": "OTI5NzU1ODczOTQzODk2MTM2.Ydr8og.A4qCaMBinZ-9Wv16Zid7x5NmxjQ", + "token_beta": "OTM0NDU5NjQzNzA5Nzc5OTg4.YewZXA.R7W4r7vqsNnxEvMqKvsQWkBj-5Y", + "token_toast": "OTMxMTUwNTQxNDkxNDI5Mzc2.G2_YHE.BfVwlL1ijqQ1inQjIJUPF27Nf_NlPK7cB80UhQ", + "token_tae": "Nzc5NDA0NjM3MjAzMDcxMDY3.Gp6qw1.kSBKGY9Xeq4mMlhBp-pmZV-D3ZAOfOvk7-YQ9Y" +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..696ac7c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "sourceMap": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "target": "ESNext", + "module": "CommonJS", + "baseUrl": "./", + "rootDir": "src/", + "outDir": "dist/", + "moduleResolution": "node", + "typeRoots": [ "node_modules/@types" ], + }, + "include": [ "./**/*.ts" ], + "exclude": [ "dist", "node_modules" ], +} \ No newline at end of file