EzLevel.js

let database;
/**
 * Options for discord easy leveling
 * @typedef {object} DiscordEasyLevelingOptions
 * @property {string} startingXP The amount of XP you want to user to start with
 * @property {string} startingLevel The level you want the user to start with
 * @property {string} levelUpXP The amount of XP need to level up a user
 * @property {string} database The database you want. This can be json or sqlite
 */
/**
 * @typedef {object} UserDataObject
 * @property {number} XPoverTime Amount of XP a user has aquired overtime
 * @property {string} userId Id of the user
 * @property {number} level Current level of a user
 * @property {number} xp Current XP of a user
 */
/**
 * Array of objects containing all users data
 * @typedef {array<object>} AllData
 * @property {string} ID the id of the data
 * @property {UserDataObject} UserData The data of a user
 */
/**
 * @author retrouser955
 * @see <a href="https://github.com/retrouser955">Retro github</a>
 */
const Database = require('easy-json-database')
const { EventEmitter } = require("events")
const events = require('./events/events.js')
const deleteModule = require('./deletedb.js')
const generateChartImage = require('./xpChart.js')
class EasyLeveling extends EventEmitter {
    /**
     * Create a new Discord Easy Level
     * @param {any} client Your Discord.js Client
     * @param {DiscordEasyLevelingOptions} options Discord XP level options
     */
    constructor(client, options) {
        super()
        if(!client) throw new Error('Easy Leveling Error: A valid discord client must be provided')
        if(!options) throw new Error('Easy Leveling Error: Options must be defined. Consinder reading readme.md')
        if(typeof options != 'object') throw new Error('Easy Leveling Error: Typeof options must be an object')
        // if(!options.startingXP || !options.startingLevel || !options.levelUpXP || !options.database) throw new Error('Easy Leveling Error: starting XP, starting Level or level up XP must be defined')
        this.client = client
        this.startingXP = options.startingXP || 1
        this.startingLevel = options.startingLevel || 1
        this.levelUpXP = options.levelUpXP || 100
        if(options.database == 'json') {
            this.db = new Database('./EasyLeveling.json')
            database = this.db
	        this.dbName = 'json'
        } else if(options.database == 'sqlite') {
            this.db = require('quick.db')
            database = this.db
	        this.dbName = 'sqlite'
        } else {
            throw new Error("Easy Leveling Error: Database must be an option between a 'json' databse and a 'sqlite' database")
        }
        process.on('uncaughtException', (err) => {
            this.emit(events.error, err, undefined)
        })
    }
    /**
     * add level to your desire user
     * @param {string} userId The id of the user you want to add levels
     * @param {string} guildId The id of the guild that the user is in
     */
    async addLevels(userId, guildId, channelId) {
        if(!userId) throw new Error('Easy Leveling Error: A valid user id must be provided')
        if(!guildId) throw new Error('Easy Level Error: A valid guild id must be provided')
        if(!channelId) throw new Error('Easy Level Error: A valid channel id must be provided')
        try {
            const dbHasLevel = this.db.has(`${userId}-${guildId}`)
            if(!dbHasLevel) {
                this.db.set(`${userId}-${guildId}`, { XP: this.startingXP })
                this.db.set(`${userId}-${guildId}.level`, this.startingLevel)
                this.db.set(`${userId}-${guildId}.userId`, userId)
                this.db.set(`${userId}-${guildId}.XPoverTime`, 1)
                return
            }
            const userLevelUp = await this.db.get(`${userId}-${guildId}.XP`)
            if(userLevelUp == this.levelUpXP) {
                await this.db.set(`${userId}-${guildId}.XP`, 0)
                const userHasLevel = this.db.has(`${userId}-${guildId}.level`)
                if(!userHasLevel) return await this.db.set(`${userId}-${guildId}.level`, 1)
                if(this.dbName == 'json') {
                    const newLevel = userLevelUp + 1
                    this.db.set(`${userId}-${guildId}.level`, newLevel)
                } else {
                    await this.db.add(`${userId}-${guildId}.level`, 1)
                }
                const newLevel = this.db.get(`${userId}-${guildId}.level`)
                const lastLevel = newLevel - 1
                this.emit(events.UserLevelUpEvent, newLevel, lastLevel, userId, guildId, channelId)
                return
            }
            if(this.dbName == 'json') {
                const currectData = await this.db.get(`${userId}-${guildId}.XP`)
                const XPoverTime = await this.db.get(`${userId}-${guildId}.XPoverTime`)
                const newXPoverTime = XPoverTime + 1
                const newData = currectData + 1
                await this.db.set(`${userId}-${guildId}.XP`, newData)
                await this.db.set(`${userId}-${guildId}.XPoverTime`, newXPoverTime)
                return
            }
            this.db.add(`${userId}-${guildId}.XP`, 1)
            this.db.add(`${userId}-${guildId}.XPoverTime`, 1)
        } catch (error) {
            this.emit(events.error, error, 'addLevels')
        }
    }
    /**
     * get the level and xp of the user
     * @param {string} userId user id
     * @param {string} guildId guild id
     * @returns {object} XP and the level of the user
     */
    async getUserLevel(userId, guildId) {
        if(!userId) throw new Error('Easy Level Error: A valid user id must be provided')
        if(!guildId) throw new Error('Easy Level Error: A valid guild id must be provided')
        try {
            const level = await this.db.get(`${userId}-${guildId}.level`)
            const xp = await this.db.get(`${userId}-${guildId}.XP`)
            const data = {
                level: level,
                xp: xp
            }
            return data
        } catch (error) {
            this.emit(events.error, error, 'getUserLevel')
        }
    }
    /**
     * force set the level of a user
     * @param {number} level amount of level you want the author to have
     * @param {string} userId user id of the user you want to set level to
     * @param {string} guildId the discord guild you want the level to be set in
     */
    async setLevel(level, userId, guildId) {
        if(!level) throw new Error('Easy Level Error: A valid level must be provided')
        if(typeof level != "number") throw new SyntaxError('Easy Level Error: Type of level must be a number')
        if(!userId) throw new Error('Easy Level Error: A valid user id must be provided')
        if(!guildId) throw new Error('Easy Level Error: A valid guild id must be provided')
        try {
            await this.db.set(`${userId}-${guildId}.level`, level) 
        } catch (error) {
            this.emit(events.error, error, 'setLevel')
        }
    }
    /**
     * force set the xp of a user
     * @param {string} xp amount of XP you want the author to have
     * @param {string} userId user id of the user you want to set XP to
     * @param {string} guildId the discord guild you want the XP to be set in
     */
    async setXP(xp, userId, guildId) {
        if(!xp) throw new Error('Easy Level Error: A valid xp must be provided')
        if(typeof xp != 'number') throw new SyntaxError('Easy Level Error: Type of xp must be a number')
        if(xp > this.levelUpXP) throw new Error(`Easy Level Error: Amount of XP cannot be more than ${this.levelUpXP}`)
        if(xp < 0) throw new Error(`Easy Level Error: Amount of XP cannot be more than 0`)
        try {
            await this.db.set(`${userId}-${guildId}.XP`, xp)
        } catch (error) {
            this.emit(events.error, error, 'setXP')
        }
    }
    /**
     * get all data from the database. powered by quick.db
     * @returns {AllData}
     */
    async getAllData() {
        try {
            const allData = this.db.all()
            return allData
        } catch (error) {
            this.emit(events.error, error, 'getAllData')
        }
    }
    async deleteAllData() {
        deleteModule.deleteAllData(this.db, this.dbName)
    }
    /**
     * will delete a user's data from the database
     * @param {string} userId the id of the user you want to delete
     * @param {string} guildId the id of the guild you want the data deleted from
     */
    async deleteUserData(userId, guildId) {
        if(!userId) throw new Error('Easy Level Error: A valid user id must be provided!')
        if(!guildId) throw new Error('Easy Level Error: A valid user guild must be provided!')
        try {
            deleteModule.deleteUserData(userId, guildId, this.db)
        } catch(err) {
            this.emit(events.error, error, 'deleteUserData')
        }
    }
    /**
     * Reduce the amount of level(s) from a user
     * @param {string} userId Id of the user you want to reduce levels from
     * @param {string} guildId Id of the guild you want to reduce Levels from
     * @param {number} amount Amount of levels you want to reduce
     */
    async reduceLevels(userId, guildId, amount) {
        if(!userId) throw new Error('Easy Level Error: A valid user id must be provided!')
        if(!guildId) throw new Error('Easy Level Error: A valid user guild must be provided!')
        if(!amount) throw new Error('Easy Level Error: An amount must be provided!')
        if(typeof amount != 'number') throw new Error("Easy Level TypeError: Type of 'amount' must be a number")
        try {
            const xpAmount = amount * 100
            if(this.dbName == 'json') {
                const currectData = await this.db.get(`${userId}-${guildId}.level`)
                const newData = currectData - amount
                const XPoverTime = await this.db.get(`${userId}-${guildId}.XPoverTime`)
                const newXPoverTime = XPoverTime - xpAmount
                this.db.set(`${userId}-${guildId}.level`, newData)
                this.db.set(`${userId}-${guildId}.XPoverTime`, newXPoverTime)
                return
            }
            this.db.subtract(`${userId}-${guildId}.level`, amount)
            this.db.subtract(`${userId}-${guildId}.XPoverTime`, xpAmount)
        } catch (error) {
            this.emit(events.error, error, 'reduceLevels')
        }
    }
    /**
     * reduce the amount of xp(s) from a user
     * @param {string} userId Id of the user you want to reduce xp from
     * @param {string} guildId Id of the guild you want to reduce xp from
     * @param {number} amount Amount of xp(s) you want to reduce
     */
    async reduceXP(userId, guildId, amount) {
        if(!userId) throw new Error('Easy Level Error: A valid user id must be provided!')
        if(!guildId) throw new Error('Easy Level Error: A valid user guild must be provided!')
        if(!amount) throw new Error('Easy Level Error: An amount must be provided!')
        try {
            if(typeof amount != 'number') throw new Error("Easy Level TypeError: Type of 'amount' must be a number")
            if(this.dbName == 'json') {
                const currectData = await this.db.get(`${userId}-${guildId}.XP`)
                const newData = currectData - amount
                const XPoverTime = await this.db.get(`${userId}-${guildId}.XPoverTime`)
                const newXPoverTime = XPoverTime - amount
                this.db.set(`${userId}-${guildId}.XP`, newData)
                this.db.set(`${userId}-${guildId}.XPoverTime`, newXPoverTime)
                return
            }
            this.db.subtract(`${userId}-${guildId}.XPoverTime`, amount)
            this.db.subtract(`${userId}-${guildId}.XP`, amount) 
        } catch (error) {
            this.emit(events.error, error, 'reduceXP')
        }
    }
    /**
     * get the top users of a guild
     * @param {string} guildId The guild id of the top users
     * @param {number} amountOfUsers Amount of people in the array
     * @returns {array} top users 
     */
    async getTopUser(guildId, amountOfUsers) {
        if(!guildId) throw new Error('Easy level Error: guildId must be a valid discord guild')
        if(!amountOfUsers) throw new Error('Easy level Error: Amount of users must defined')
        if(typeof amountOfUsers != 'number') throw new TypeError('Easy Level TypeError: Type of \'amount of users\' must be a number')
        if(this.dbName == 'json') {
            const allData = await this.db.all()
            const XPforGuild = []
            for(const key of allData) {
                if(String(key.key).includes(guildId)) {
                    XPforGuild.push({
                        xpOverTime: key.data.XPoverTime,
                        userId: key.data.userId,
                        level: key.data.level,
                        xp: key.data.XP
                    })
                }
            }
            XPforGuild.sort((a, b) => {
                return b.xpOverTime - a.xpOverTime
            })
            let top10 = []
            for(let i = 0; i < amountOfUsers; i++) {
                top10.push(XPforGuild[i])
            }
            return top10
        }
        const allData = await this.db.all()
        const XPforGuild = []
        for(const key of allData) {
            if(String(key.ID).includes(guildId)) {
                XPforGuild.push({
                    xpOverTime: key.data.XPoverTime,
                    userId: key.data.userId,
                    level: key.data.level,
                    xp: key.data.XP
                })
            }
        }
        XPforGuild.sort((a, b) => {
            return b.xpOverTime - a.xpOverTime
        })
        let top10 = []
        for(let i = 0; i < amountOfUsers; i++) {
            top10.push(XPforGuild[i])
        }
        return top10
    }
    /**
     * Generate a chart of the XP usage in a guild
     * @param {string} guildId the id of a discord guild you want the chart to generate
     * @param {number} amountOfUsers amount of users in a chart (do not set it higher than 5)
     * @returns {BufferEncoding} Image of a chart buffered
     */
    async generateXPChart(guildId, amountOfUsers) {
        if(!guildId) throw new Error('Easy Level Error: A valid discord guild must be provided')
        if(!amountOfUsers) throw new Error('Easy level Error: Amount of users must defined')
        if(typeof amountOfUsers != 'number') throw new TypeError('Easy Level TypeError: Type of \'amount of users\' must be a number')
        const bufferedChart = generateChartImage(this.dbName, this.db, amountOfUsers, guildId, this.client)
        return bufferedChart
    }
}
module.exports = {
    EasyLeveling: EasyLeveling
}