diff --git a/.npmignore b/.npmignore index 7247df5..975d5f3 100644 --- a/.npmignore +++ b/.npmignore @@ -2,7 +2,6 @@ .github .gitignore .idea -Mini_Docs node_modules pnpm-lock.yaml src diff --git a/Mini_Docs/add.md b/Mini_Docs/add.md deleted file mode 100644 index 5a9accc..0000000 --- a/Mini_Docs/add.md +++ /dev/null @@ -1,53 +0,0 @@ -## Functions - -
-
addLevel(userId, guildId, level, username)Promise.<UserResult>
-

Add XP to a user

-
-
addXP(userId, guildId, xpData, username)Promise.<XPResult>
-

Add XP to a user.

-
-
- - - -## addLevel(userId, guildId, level, username) ⇒ Promise.<UserResult> - -Add XP to a user - -**Kind**: global function -**Returns**: Promise.<UserResult> - - Object of user data on success -**Throws**: - -- XpFatal - If parameters are not provided correctly - -**Link**: `Documentation:` https://simplyxp.js.org/docs/addlevel - -| Param | Type | Description | -|----------|---------------------|-------------------------------------------| -| userId | string | | -| guildId | string | | -| level | number | | -| username | string | Username to use if auto_create is enabled | - - - -## addXP(userId, guildId, xpData, username) ⇒ Promise.<XPResult> - -Add XP to a user. - -**Kind**: global function -**Returns**: Promise.<XPResult> - - Object of user data on success. -**Throws**: - -- XpFatal - If parameters are not provided correctly. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/addxp - -| Param | Type | Description | -|----------|--------------------------------------------|--------------------------------------------------------------------------| -| userId | string | The ID of the user. | -| guildId | string | The ID of the guild. | -| xpData | number \| Object | The XP to add, can be a number or an object with min and max properties. | -| username | string | Username to use if auto_create is enabled. | - diff --git a/Mini_Docs/cards.md b/Mini_Docs/cards.md deleted file mode 100644 index ecacc83..0000000 --- a/Mini_Docs/cards.md +++ /dev/null @@ -1,51 +0,0 @@ -## Functions - -
-
rankCard(guild, user, options, locales)Promise.<{attachment: Buffer, description: string, name: string}>
-

Generate a simple user rank card

-
-
leaderboardCard(data, options, guildInfo, locales)Promise.<{attachment: Buffer, description: string, name: string}>
-

Generate a simple leaderboard card

-
-
- - - -## rankCard(guild, user, options, locales) ⇒ Promise.<{attachment: Buffer, description: string, name: string}> - -Generate a simple user rank card - -**Kind**: global function -**Throws**: - -- XpFatal - If parameters are not provided correctly - -**Link**: [Documentation](https://simplyxp.js.org/docs/rankCard) - -| Param | Type | Description | -|---------|------------------------------|-----------------------------------------------------| -| guild | Object | (id, name) | -| user | UserOptions | (id, username, avatarURL) | -| options | RankCardOptions | (background, color, legacy, lvlbar, lvlbarBg, font) | -| locales | rankLocales | [BETA] Translate the rank card | - - - -## leaderboardCard(data, options, guildInfo, locales) ⇒ Promise.<{attachment: Buffer, description: string, name: string}> - -Generate a simple leaderboard card - -**Kind**: global function -**Throws**: - -- XpFatal - If parameters are not provided correctly - -**Link**: [Documentation](https://simplyxp.js.org/docs/leaderboard) - -| Param | Type | Description | -|-----------|---------------------------------|-------------------------------------| -| data | Array.<User> | Array of user data | -| options | LeaderboardOptions | (artworkColor, artworkImage, light) | -| guildInfo | Object | Guild info | -| locales | LeaderboardLocales | Locales | - diff --git a/Mini_Docs/charts.md b/Mini_Docs/charts.md deleted file mode 100644 index a0faa93..0000000 --- a/Mini_Docs/charts.md +++ /dev/null @@ -1,19 +0,0 @@ - - -## charts(guildId, options) ⇒ Promise.<{attachment: Buffer, description: string, name: string}> - -Creates a chart - -**Kind**: global function -**Returns**: Promise.<{attachment: Buffer, description: string, name: string}> - Chart attachment -**Throws**: - -- XpFatal If invalid parameters are provided, or if there are not enough users to create a chart - -**Link**: `Documentation:` https://simplyxp.js.org/docs/charts - -| Param | Type | -|---------|---------------------------| -| guildId | string | -| options | ChartOptions | - diff --git a/Mini_Docs/connect.md b/Mini_Docs/connect.md deleted file mode 100644 index 0ab4e35..0000000 --- a/Mini_Docs/connect.md +++ /dev/null @@ -1,18 +0,0 @@ - - -## connect(uri, options) ⇒ Promise.<boolean> - -Connect to a database (MongoDB, SQLite) - -**Kind**: global function -**Throws**: - -- XpFatal If an invalid type is provided or if the value is not provided. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/connect - -| Param | Type | -|---------|--------------------------------| -| uri | string | -| options | ConnectionOptions | - diff --git a/Mini_Docs/create.md b/Mini_Docs/create.md deleted file mode 100644 index 70a420f..0000000 --- a/Mini_Docs/create.md +++ /dev/null @@ -1,19 +0,0 @@ - - -## create(userId, guildId, username) ⇒ Promise.<UserResult> - -Create a new user in the database - -**Kind**: global function -**Throws**: - -- XpFatal If invalid parameters are provided - -**Link**: `Documentation:` https://simplyxp.js.org/docs/create - -| Param | Type | -|----------|---------------------| -| userId | string | -| guildId | string | -| username | string | - diff --git a/Mini_Docs/deprecated/rank.md b/Mini_Docs/deprecated/rank.md deleted file mode 100644 index 9324591..0000000 --- a/Mini_Docs/deprecated/rank.md +++ /dev/null @@ -1,21 +0,0 @@ - - -## ~~rank(message, userId, _guildId, options) ⇒ Promise.<{attachment: Buffer, description: string, name: -string}>~~ - -***Deprecated*** - -**Kind**: global function -**Throws**: - -- XpFatal - If parameters are not provided correctly - -**Link**: `Documentation:` https://simplyxp.js.org/docs/deprecated/rank - -| Param | Type | Description | -|----------|------------------------------|-------------| -| message | Message | (DISCORD) | -| userId | string | | -| _guildId | string | | -| options | RankCardOptions | | - diff --git a/Mini_Docs/fetch.md b/Mini_Docs/fetch.md deleted file mode 100644 index dcc7034..0000000 --- a/Mini_Docs/fetch.md +++ /dev/null @@ -1,19 +0,0 @@ - - -## fetch(userId, guildId, username) ⇒ Promise.<{name: (string\|null), user: string, guild: string, level: number, position: number, xp: number}> - -Fetch user data - -**Kind**: global function -**Throws**: - -- XpFatal If invalid parameters are provided, or if the user data is not found. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/fetch - -| Param | Type | Description | -|----------|---------------------|-------------------------------------------| -| userId | string | | -| guildId | string | | -| username | string | Username to use if auto_create is enabled | - diff --git a/Mini_Docs/functions/database.md b/Mini_Docs/functions/database.md deleted file mode 100644 index e3425ed..0000000 --- a/Mini_Docs/functions/database.md +++ /dev/null @@ -1,134 +0,0 @@ - - -## db - -**Kind**: global class - -* [db](#db) - * [new db()](#new_db_new) - * [.getCollection(collection)](#db.getCollection) ⇒ Collection - * [.createOne(query)](#db.createOne) ⇒ Promise.<(UserResult\|LevelRoleResult)> - * [.deleteOne(query)](#db.deleteOne) ⇒ Promise.<boolean> - * [.findOne(query)](#db.findOne) ⇒ Promise.<(UserResult\|LevelRoleResult)> - * [.find(query)](#db.find) ⇒ Promise.<(Array.<UserResult>\|Array.<LevelRoleResult>)> - * [.updateOne(filter, update, [options])](#db.updateOne) ⇒ Promise.<(UserResult\|LevelRoleResult) - > - - - -### new db() - -Database class providing methods to interact with the database. - - - -### db.getCollection(collection) ⇒ Collection - -Gets a collection from the database. - -**Kind**: static method of [db](#db) -**Returns**: Collection - The collection. -**Throws**: - -- XpFatal Throws an error if there is no database connection, or database type is invalid. - -**Link**: https://simplyxp.js.org/docs/handlers/database#getCollection Documentation - -| Param | Type | Description | -|------------|-------------------------|------------------------| -| collection | collection | The collection to get. | - - - -### db.createOne(query) ⇒ Promise.<(UserResult\|LevelRoleResult)> - -Creates one document in the database. - -**Kind**: static method of [db](#db) -**Returns**: Promise.<(UserResult\|LevelRoleResult)> - The created document. -**Throws**: - -- XpFatal Throws an error if there is no database connection. - -**Link**: https://simplyxp.js.org/docs/handlers/database#createOne Documentation - -| Param | Type | Description | -|-------|-----------------------------------------------------------|-------------------------| -| query | UserOptions \| LevelRoleOptions | The document to create. | - - - -### db.deleteOne(query) ⇒ Promise.<boolean> - -Deletes one document from the database. - -**Kind**: static method of [db](#db) -**Returns**: Promise.<boolean> - `true` if the document was successfully deleted, -otherwise `false`. -**Throws**: - -- XpFatal Throws an error if there is no database connection. - -**Link**: https://simplyxp.js.org/docs/handlers/database#deleteOne Documentation - -| Param | Type | Description | -|-------|-----------------------------------------------------------|-------------------------| -| query | UserOptions \| LevelRoleOptions | The document to delete. | - - - -### db.findOne(query) ⇒ Promise.<(UserResult\|LevelRoleResult)> - -Finds one document in the database. - -**Kind**: static method of [db](#db) -**Returns**: Promise.<(UserResult\|LevelRoleResult)> - The found document. -**Throws**: - -- XpFatal Throws an error if there is no database connection. - -**Link**: https://simplyxp.js.org/docs/handlers/database#findOne Documentation - -| Param | Type | Description | -|-------|-----------------------------------------------------------|---------------------------------------| -| query | UserOptions \| LevelRoleOptions | The query to search for the document. | - - - -### db.find(query) ⇒ Promise.<(Array.<UserResult>\|Array.<LevelRoleResult>)> - -Finds multiple documents in the database. - -**Kind**: static method of [db](#db) -**Returns**: Promise.<(Array.<UserResult>\|Array.<LevelRoleResult>)> - An array of found -documents. -**Throws**: - -- XpFatal Throws an error if there is no database connection. - -**Link**: https://simplyxp.js.org/docs/handlers/database#find Documentation - -| Param | Type | Description | -|-------|-----------------------------------------------------------|---------------------------------------------| -| query | UserOptions \| LevelRoleOptions | The query to search for multiple documents. | - - - -### db.updateOne(filter, update, [options]) ⇒ Promise.<(UserResult\|LevelRoleResult)> - -Updates one document in the database. - -**Kind**: static method of [db](#db) -**Returns**: Promise.<(UserResult\|LevelRoleResult)> - The updated document. -**Throws**: - -- XpFatal Throws an error if there is no database connection. - -**Link**: https://simplyxp.js.org/docs/handlers/database#updateOne Documentation - -| Param | Type | Description | -|-----------|-----------------------------------------------------------|--------------------------------------------| -| filter | UserOptions \| LevelRoleOptions | The document to update. | -| update | UserOptions \| LevelRoleOptions | The document update data. | -| [options] | object | MongoDB options for updating the document. | - diff --git a/Mini_Docs/functions/utilities.md b/Mini_Docs/functions/utilities.md deleted file mode 100644 index 84bd782..0000000 --- a/Mini_Docs/functions/utilities.md +++ /dev/null @@ -1,48 +0,0 @@ -## Functions - -
-
convertFrom(type)number
-

Convert XP to level and vice versa.

-
-
updateOptions(clientOptions)void
-

Updates the options of the XP client.

-
-
- - - -## convertFrom(type) ⇒ number - -Convert XP to level and vice versa. - -**Kind**: global function -**Returns**: number - - The converted value. (XP to level or level to XP) -**Throws**: - -- XpFatal If an invalid type is provided or if the value is not provided. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/utilities/convert - -| Param | Type | Description | -|--------|---------------------------------------------------------------|----------------------------------------| -| value. | number | | -| type | "xp" \| "level" | Type to convert from (Default: level). | - - - -## updateOptions(clientOptions) ⇒ void - -Updates the options of the XP client. - -**Kind**: global function -**Returns**: void - - Nothing. -**Throws**: - -- XpFatal If an invalid option is provided. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/utilities/updateOptions - -| Param | Type | Description | -|---------------|-------------------------------|----------------------------| -| clientOptions | NewClientOptions | The new options to update. | - diff --git a/Mini_Docs/functions/xplogs.md b/Mini_Docs/functions/xplogs.md deleted file mode 100644 index d37f344..0000000 --- a/Mini_Docs/functions/xplogs.md +++ /dev/null @@ -1,26 +0,0 @@ - - -## XpFatal - -**Kind**: global class - -* [XpFatal](#XpFatal) - * [new XpFatal()](#new_XpFatal_new) - * [new XpFatal(options)](#new_XpFatal_new) - - - -### new XpFatal() - -Emits a fatal error message - - - -### new XpFatal(options) - -Emits a simple error message - -| Param | Type | -|---------|-------------------------| -| options | errOptions | - diff --git a/Mini_Docs/leaderboard.md b/Mini_Docs/leaderboard.md deleted file mode 100644 index b8812d9..0000000 --- a/Mini_Docs/leaderboard.md +++ /dev/null @@ -1,19 +0,0 @@ - - -## leaderboard(guildId, limit) ⇒ Promise.<Array.<User>> - -Get array of all users in the leaderboard - -**Kind**: global function -**Returns**: Promise.<Array.<User>> - Array of all users in the leaderboard -**Throws**: - -- XpFatal If guild ID is not provided or limit is less than 1 - -**Link**: `Documentation:` https://simplyxp.js.org/docs/leaderboard - -| Param | Type | Description | -|---------|---------------------|--------------------------| -| guildId | string | Guild ID | -| limit | number | Limit of users to return | - diff --git a/Mini_Docs/migrate.md b/Mini_Docs/migrate.md deleted file mode 100644 index 4549460..0000000 --- a/Mini_Docs/migrate.md +++ /dev/null @@ -1,54 +0,0 @@ - - -## migrate - -**Kind**: global class - -* [migrate](#migrate) - * [new migrate()](#new_migrate_new) - * [.discord_xp(deleteOld)](#migrate.discord_xp) ⇒ Promise.<boolean> - * [.fromDB(dbType, connection)](#migrate.fromDB) ⇒ Promise.<boolean> - - - -### new migrate() - -Migration functions - - - -### migrate.discord\_xp(deleteOld) ⇒ Promise.<boolean> - -Effortlessly migrate from discord-xp to simply-xp. - -**Kind**: static method of [migrate](#migrate) -**Returns**: Promise.<boolean> - - Returns true if migration is successful -**Throws**: - -- XpLog.err - If migration fails. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/migrate/discord_xp - -| Param | Type | Default | Description | -|-----------|----------------------|--------------------|---------------------------------| -| deleteOld | boolean | false | Delete old data after migration | - - - -### migrate.fromDB(dbType, connection) ⇒ Promise.<boolean> - -Effortlessly migrate from MongoDB to SQLite. (or vice versa) - -**Kind**: static method of [migrate](#migrate) -**Returns**: Promise.<boolean> - - Returns true if migration is successful -**Throws**: - -- XpFatal - If parameters are not provided correctly - -**Link**: `Documentation:` https://simplyxp.js.org/docs/migrate/database - -| Param | Type | -|------------|---------------------------------------------------------------------| -| dbType | "mongodb" \| "sqlite" | -| connection | Database \| MongoClient | - diff --git a/Mini_Docs/reset.md b/Mini_Docs/reset.md deleted file mode 100644 index 8a7cdca..0000000 --- a/Mini_Docs/reset.md +++ /dev/null @@ -1,20 +0,0 @@ - - -## reset(userId, guildId, erase, username) ⇒ Promise.<boolean> - -Reset user levels to 0 in a guild - -**Kind**: global function -**Throws**: - -- XpFatal If an invalid type is provided or if the value is not provided. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/reset - -| Param | Type | Default | Description | -|----------|----------------------|--------------------|-------------------------------------------| -| userId | string | | | -| guildId | string | | | -| erase | boolean | false | Erase user entry from the database | -| username | string | | Username to use if auto_create is enabled | - diff --git a/Mini_Docs/roleSetup.md b/Mini_Docs/roleSetup.md deleted file mode 100644 index df2c9ac..0000000 --- a/Mini_Docs/roleSetup.md +++ /dev/null @@ -1,75 +0,0 @@ - - -## roleSetup - -**Kind**: global class - -* [roleSetup](#roleSetup) - * [new roleSetup()](#new_roleSetup_new) - * [.add(guildId, options)](#roleSetup.add) ⇒ Promise.<boolean> - * [.find(guildId, levelNumber)](#roleSetup.find) ⇒ Promise.<RoleSetupObject> - * [.remove(guildId, levelNumber)](#roleSetup.remove) ⇒ Promise.<boolean> - - - -### new roleSetup() - -Setup roles for levels - - - -### roleSetup.add(guildId, options) ⇒ Promise.<boolean> - -Add a role to the role setup - -**Kind**: static method of [roleSetup](#roleSetup) -**Returns**: Promise.<boolean> - - True if successful -**Throws**: - -- XpFatal If an invalid type is provided or value is not provided. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/roleSetup/add - -| Param | Type | Description | -|---------|------------------------------|--------------------| -| guildId | string | The guild ID | -| options | RoleSetupObject | Level/role options | - - - -### roleSetup.find(guildId, levelNumber) ⇒ Promise.<RoleSetupObject> - -Find a role in roleSetup - -**Kind**: static method of [roleSetup](#roleSetup) -**Returns**: Promise.<RoleSetupObject> - - The level role object -**Throws**: - -- XpFatal If an invalid type is provided or value is not provided. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/roleSetup/find - -| Param | Type | Description | -|-------------|---------------------|------------------| -| guildId | string | The guild ID | -| levelNumber | number | The level number | - - - -### roleSetup.remove(guildId, levelNumber) ⇒ Promise.<boolean> - -Remove a level from the role setup - -**Kind**: static method of [roleSetup](#roleSetup) -**Returns**: Promise.<boolean> - - True if successful -**Throws**: - -- XpFatal If an invalid type is provided or value is not provided. - -**Link**: `Documentation:` https://simplyxp.js.org/docs/roleSetup/remove - -| Param | Type | Description | -|-------------|---------------------|------------------| -| guildId | string | The guild ID | -| levelNumber | number | The level number | - diff --git a/Mini_Docs/set.md b/Mini_Docs/set.md deleted file mode 100644 index 6538448..0000000 --- a/Mini_Docs/set.md +++ /dev/null @@ -1,53 +0,0 @@ -## Functions - -
-
setLevel(userId, guildId, level, username)Promise.<UserResult>
-

Set user level

-
-
setXP(userId, guildId, xpData, username)Promise.<XPResult>
-

Set user XP

-
-
- - - -## setLevel(userId, guildId, level, username) ⇒ Promise.<UserResult> - -Set user level - -**Kind**: global function -**Returns**: Promise.<UserResult> - - Object of user data on success -**Throws**: - -- XpFatal - If parameters are not provided correctly - -**Link**: `Documentation:` https://simplyxp.js.org/docs/setlevel - -| Param | Type | Description | -|----------|---------------------|-------------------------------------------| -| userId | string | | -| guildId | string | | -| level | number | | -| username | string | Username to use if auto_create is enabled | - - - -## setXP(userId, guildId, xpData, username) ⇒ Promise.<XPResult> - -Set user XP - -**Kind**: global function -**Returns**: Promise.<XPResult> - - Object of user data on success -**Throws**: - -- XpFatal - If parameters are not provided correctly - -**Link**: `Documentation:` https://simplyxp.js.org/docs/setxp - -| Param | Type | Description | -|----------|---------------------|-------------------------------------------| -| userId | string | | -| guildId | string | | -| xpData | number | | -| username | string | Username to use if auto_create is enabled | - diff --git a/Mini_Docs/types/ChartOptions.md b/Mini_Docs/types/ChartOptions.md deleted file mode 100644 index 5d14cff..0000000 --- a/Mini_Docs/types/ChartOptions.md +++ /dev/null @@ -1,19 +0,0 @@ -## ChartOptions - -### Properties - -- font `(string)`: The font to be used in the chart. -- limit `(2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)`: The maximum number of users to be displayed in the chart. -- theme `("blue" | "dark" | "discord" | "green" | "orange" | "red" | "space" | "yellow")`: The theme to be used in the chart. -- type `("bar" | "doughnut" | "pie")`: The type of chart to be created. - -### Example - -```typescript -export interface ChartOptions { - font?: string; - limit?: 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; - theme?: "blue" | "dark" | "discord" | "green" | "orange" | "red" | "space" | "yellow"; - type?: "bar" | "doughnut" | "pie"; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/ConnectionOptions.md b/Mini_Docs/types/ConnectionOptions.md deleted file mode 100644 index c196af6..0000000 --- a/Mini_Docs/types/ConnectionOptions.md +++ /dev/null @@ -1,25 +0,0 @@ -## ConnectionOptions - -### Description - -An interface representing the options for a database connection. - -### Properties - -- `type` (required, string): The type of the database. It can be one of the following values: "mongodb", "mysql", or " - sqlite". -- `autopurge` (boolean, optional): Indicates whether to automatically purge data of users/servers that are no longer - relevant. **(REQUIRES DISCORD/GUILDED)** -- `notify` (boolean, optional): Indicates whether to enable logging with simply-xp. -- `debug` (boolean, optional): Indicates whether to enable debugging with simply-xp. - -### Example - -```typescript -export type ConnectionOptions = { - type: "mongodb" | "sqlite" | undefined; - auto_purge?: false | unknown; - notify?: boolean; - debug?: boolean; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/DatabaseOptions.md b/Mini_Docs/types/DatabaseOptions.md deleted file mode 100644 index 163801e..0000000 --- a/Mini_Docs/types/DatabaseOptions.md +++ /dev/null @@ -1,57 +0,0 @@ -## UserOptions - -### Description - -An interface representing the options for many Database methods. - -### Properties - -- collection `(string)`: The name of the collection to search in. -- data `(object)`: The data to be inserted into the database. -- guild `(string)`: The ID of the guild for whom the document is related to. -- user `(string)`: The ID of the user for whom the document is related to. -- name `(string)`: The name of the user for whom the document is related to. -- level `(number)`: The level of the user in the guild. -- xp `(number)`: The XP of the user in the guild. - -### Example - -```typescript -export type UserOptions = { - collection: "simply-xps"; - data: { - guild: string; - user?: string; - name?: string; - level?: number; - xp?: number; - }; -} -``` - -## LevelRoleOptions - -### Description - -An interface representing the options for many Database methods. - -### Properties - -- collection `(string)`: The name of the collection to search in. -- data `(object)`: The data to be inserted into the database. -- guild `(string)`: The ID of the guild for whom the document is related to. -- level `(number)`: The level of the user in the guild. -- roles `(string | Array)`: The role(s) to be assigned to the user when they reach the specified level. - -### Example - -```typescript -export type LevelRoleOptions = { - collection: "simply-xp-levelroles"; - data: { - guild: string; - level?: number; - roles?: string | Array; - }; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/HexColor.md b/Mini_Docs/types/HexColor.md deleted file mode 100644 index 4fea51e..0000000 --- a/Mini_Docs/types/HexColor.md +++ /dev/null @@ -1,11 +0,0 @@ -## HexColor - -### Properties - -- `HexColor` (string): The hex color. - -### Example - -```typescript -type HexColor = `#${string}` | `0x${string}`; -``` \ No newline at end of file diff --git a/Mini_Docs/types/LevelRoleResult.md b/Mini_Docs/types/LevelRoleResult.md deleted file mode 100644 index e185aec..0000000 --- a/Mini_Docs/types/LevelRoleResult.md +++ /dev/null @@ -1,21 +0,0 @@ -# LevelRoleResult - -### Properties - -- `_id` (string, optional): The ID of the document. -- `guild` (string): The guild ID of the level role. -- `level` (number): The level of the level role. -- `roles` (string[] | string): The roles of the level role. -- `timestamp` (string): The timestamp of the level role. - -### Example - -```typescript -export type LevelRoleResult = { - _id?: string, - guild: string; - level: number; - roles: Array; - timestamp: string; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/RankCardOptions.md b/Mini_Docs/types/RankCardOptions.md deleted file mode 100644 index 8e2a4dd..0000000 --- a/Mini_Docs/types/RankCardOptions.md +++ /dev/null @@ -1,63 +0,0 @@ -## RankCardOptions - -### Description - -An interface representing the options for a rank card. - -### Properties - -- `background` (string, optional): The background image of the rank card. -- `color` (string, optional): The color of the rank card. -- `legacy` (boolean, optional): Indicates whether to use the legacy rank card. -- `lvlbar` (HexColor, optional): The color of the level bar. -- `lvlbarBg` (HexColor, optional): The background color of the level bar. -- `font` (string, optional): The font of the rank card. - -### Example - -```typescript -export type RankCardOptions = { - background?: URL; - color?: HexColor; - legacy?: boolean; - lvlbar?: HexColor; - lvlbarBg?: HexColor; - font?: string; -}; -``` - -## rankLocales - -### Properties - -- `level` (string, default: `"Level"`): The text to be displayed for the level. -- `next_level` (string, default: `"Next Level"`): The text to be displayed for the next level. -- `xp` (string, default: `"XP"`): The text to be displayed for the XP. - -### Example - -```typescript -export type rankLocales = { - level?: string; - next_level?: string; - xp?: string; -} -``` - -## UserOptions - -### Properties - -- `id` (string): The ID of the user. -- `username` (string): The username of the user. -- `avatarURL` (string): The avatar URL of the user. - -### Example - -```typescript -export type UserOptions = { - id: string; - username: string; - avatarURL: string; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/UserResult.md b/Mini_Docs/types/UserResult.md deleted file mode 100644 index 3f31f1b..0000000 --- a/Mini_Docs/types/UserResult.md +++ /dev/null @@ -1,23 +0,0 @@ -# UserResult - -### Properties - -- `_id` (string, optional): The ID of the document. -- `user` (string): The ID of the user. -- `name` (string, optional): The name of the user. -- `guild` (string): The ID of the guild. -- `level` (number): The level of the user in the guild. -- `xp` (number): The XP of the user in the guild. - -### Example - -```typescript -export interface UserResult { - _id?: string, - user: string; - name?: string; - guild: string; - level: number; - xp: number; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/XPResult.md b/Mini_Docs/types/XPResult.md deleted file mode 100644 index 92213b1..0000000 --- a/Mini_Docs/types/XPResult.md +++ /dev/null @@ -1,19 +0,0 @@ -# XPResult - -### Properties - -- `_id` (string, optional): The ID of the document. -- `user` (string): The ID of the user. -- `name` (string, optional): The name of the user. -- `guild` (string): The ID of the guild. -- `level` (number): The level of the user in the guild. -- `xp` (number): The XP of the user in the guild. -- `hasLevelledUp` (boolean): Whether or not the user has levelled up. - -### Example - -```typescript -interface XPResult extends UserResult { - hasLevelledUp: boolean; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/clientOptions.md b/Mini_Docs/types/clientOptions.md deleted file mode 100644 index 828575f..0000000 --- a/Mini_Docs/types/clientOptions.md +++ /dev/null @@ -1,21 +0,0 @@ -## ClientOptions - -### Properties - -- `auto_create` (boolean, optional): Whether to automatically create a database if it doesn't exist. -- `auto_purge` (boolean, optional): Whether to automatically purge the database if it exists. -- `dbOptions` (object, required): The database options. -- `notify` (boolean, optional): Whether to notify the user when they level up. -- `debug` (boolean, optional): Whether to enable debug mode. - -### Example - -```typescript -interface NewClientOptions { - auto_create: boolean; - auto_purge: boolean; - dbOptions: { type: "mongodb" | "sqlite"; database: MongoClient | Database; }; - notify: boolean; - debug: boolean; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/leaderboardCardOptions.md b/Mini_Docs/types/leaderboardCardOptions.md deleted file mode 100644 index 6e66c37..0000000 --- a/Mini_Docs/types/leaderboardCardOptions.md +++ /dev/null @@ -1,45 +0,0 @@ -## LeaderboardCardOptions - -### Description - -An interface representing the options for a leaderboard card. - -### Properties - -- `artworkColors` (Array, optional): The colors of the artwork. -- `artworkImage` (URL, optional): The image of the artwork. -- `borderColors` (Array, optional): The colors of the border. -- `backgroundColor` (HexColor, optional): The background color of the leaderboard. -- `backgroundImage` (URL, optional): The background image of the leaderboard. -- `font` (string, optional): The font of the leaderboard. -- `light` (boolean, optional): Indicates whether to use the light theme. - -### Example - -```typescript -export type LeaderboardOptions = { - artworkColors?: [HexColor, HexColor]; - artworkImage?: URL; - borderColors?: [HexColor, HexColor]; - backgroundColor?: HexColor; - backgroundImage?: URL; - font?: string; - light?: boolean; -} -``` - -## LeaderboardLocales - -### Properties - -- `level` (string, default: `"LEVEL"`): The text to be displayed for the level. -- `members` (string, default: `"Members"`): The text to be displayed for the members. - -### Example - -```typescript -export type LeaderboardLocales = { - level?: string; - members?: string; -} -``` \ No newline at end of file diff --git a/Mini_Docs/types/roleSetupObject.md b/Mini_Docs/types/roleSetupObject.md deleted file mode 100644 index 8da0902..0000000 --- a/Mini_Docs/types/roleSetupObject.md +++ /dev/null @@ -1,17 +0,0 @@ -## RoleSetupObject - -### Properties - -- `guild` (string, optional): The guild ID of the role setup. -- `level` (number): The level of the role setup. -- `roles` (string[] | string): The roles of the role setup. - -### Example - -```typescript -export type RoleSetupObject = { - guild?: string; - level: number; - roles: string[] | string; -} -``` \ No newline at end of file diff --git a/README.md b/README.md index 971f6d4..7e6a6f7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ [![Version](https://img.shields.io/npm/v/simply-xp.svg?style=for-the-badge)](https://www.npmjs.com/package/simply-xp) [![CodeFactor](https://www.codefactor.io/repository/github/abadima/simply-xp/badge?style=for-the-badge)](https://www.codefactor.io/repository/github/abadima/simply-xp) -[![Documentation](https://img.shields.io/badge/SimplyXP-Documentation-6b46d4?style=for-the-badge)](https://simplyxp.js.org) +[![Documentation](https://img.shields.io/badge/SimplyXP-Documentation-6b46d4?style=for-the-badge)](https://simplyxp.js.org/docs/next/intro/) [![Support](https://img.shields.io/badge/Discord-Support-5865F2?style=for-the-badge&logo=discord)](https://discord.gg/hjhnjYJNHX) @@ -45,6 +45,7 @@ yarn add simply-xp@dev - Added `db` class for extended database functionality - Added `leaderboardCard()` function - Added `convertFrom()` function +- Added `compareCard` function - Added `migrate` class # 🎉 V2 Changes 🎉 @@ -63,8 +64,7 @@ yarn add simply-xp@dev # ⚠️ V2 Breaking Changes ⚠️ - `create()` Now requires `username` argument. -- `charts()` Requires new arguments. -- `rank()` is **deprecated**, use `rankCard()` instead. -- `rankCard()` Requires completely new arguments. +- `charts()` Requires new arguments, and is revamped. +- `rank()` is **deprecated**, use `rankCard()` instead. (REQUIRES NEW ARGUMENTS) - `roleSetup()` functions loses `client` argument. - `leaderboard()` loses `client` argument, and returns `user` instead of `userID` \ No newline at end of file diff --git a/Tests/charts.png b/Tests/charts.png index ed52f59..510cb75 100644 Binary files a/Tests/charts.png and b/Tests/charts.png differ diff --git a/Tests/clean.mjs b/Tests/clean.mjs index 6c63bd3..c2de185 100644 --- a/Tests/clean.mjs +++ b/Tests/clean.mjs @@ -1,3 +1,5 @@ +// noinspection SpellCheckingInspection + import {readdir, readFileSync, writeFileSync} from "fs"; import {exec} from "child_process"; import {minify} from "uglify-js"; diff --git a/Tests/compareCard.png b/Tests/compareCard.png new file mode 100644 index 0000000..0ece956 Binary files /dev/null and b/Tests/compareCard.png differ diff --git a/Tests/jsdocs.cjs b/Tests/jsdocs.cjs index 50de6f4..de5aea9 100644 --- a/Tests/jsdocs.cjs +++ b/Tests/jsdocs.cjs @@ -1,4 +1,3 @@ -const jsdoc2md = require('jsdoc-to-markdown'); const { readdirSync } = require('fs'); const { resolve } = require('path'); const {execSync} = require("child_process"); diff --git a/Tests/leaderboard.png b/Tests/leaderboard.png index caa65a0..9faa795 100644 Binary files a/Tests/leaderboard.png and b/Tests/leaderboard.png differ diff --git a/Tests/rankCard.png b/Tests/rankCard.png new file mode 100644 index 0000000..72bd7a2 Binary files /dev/null and b/Tests/rankCard.png differ diff --git a/Tests/rankcard.png b/Tests/rankcard.png deleted file mode 100644 index 67c1f6e..0000000 Binary files a/Tests/rankcard.png and /dev/null differ diff --git a/Tests/test.cjs b/Tests/test.cjs index b4c00f1..cbbf959 100644 --- a/Tests/test.cjs +++ b/Tests/test.cjs @@ -31,27 +31,47 @@ async function test(dbType) { await xp.create("1234567894", "0987654321", "Snowball");*/ - await xp.setLevel("1234567890", "0987654321", 25, "Abadima"); + await xp.setLevel("326815959358898189", "0987654321", 69, "Abadima"); - await xp.setLevel("1234567893", "0987654321", 20, "Rahul"); + await xp.addXP("326815959358898189", "0987654321", 13899, "Abadima"); - await xp.setLevel("1234567894", "0987654321", 15, "Jena"); + await xp.setLevel("1234567893", "0987654321", 69, "Rahul"); - await xp.addLevel("1234567892", "0987654321", 10, "Ash"); + await xp.setLevel("1234567894", "0987654321", 20, "Sammy"); + + await xp.setLevel("1234567895", "0987654321", 15, "Jena"); + + await xp.addLevel("1234567892", "0987654321", 10, "Jeremy"); await xp.setLevel("1234567891", "0987654321", 5, "Elizabeth"); + // await xp.leaderboard("0987654321").then(console.log); + await xp.rankCard( {id: "0987654321", name: "SimplyTests"}, { - avatarURL: "https://cdn.discordapp.com/avatars/326815959358898189/67f99af24216f6d98d8d61a3b127d160.webp", - id: "1234567890", username: "Abadima" + avatarURL: "https://avatarfiles.alphacoders.com/280/280594.jpg", + id: "326815959358898189", username: "Abadima" }, - {background: "https://img.freepik.com/free-vector/gradient-wavy-purple-background_23-2149117433.jpg"} - ).then(results => { - require("fs").writeFileSync("Tests/rankcard.png", results.attachment); + {light: false}).then(results => { + require("fs").writeFileSync("Tests/rankCard.png", results.attachment); }); + await xp.compareCard( + {id: "0987654321", name: "SimplyTests"}, + { + avatarURL: "https://avatarfiles.alphacoders.com/280/280594.jpg", + id: "326815959358898189", username: "Abadima" + }, + { + avatarURL: "https://rahuletto.thedev.id/assets/logo.webp", + id: "1234567893", username: "Rahuletto" + }, + {light: false}).then(results => { + require("fs").writeFileSync("Tests/compareCard.png", results.attachment); + } + ); + await xp.leaderboard("0987654321").then(async (users) => { await xp.leaderboardCard(users, { // artworkImage: "https://th.bing.com/th/id/R.8cd8594560bd9cf4b042833a4acefaa5?rik=A6B1qYN%2b5GQAcA&riu=http%3a%2f%2fwallpaperswide.com%2fdownload%2fdesert_sky-wallpaper-2560x720.jpg&ehk=rE5VYZy8njd5ZeNT2p4sP7C5psjSf%2bxLZmV%2bvlQCffs%3d&risl=&pid=ImgRaw&r=0", @@ -67,21 +87,16 @@ async function test(dbType) { }); await xp.charts("0987654321", { - theme: "space", - type: "doughnut" + theme: "discord", type: "doughnut" }).then(results => { require("fs").writeFileSync("Tests/charts.png", results.attachment); }); - await xp.reset("1234567890", "0987654321", true); - - await xp.reset("1234567893", "0987654321", true); - - await xp.reset("1234567894", "0987654321", true); - - await xp.reset("1234567892", "0987654321", true); - - await xp.reset("1234567891", "0987654321", true); + await xp.db.deleteMany({ + collection: "simply-xps", data: { + guild: "0987654321" + } + }); } -test("mongodb"); \ No newline at end of file +test("sqlite"); \ No newline at end of file diff --git a/Tests/test.db b/Tests/test.db index c57fb6e..33caab2 100644 Binary files a/Tests/test.db and b/Tests/test.db differ diff --git a/UPDATES@DEV.md b/UPDATES@DEV.md index 217e0f9..8cc5226 100644 --- a/UPDATES@DEV.md +++ b/UPDATES@DEV.md @@ -1,5 +1,35 @@ # VERSION 2@DEV CHANGELOGS +## [DEV 5](https://github.com/Abadima/simply-xp/releases/tag/v2.0.0-dev.5) + + + +### Additions + +- Added `better-sqlite3` V9 Support +- Added `compareCard()` function, to compare two users. +- `db.deleteMany()` is now added, to make deleting multiple users easier. +- `rankCard()` now supports **Modern Design**, you can still override by passing `legacy: true` in options. + +### Changes + +- `connect()` will now install `mongodb v6` by default, versions down to `v4` are still supported. + +### Bug Fixes + +- Fix `charts()` handling invalid `type` & `theme` parameters. +- Missing `await` in `migrate.fromDB()` function. +- SQLite deleting username on `updateOne()`. + +### Improvements + +- Remove duplicate code in `charts()`. +- Updated colours in `charts()` themes, to make them more accurate and easier to differentiate. +- `leaderboard()` removes loop, and replaces with an `Asyncronous` method to improve performance. +- Tweaked `checkPackageVersion()`, which also updates `migrate` class, and `updateOptions()` function. +- Optimized font file (again), significantly lowers package size. +- Update `JSDocs` to feature our updated documentation urls. + ## [DEV 4](https://github.com/Abadima/simply-xp/releases/tag/v2.0.0-dev.4) ### Additions @@ -34,6 +64,7 @@ ## [DEV 3](https://github.com/Abadima/simply-xp/releases/tag/v2.0.0-dev.3) ### Additions + - Added `db.getCollection()` feature, useful for custom database implementations. - Implemented `migrate.fromDB()`, from MongoDB to SQLite. (Support for SQLite to MongoDB coming soon) diff --git a/package.json b/package.json index 5c14d14..939e6fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simply-xp", - "version": "2.0.0-dev.4", + "version": "2.0.0-dev.5", "description": "The easiest way to implement xp system", "main": "lib/xp.js", "scripts": { @@ -14,7 +14,10 @@ "author": "Abadima", "keywords": [ "amaribot", + "canvacord", "charts", + "compare", + "compete", "discord", "discord.js", "discord-xp", @@ -22,14 +25,15 @@ "fun", "guilded", "leaderboard", - "level role", - "level up", + "level", + "leveling", "mee6", "mongodb", "package", "simply", "simply-djs", "simplydjs", + "slack", "sqlite", "system", "xp" @@ -37,33 +41,32 @@ "license": "Apache-2.0", "repository": { "type": "git", - "url": "git+https://github.com/Rahuletto/simply-xp.git" + "url": "git+https://github.com/Abadima/simply-xp.git" }, "dependencies": { "@napi-rs/canvas": "^0.1.44" }, "devDependencies": { - "@types/better-sqlite3": "^7.6.4", - "@types/node": "^20.5.4", - "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", - "better-sqlite3": "8.5.1", + "@types/better-sqlite3": "^7.6.5", + "@types/node": "^20.8.6", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "better-sqlite3": "^9.0.0", "discord.js": "^14.13.0", - "eslint": "^8.47.0", + "eslint": "^8.51.0", "jsdoc-to-markdown": "^8.0.0", - "mongodb": "^5.8.1", - "typescript": "^5.1.6", + "mongodb": "^6.1.0", + "typescript": "^5.2.2", "uglify-js": "^3.17.4" }, "engines": { "node": ">=15.5.0" }, "bugs": { - "url": "https://github.com/Rahuletto/simply-xp/issues" + "url": "https://github.com/Abadima/simply-xp/issues" }, "homepage": "https://simplyxp.js.org", "directories": { - "doc": "Mini_Docs", "lib": "lib" } } diff --git a/src/Fonts/BalooBhaijaan-Regular.otf b/src/Fonts/BalooBhaijaan-Regular.otf deleted file mode 100644 index a395563..0000000 Binary files a/src/Fonts/BalooBhaijaan-Regular.otf and /dev/null differ diff --git a/src/Fonts/BalooBhaijaan-Regular.woff2 b/src/Fonts/BalooBhaijaan-Regular.woff2 new file mode 100644 index 0000000..aa3b883 Binary files /dev/null and b/src/Fonts/BalooBhaijaan-Regular.woff2 differ diff --git a/src/add.ts b/src/add.ts index 43a2683..045af13 100644 --- a/src/add.ts +++ b/src/add.ts @@ -10,7 +10,7 @@ import {xp} from "../xp"; * @param {string} guildId * @param {number} level * @param {string} username - Username to use if auto_create is enabled - * @link `Documentation:` https://simplyxp.js.org/docs/addlevel + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/addlevel * @returns {Promise} - Object of user data on success * @throws {XpFatal} - If parameters are not provided correctly */ @@ -62,7 +62,7 @@ interface XPResult extends UserResult { * @param {string} guildId - The ID of the guild. * @param {number | {min: number, max: number}} xpData - The XP to add, can be a number or an object with min and max properties. * @param {string} username - Username to use if auto_create is enabled. - * @link `Documentation:` https://simplyxp.js.org/docs/addxp + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/addxp * @returns {Promise} - Object of user data on success. * @throws {XpFatal} - If parameters are not provided correctly. */ diff --git a/src/cards.ts b/src/cards.ts index 04aef9d..a029386 100644 --- a/src/cards.ts +++ b/src/cards.ts @@ -14,6 +14,33 @@ let cachedLeaderboardArtwork: Image | null, cachedLeaderboardContext: SKRSContext2D | null, cachedLeaderboardImage: Image | null; +export type CompareCardLocales = { + level?: string; + versus?: string; +} + +/** + * @property {URL} background - Background image URL + * @property {HexColor} color - Avatar border color + * @property {HexColor} centerBar - Center bar color + * @property {HexColor} centerBarBg - Center bar background color + * @property {string} font - ABSOLUTE FILE PATH + * @property {boolean} light - Use light theme + */ +export interface CompareCardOptions { + background?: URL; + color?: HexColor; + centerBar?: HexColor; + centerBarBg?: HexColor; + font?: string; + light?: boolean; +} + +export type LeaderboardCardLocales = { + level?: string; + members?: string; +} + /** * @property {[HexColor, HexColor]} artworkColors - Gradient colors * @property {URL} artworkImage @@ -23,7 +50,7 @@ let cachedLeaderboardArtwork: Image | null, * @property {string} font - ABSOLUTE FILE PATH * @property {boolean} light - Use light theme */ -export interface LeaderboardOptions { +export interface LeaderboardCardOptions { artworkColors?: [HexColor, HexColor]; artworkImage?: URL; borderColors?: [HexColor, HexColor]; @@ -33,9 +60,15 @@ export interface LeaderboardOptions { light?: boolean; } +export type RankCardLocales = { + level?: string; + next_level?: string; + xp?: string; +} + /** * @property {URL} background - Background image URL - * @property {HexColor} color + * @property {HexColor} color - Avatar border color * @property {boolean} legacy - Use legacy card design * @property {HexColor} lvlbar * @property {HexColor} lvlbarBg @@ -45,250 +78,174 @@ export interface RankCardOptions { background?: URL; color?: HexColor; legacy?: boolean; + light?: boolean; lvlbar?: HexColor; lvlbarBg?: HexColor; font?: string; } -export type UserOptions = { +export type CardUserOptions = { id: string; username: string; avatarURL: string; } -export type LeaderboardLocales = { - level?: string; - members?: string; -} - -export type rankLocales = { - level?: string; - next_level?: string; - xp?: string; -} - /** - * Generate a simple user rank card + * Generate a simple comparison card * @async * @param {{id: string, name: string}} guild - (id, name) - * @param {UserOptions} user - (id, username, avatarURL) - * @param {RankCardOptions?} options - (background, color, legacy, lvlbar, lvlbarBg, font) - * @param {rankLocales?} locales - [BETA] Translate the rank card - * @link [Documentation](https://simplyxp.js.org/docs/rankCard) + * @param {CardUserOptions} user1 - User 1 + * @param {CardUserOptions} user2 - User 2 + * @param {CompareCardOptions?} options - (background, color, legacy, lvlbar, lvlbarBg, font) + * @param {CompareCardLocales?} locales - [BETA] Translate the rank card + * @link `Documentation` https://simplyxp.js.org/docs/next/functions/compareCard * @returns {Promise<{attachment: Buffer, description: string, name: string}>} - * @throws {XpFatal} - If parameters are not provided correctly + * @throws {XpFatal} - If parameters are not provided correctly or if the user is not found in the database */ -export async function rankCard(guild: { - id: string, - name: string -}, user: UserOptions, options: RankCardOptions = {}, locales: rankLocales = {}): Promise<{ +export async function compareCard(guild: { + id: string, name: string +}, user1: CardUserOptions, user2: CardUserOptions, options: CompareCardOptions = {}, locales: CompareCardLocales = {}): Promise<{ attachment: Buffer; description: string; name: string; }> { - if (!guild) throw new XpFatal({function: "rankCard()", message: "No Guild Provided"}); - if (!user) throw new XpFatal({function: "rankCard()", message: "No User Provided"}); - - // TODO: Add support for modern mode - options.legacy = true; - let canvas: Canvas, context: SKRSContext2D; - - if (!locales?.level) locales.level = "Level"; - if (!locales?.next_level) locales.next_level = "Next Level"; - if (!locales?.xp) locales.xp = "XP"; - - XpLog.debug("rankCard()", "LEGACY MODE ENABLED"); - XpLog.info("rankCard()", "Modern RankCard is coming in dev.5"); + if (!guild?.id || !guild?.name) throw new XpFatal({function: "compareCard()", message: "Please provide a guild"}); + if (!user1?.id || !user1?.username || !user2?.id || !user2?.username) throw new XpFatal({ + function: "compareCard()", + message: "Please provide two valid users!" + }); - if (!user?.avatarURL.endsWith(".png") && !user.avatarURL.endsWith(".jpg") && !user.avatarURL.endsWith(".webp")) { + if (!user1?.avatarURL.endsWith(".png") && !user1.avatarURL.endsWith(".jpg") && !user1.avatarURL.endsWith(".webp")) { throw new XpFatal({ - function: "rankCard()", message: "Invalid avatar image, avatar image must be a png, jpg, or webp" + function: "compareCard()", message: "[USER 1] Avatar image must be a png, jpg, or webp" }); } - - if (!user || !user.id || !user.username) { + if (!user2?.avatarURL.endsWith(".png") && !user2.avatarURL.endsWith(".jpg") && !user2.avatarURL.endsWith(".webp")) { throw new XpFatal({ - function: "rankCard()", message: "Invalid User Provided, user must contain id, username, and avatarURL." + function: "compareCard()", message: "[USER 2] Avatar image, avatar image must be a png, jpg, or webp" }); } - GlobalFonts.registerFromPath(options?.font || join(__dirname, "Fonts", "BalooBhaijaan-Regular.otf"), "Sans Serif"); + GlobalFonts.registerFromPath(options?.font || join(__dirname, "Fonts", "BalooBhaijaan-Regular.woff2"), "Sans Serif"); + + if (!locales?.level) locales.level = "Level"; + if (!locales?.versus) locales.versus = "vs"; - if (!cachedRankImage) cachedRankImage = await loadImage(options?.background || "https://i.ibb.co/dck2Tnt/rank-card.webp").catch(() => { + const compareImage = await loadImage(options?.background || "https://i.ibb.co/WnfXZjc/clouds.jpg").catch(() => { throw new XpFatal({ - function: "rankCard()", message: "Unable to load background image, is it valid?" + function: "compareCard()", message: "Unable to load background image, is it valid?" }); }); - const avatarURL = await loadImage(user.avatarURL).catch(() => { + const avatarURL1 = await loadImage(user1.avatarURL).catch(() => { throw new XpFatal({ - function: "rankCard()", message: "Unable to load user's AvatarURL, is it reachable?" + function: "compareCard()", message: "[USER 1] Unable to load user's AvatarURL, is it reachable?" }); }); - let dbUser = await db.findOne({collection: "simply-xps", data: {guild: guild.id, user: user.id}}) as User; - if (!dbUser) { - if (xp.auto_create) dbUser = await create(user.id, guild.id, user.username) as User; - else throw new XpFatal({function: "rankCard()", message: "User not found in database"}); - } - - const users = await db.find({collection: "simply-xps", data: {guild: guild.id}}) as User[]; - - dbUser.position = users.sort((a, b) => b.xp - a.xp).findIndex((u) => u.user === user.id) + 1; - - if (options?.legacy) { - const Username = user.username.replace(/[\u007f-\uffff]/g, ""), - BoxColor = options?.color || "#9900ff", - LevelBarFill = options?.lvlbar || "#ffffff", - LevelBarBackground = options?.lvlbarBg || "#ffffff", - TextEXP = shortener(dbUser.xp) + ` ${locales.xp}`, - LvlText = locales.level + ` ${shortener(dbUser.level)}`, - TextXpNeeded = "{current} / {needed}", - nextLevelXP = convertFrom(dbUser.level + 1), - currentLevelXP = convertFrom(dbUser.level), - progress = (((100 * (dbUser.xp - currentLevelXP)) / (nextLevelXP - currentLevelXP)) * 660) / 100; - - - if (!cachedRankContext || !cachedRankCanvas) { - canvas = createCanvas(1080, 400); - context = canvas.getContext("2d"); - - RoundedBox(context, 0, 0, canvas.width, canvas.height, 50); - context.clip(); - - context.fillStyle = "#000000"; - context.fillRect(0, 0, 1080, 400); - - context.globalAlpha = 0.7; - context.drawImage(cachedRankImage, -5, 0, 1090, 400); - context.restore(); + const avatarURL2 = await loadImage(user2.avatarURL).catch(() => { + throw new XpFatal({ + function: "compareCard()", message: "[USER 2] Unable to load user's AvatarURL, is it reachable?" + }); + }); - context.fillStyle = "#000000"; - context.globalAlpha = 0.4; - context.fillRect(40, 0, 240, canvas.height); - context.globalAlpha = 1; - } else { - canvas = cachedRankCanvas; - context = cachedRankContext; - } + let dbUser1 = await db.findOne({collection: "simply-xps", data: {guild: guild.id, user: user1.id}}) as User; + if (!dbUser1) { + if (xp.auto_create && user2?.username) dbUser1 = await create(user1.id, guild.id, user1.username) as User; + else throw new XpFatal({function: "compareCard()", message: "[USER 1] User not found in database"}); + } - context.save(); - RoundedBox(context, 70, 30, 180, 180, 50); - context.strokeStyle = BoxColor; - context.lineWidth = 15; - context.stroke(); - context.clip(); - context.drawImage(avatarURL, 70, 30, 180, 180); - context.restore(); + let dbUser2 = await db.findOne({collection: "simply-xps", data: {guild: guild.id, user: user2.id}}) as User; + if (!dbUser2) { + if (xp.auto_create && user2?.username) dbUser2 = await create(user2.id, guild.id, user2.username) as User; + else throw new XpFatal({function: "compareCard()", message: "[USER 2] User not found in database"}); + } - context.save(); - RoundedBox(context, 70, 240 + 50 + 30, 180, 50, 20); - context.strokeStyle = "#BFC85A22"; - context.stroke(); - context.clip(); - context.fillStyle = BoxColor; - context.globalAlpha = 1; - context.fillRect(70, 320, 180, 50); - context.globalAlpha = 1; - context.fillStyle = "#ffffff"; - context.textAlign = "center"; - dynamicFont(context, TextEXP, 160, 358, 160, 32); - context.restore(); + const canvas = createCanvas(1080, 400); + const context = canvas.getContext("2d"); - context.save(); - RoundedBox(context, 70, 240, 180, 50, 20); - context.strokeStyle = "#BFC85A22"; - context.stroke(); - context.clip(); - context.fillStyle = BoxColor; - context.globalAlpha = 1; - context.fillRect(70, 240, 180, 50); - context.globalAlpha = 1; - context.fillStyle = "#ffffff"; - context.textAlign = "center"; - dynamicFont(context, LvlText, 160, 278, 160, 32); - context.restore(); + RoundedBox(context, 0, 0, canvas.width, canvas.height, 25); + context.clip(); - context.save(); - context.textAlign = "left"; - context.fillStyle = "#ffffff"; - context.shadowColor = "#000000"; - context.shadowBlur = 15; - context.shadowOffsetX = 1; - context.shadowOffsetY = 1; - context.font = "39px \"Sans Serif\""; - context.fillText(Username, 390, 80); - context.restore(); + context.globalAlpha = 0.2; - context.save(); - context.textAlign = "right"; - context.fillStyle = "#ffffff"; - context.shadowColor = "#000000"; - context.shadowBlur = 15; - context.shadowOffsetX = 1; - context.shadowOffsetY = 1; - context.font = "55px \"Sans Serif\""; - context.fillText("#" + dbUser.position, canvas.width - 55, 80); - context.restore(); + context.fillStyle = options?.light ? "#ffffff" : "#000000"; + context.fill(); - context.save(); - RoundedBox(context, 390, 305, 660, 70, Number(20)); - context.strokeStyle = "#BFC85A22"; - context.stroke(); - context.clip(); - context.fillStyle = "#ffffff"; - context.textAlign = "center"; - dynamicFont(context, guild.name, 720, 355, 700, 45); - context.globalAlpha = 0.2; - context.fillRect(390, 305, 660, 70); - context.restore(); + context.globalAlpha = 0.5; - context.save(); - RoundedBox(context, 390, 145, 660, 50, 20); - context.strokeStyle = "#BFC85A22"; - context.stroke(); - context.clip(); - context.fillStyle = LevelBarBackground; - context.globalAlpha = 0.2; - context.fillRect(390, 145, 660, 50); - context.restore(); + context.drawImage(compareImage, -5, 0, 1090, 400); + context.restore(); - context.save(); - RoundedBox(context, 390, 145, progress, 50, 20); - context.strokeStyle = "#BFC85A22"; - context.stroke(); - context.clip(); - context.fillStyle = LevelBarFill; - context.globalAlpha = 0.5; - context.fillRect(390, 145, progress, 50); - context.restore(); + context.globalAlpha = 1; - context.save(); - context.textAlign = "left"; - context.fillStyle = "#ffffff"; - context.globalAlpha = 0.8; - context.font = "30px \"Sans Serif\""; - context.fillText(`${locales.next_level}: ` + shortener(nextLevelXP) + ` ${locales.xp}`, 390, 230); - context.restore(); + const Username1 = user1.username.replace(/[\u007f-\uffff]/g, ""), + Username2 = user2.username.replace(/[\u007f-\uffff]/g, ""), + cardBoxColor = options?.color || "rgba(255,255,255,0.5)", + CenterBarBackground = options?.centerBarBg || options?.light ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)", + LvlText1 = locales.level + ` ${shortener(dbUser1.level)}`, + LvlText2 = locales.level + ` ${shortener(dbUser2.level)}`; - const textXPEdited = TextXpNeeded.replace(/{needed}/g, shortener(nextLevelXP)).replace(/{current}/g, shortener(dbUser.xp)); - context.textAlign = "center"; - context.fillStyle = "#474747"; - context.globalAlpha = 1; - context.font = "30px \"Sans Serif\""; - context.fillText(textXPEdited, 730, 180); + // Add Usernames + context.save(); + context.textAlign = "center"; + context.fillStyle = "#ffffff"; + context.shadowColor = "#000000"; + context.shadowBlur = 6; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.font = "39px \"Sans Serif\""; + context.fillText(`${Username1} ${locales.versus} ${Username2}`, 540, 60); + context.restore(); + + // Add User 1 Avatar + context.save(); + context.beginPath(); + context.arc(160, 200, 100, 0, Math.PI * 2, true); + context.closePath(); + context.strokeStyle = cardBoxColor; + context.lineWidth = 15; + context.stroke(); + context.clip(); + context.drawImage(avatarURL1, 50, 90, 220, 220); + context.restore(); + + // Add User 2 Avatar + context.save(); + context.beginPath(); + context.arc(920, 200, 100, 0, Math.PI * 2, true); + context.closePath(); + context.strokeStyle = cardBoxColor; + context.lineWidth = 15; + context.stroke(); + context.clip(); + context.drawImage(avatarURL2, 810, 90, 220, 220); + context.restore(); + + // Add Level Texts + context.save(); + context.globalAlpha = 1; + context.fillStyle = "#ffffff"; + context.textAlign = "center"; + context.font = "25px \"Sans Serif\""; + context.fillText(LvlText1, 160, 350); - } else { - // TODO: Add support for modern mode + context.fillText(LvlText2, 920, 350); + context.restore(); - canvas = createCanvas(1080, 360); - } + // Add sleek center bar + context.save(); + context.globalAlpha = 1; + RoundedBox(context, 265, 330, 540, 25, 10); + context.clip(); + context.fillStyle = CenterBarBackground; + context.fill(); return { attachment: canvas.toBuffer("image/png"), - description: "Simply-XP Rank Card", - name: "rank.png" + description: "Simply-XP Comparison Card", + name: "compareCard.png" }; } @@ -296,18 +253,18 @@ export async function rankCard(guild: { * Generate a simple leaderboard card * @async * @param {Array} data - Array of user data - * @param {LeaderboardOptions?} options - (artworkColor, artworkImage, light) + * @param {LeaderboardCardOptions?} options - (artworkColor, artworkImage, light) * @param {{name: string, imageURL: string, memberCount: number}?} guildInfo - Guild info - * @param {LeaderboardLocales} locales - Locales - * @link [Documentation](https://simplyxp.js.org/docs/leaderboard) + * @param {LeaderboardCardLocales?} locales - Locales + * @link `Documentation` https://simplyxp.js.org/docs/next/functions/leaderboard * @returns {Promise<{attachment: Buffer, description: string, name: string}>} * @throws {XpFatal} - If parameters are not provided correctly */ -export async function leaderboardCard(data: Array, options: LeaderboardOptions = {}, guildInfo?: { +export async function leaderboardCard(data: Array, options: LeaderboardCardOptions = {}, guildInfo?: { name: string, imageURL: string, memberCount: number -}, locales: LeaderboardLocales = {}): Promise<{ attachment: Buffer; description: string; name: string; }> { +}, locales: LeaderboardCardLocales = {}): Promise<{ attachment: Buffer; description: string; name: string; }> { if (!data || data.length < 1) throw new XpFatal({ function: "leaderboardCard()", message: "There must be at least 1 user in the data array" @@ -325,7 +282,7 @@ export async function leaderboardCard(data: Array, options: LeaderboardOpt }); }); - GlobalFonts.registerFromPath(options?.font || join(__dirname, "Fonts", "BalooBhaijaan-Regular.otf"), "Sans Serif"); + GlobalFonts.registerFromPath(options?.font || join(__dirname, "Fonts", "BalooBhaijaan-Regular.woff2"), "Sans Serif"); if (!locales.level) locales.level = "LEVEL"; if (!locales.members) locales.members = "Members"; @@ -430,10 +387,10 @@ export async function leaderboardCard(data: Array, options: LeaderboardOpt RoundedBox(context, 30, cardY, 1290, 90, 20); } else { if (i === 0) { - RoundedBox(context, 30, cardY, 1290, 90, 20, {top: true, bottom: false}); + RoundedBox(context, 30, cardY, 1290, 90, 20, undefined, {top: true, bottom: false}); } else if (i === data.length - 1) { - RoundedBox(context, 30, cardY, 1290, 90, 20, {top: false, bottom: true}); + RoundedBox(context, 30, cardY, 1290, 90, 20, undefined, {top: false, bottom: true}); } else { RoundedBox(context, 30, cardY, 1290, 90, 0); } @@ -473,20 +430,320 @@ export async function leaderboardCard(data: Array, options: LeaderboardOpt }; } +/** + * Generate a simple user rank card + * @async + * @param {{id: string, name: string}} guild - (id, name) + * @param {CardUserOptions} user - (id, username, avatarURL) + * @param {RankCardOptions?} options - (background, color, legacy, lvlbar, lvlbarBg, font) + * @param {RankCardLocales?} locales - [BETA] Translate the rank card + * @link `Documentation` https://simplyxp.js.org/docs/next/functions/rankCard + * @returns {Promise<{attachment: Buffer, description: string, name: string}>} + * @throws {XpFatal} - If parameters are not provided correctly + */ +export async function rankCard(guild: { + id: string, name: string +}, user: CardUserOptions, options: RankCardOptions = {}, locales: RankCardLocales = {}): Promise<{ + attachment: Buffer; description: string; name: string; +}> { + if (!guild) throw new XpFatal({function: "rankCard()", message: "No Guild Provided"}); + if (!user) throw new XpFatal({function: "rankCard()", message: "No User Provided"}); + + let canvas: Canvas, context: SKRSContext2D; + + if (!locales?.level) locales.level = "Level"; + if (!locales?.next_level) locales.next_level = "Next Level"; + if (!locales?.xp) locales.xp = "XP"; + + XpLog.debug("rankCard()", `${options?.legacy ? "LEGACY" : "MODERN"} ENABLED`); + + if (!user?.avatarURL.endsWith(".png") && !user.avatarURL.endsWith(".jpg") && !user.avatarURL.endsWith(".webp")) { + throw new XpFatal({ + function: "rankCard()", message: "Invalid avatar image, avatar image must be a png, jpg, or webp" + }); + } + + + if (!user || !user.id || !user.username) { + throw new XpFatal({ + function: "rankCard()", message: "Invalid User Provided, user must contain id, username, and avatarURL." + }); + } + + GlobalFonts.registerFromPath(options?.font || join(__dirname, "Fonts", "BalooBhaijaan-Regular.woff2"), "Sans Serif"); + + if (!cachedRankImage) cachedRankImage = await loadImage(options?.background || (options?.legacy ? "https://i.ibb.co/dck2Tnt/rank-card.webp" : "https://i.ibb.co/WnfXZjc/clouds.jpg")).catch(() => { + throw new XpFatal({ + function: "rankCard()", message: "Unable to load background image, is it valid?" + }); + }); + + const avatarURL = await loadImage(user.avatarURL).catch(() => { + throw new XpFatal({ + function: "rankCard()", message: "Unable to load user's AvatarURL, is it reachable?" + }); + }); + + let dbUser = await db.findOne({collection: "simply-xps", data: {guild: guild.id, user: user.id}}) as User; + if (!dbUser) { + if (xp.auto_create) dbUser = await create(user.id, guild.id, user.username) as User; + else throw new XpFatal({function: "rankCard()", message: "User not found in database"}); + } + + const users = await db.find({collection: "simply-xps", data: {guild: guild.id}}) as User[]; + + dbUser.position = users.sort((a, b) => b.xp - a.xp).findIndex((u) => u.user === user.id) + 1; + + if (!cachedRankContext || !cachedRankCanvas) { + canvas = createCanvas(1080, 400); + context = canvas.getContext("2d"); + + RoundedBox(context, 0, 0, canvas.width, canvas.height, 25); + context.clip(); + + if (options?.legacy) { + context.globalAlpha = 1; + context.fillStyle = "#000000"; + context.fill(); + } else { + context.globalAlpha = 0.2; + context.fillStyle = options?.light ? "#ffffff" : "#000000"; + context.fill(); + + context.globalAlpha = 0.5; + } + + context.drawImage(cachedRankImage, -5, 0, 1090, 400); + context.restore(); + } else { + canvas = cachedRankCanvas; + context = cachedRankContext; + } + + const Username = user.username.replace(/[\u007f-\uffff]/g, ""), + rankBoxColor = options?.color || (options?.legacy ? "#9900ff" : "rgba(255,255,255,0.5)"), + LevelBarFill = options?.lvlbar || "#ffffff", + LevelBarBackground = options?.lvlbarBg || options?.legacy ? "#FFFFFF" : (options?.light ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"), + TextEXP = shortener(dbUser.xp) + ` ${locales.xp}`, + LvlText = locales.level + ` ${shortener(dbUser.level)}`, + TextXpNeeded = "{current} / {needed}", + nextLevelXP = convertFrom(dbUser.level + 1), + currentLevelXP = convertFrom(dbUser.level), + progress = (((100 * (dbUser.xp - currentLevelXP)) / (nextLevelXP - currentLevelXP)) * (options?.legacy ? 660 : 530)) / 100, + positionColour = dbUser.user === "326815959358898189" ? "#ade6d8" : dbUser.position === 1 ? "#ADD8E6" : dbUser.position === 2 ? "#C0C0C0" : dbUser.position === 3 ? "#CD7F32" : "#ffffff"; + + + context.save(); + if (!options?.legacy) { + context.globalAlpha = 1; + + // Add Username + context.save(); + context.textAlign = "center"; + context.fillStyle = "#ffffff"; + context.shadowColor = "#000000"; + context.shadowBlur = 5; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.font = "39px \"Sans Serif\""; + context.fillText(Username, 540, 80); + context.restore(); + + // Add Avatar + context.save(); + context.beginPath(); + context.arc(160, 200, 100, 0, Math.PI * 2, true); + context.closePath(); + context.strokeStyle = rankBoxColor; + context.lineWidth = 15; + context.stroke(); + context.clip(); + context.drawImage(avatarURL, 50, 90, 220, 220); + context.restore(); + + // Add Position Badge + context.save(); + context.beginPath(); + context.arc(230, 130, 30, 0, Math.PI * 2, true); + context.closePath(); + context.strokeStyle = rankBoxColor; + context.lineWidth = 5; + context.stroke(); + context.clip(); + + context.beginPath(); + context.arc(230, 130, 30, 0, Math.PI * 2, true); + context.closePath(); + context.fillStyle = positionColour; + context.fill(); + context.clip(); + + // Add Position Text + context.fillStyle = "#000000"; + context.textAlign = "center"; + context.font = "25px \"Sans Serif\""; + context.fillText(shortener(dbUser.position, true), 230, 138); + context.restore(); + + // Add Level Text + context.save(); + context.fillStyle = "#ffffff"; + context.font = "25px \"Sans Serif\""; + context.textAlign = "center"; + context.globalAlpha = 1; + context.fillText(LvlText, 160, 350); + context.restore(); + + // Add sleek progress bar + context.save(); + context.globalAlpha = 1; + RoundedBox(context, 265, 330, 540, 25, 10); + context.clip(); + context.fillStyle = LevelBarBackground; + context.fill(); + + // now fill the progress bar + RoundedBox(context, 270, 335, progress, 15, 5); + context.fillStyle = LevelBarFill; + context.fill(); + context.restore(); + + // Right in the middle, add the XP Text + context.save(); + context.textAlign = "center"; + context.fillStyle = "#ffffff"; + context.globalAlpha = 0.6; + context.font = "20px \"Sans Serif\""; + context.fillText(TextXpNeeded.replace(/{needed}/g, shortener(nextLevelXP)).replace(/{current}/g, shortener(dbUser.xp)), 540, 320); + + // Add Level Text (Next Level) + context.save(); + context.fillStyle = "#ffffff"; + context.textAlign = "center"; + context.globalAlpha = 1; + context.font = "25px \"Sans Serif\""; + context.fillText(`${locales.level} ` + shortener(dbUser.level + 1), 920, 350); + context.restore(); + + } else { + context.fillStyle = "#000000"; + context.globalAlpha = 0.4; + context.fillRect(40, 0, 240, canvas.height); + context.globalAlpha = 1; + + RoundedBox(context, 70, 30, 180, 180, 50); + context.strokeStyle = rankBoxColor; + context.lineWidth = 15; + context.stroke(); + context.clip(); + context.drawImage(avatarURL, 70, 30, 180, 180); + context.restore(); + + context.save(); + RoundedBox(context, 70, 240 + 50 + 30, 180, 50, 20, "#BFC85A22"); + context.fillStyle = rankBoxColor; + context.globalAlpha = 1; + context.fillRect(70, 320, 180, 50); + context.globalAlpha = 1; + context.fillStyle = "#ffffff"; + context.textAlign = "center"; + dynamicFont(context, TextEXP, 160, 358, 160, 32); + context.restore(); + + context.save(); + RoundedBox(context, 70, 240, 180, 50, 20, "#BFC85A22"); + context.fillStyle = rankBoxColor; + context.globalAlpha = 1; + context.fillRect(70, 240, 180, 50); + context.globalAlpha = 1; + context.fillStyle = "#ffffff"; + context.textAlign = "center"; + dynamicFont(context, LvlText, 160, 278, 160, 32); + context.restore(); + + context.save(); + context.textAlign = "left"; + context.fillStyle = "#ffffff"; + context.shadowColor = "#000000"; + context.shadowBlur = 15; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.font = "39px \"Sans Serif\""; + context.fillText(Username, 390, 80); + context.restore(); + + context.save(); + context.textAlign = "right"; + context.fillStyle = "#ffffff"; + context.shadowColor = "#000000"; + context.shadowBlur = 15; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.font = "55px \"Sans Serif\""; + context.fillText("#" + dbUser.position, canvas.width - 55, 80); + context.restore(); + + context.save(); + RoundedBox(context, 390, 305, 660, 70, 20, "#BFC85A22"); + context.fillStyle = "#ffffff"; + context.textAlign = "center"; + dynamicFont(context, guild.name, 720, 355, 700, 45); + context.globalAlpha = 0.2; + context.fillRect(390, 305, 660, 70); + context.restore(); + + context.save(); + RoundedBox(context, 390, 145, 660, 50, 20, "#BFC85A22"); + context.fillStyle = LevelBarBackground; + context.globalAlpha = 0.2; + context.fillRect(390, 145, 660, 50); + context.restore(); + + context.save(); + RoundedBox(context, 390, 145, progress, 50, 20, "#BFC85A22"); + context.fillStyle = LevelBarFill; + context.globalAlpha = 0.5; + context.fillRect(390, 145, progress, 50); + context.restore(); + + context.save(); + context.textAlign = "left"; + context.fillStyle = "#ffffff"; + context.globalAlpha = 0.8; + context.font = "30px \"Sans Serif\""; + context.fillText(`${locales.next_level}: ` + shortener(nextLevelXP) + ` ${locales.xp}`, 390, 230); + context.restore(); + + const textXPEdited = TextXpNeeded.replace(/{needed}/g, shortener(nextLevelXP)).replace(/{current}/g, shortener(dbUser.xp)); + context.textAlign = "center"; + context.fillStyle = "#474747"; + context.globalAlpha = 1; + context.font = "30px \"Sans Serif\""; + context.fillText(textXPEdited, 730, 180); + } + + return { + attachment: canvas.toBuffer("image/png"), + description: "Simply-XP Rank Card", + name: "rank.png" + }; +} + /** * @constructor * @private */ -export function RoundedBox(ctx: { - beginPath: () => void; - moveTo: (arg0: number, arg1: number) => void; - lineTo: (arg0: number, arg1: number) => void; - quadraticCurveTo: (arg0: number, arg1: number, arg2: number, arg3: number) => void; - closePath: () => void; -}, x: number, y: number, width: number, height: number, radius: number, roundCorners: { - top?: boolean, - bottom?: boolean -} = {top: true, bottom: true}) { +export function RoundedBox( + ctx: SKRSContext2D, + x: number, + y: number, + width: number, + height: number, + radius: number, + strokeColor?: string, roundCorners: { + top?: boolean, + bottom?: boolean + } = {top: true, bottom: true}) { ctx.beginPath(); ctx.moveTo(x + (roundCorners.top ? radius : 0), y); ctx.lineTo(x + width - (roundCorners.top ? radius : 0), y); @@ -511,30 +768,56 @@ export function RoundedBox(ctx: { ctx.quadraticCurveTo(x, y, x + radius, y); } ctx.closePath(); + + if (strokeColor) { + ctx.strokeStyle = strokeColor; + ctx.stroke(); + ctx.clip(); + } } -function shortener(count: number | undefined): string { - const COUNT_ABBREVIATIONS = [ - "", // 0 - "k", // Thousands - "M", // Millions - "B", // Billions - "T", // Trillions - "Qa", // Quadrillions - "Qi", // Quintillions - "Sx", // Sextillions - "Sp", // Septillions - ]; +function shortener(count: number | undefined, roundedNumber?: boolean): string { + let abbreviation = "", i = 0; + const base = 1000; if (!count || count === 0) return "0"; if (count === Infinity) return "∞"; - const i = Math.floor(Math.log(count) / Math.log(1000)); - if (i >= COUNT_ABBREVIATIONS.length) { - return count.toFixed(0) + COUNT_ABBREVIATIONS[COUNT_ABBREVIATIONS.length - 1]; - } else { - return (count / Math.pow(1000, i)).toFixed(i === 0 ? 0 : 2) + COUNT_ABBREVIATIONS[i]; + while (count >= base && i < 8) { + count /= base; + i++; } + + switch (i) { + case 1: + abbreviation = "K"; // Thousands + break; + case 2: + abbreviation = "M"; // Millions + break; + case 3: + abbreviation = "B"; // Billions + break; + case 4: + abbreviation = "T"; // Trillions + break; + case 5: + abbreviation = "Qa"; // Quadrillions + break; + case 6: + abbreviation = "Qi"; // Quintillions + break; + case 7: + abbreviation = "Sx"; // Sextillions + break; + case 8: + abbreviation = "Sp"; // Septillions + break; + default: + break; + } + + return `${count.toFixed(i === 0 ? 0 : (roundedNumber ? 0 : 2))}${abbreviation}`; } diff --git a/src/charts.ts b/src/charts.ts index 4b1e938..a66162e 100644 --- a/src/charts.ts +++ b/src/charts.ts @@ -2,7 +2,7 @@ import {createCanvas, GlobalFonts} from "@napi-rs/canvas"; import {join} from "path"; import {leaderboard} from "./leaderboard"; import {RoundedBox} from "./cards"; -import {XpFatal} from "./functions/xplogs"; +import {XpFatal, XpLog} from "./functions/xplogs"; /** * Chart options @@ -23,7 +23,7 @@ export interface ChartOptions { * @async * @param {string} guildId * @param {ChartOptions?} options - * @link `Documentation:` https://simplyxp.js.org/docs/charts + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/charts * @returns {Promise<{attachment: Buffer, description: string, name: string}>} Chart attachment * @throws {XpFatal} If invalid parameters are provided, or if there are not enough users to create a chart */ @@ -32,8 +32,14 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi }> { if (!guildId) throw new XpFatal({function: "charts()", message: "No Guild ID Provided"}); if (!options) throw new XpFatal({function: "charts()", message: "No Options Provided"}); - if (!options.theme) options.theme = "blue"; - if (!options.type) options.type = "bar"; + if (!options.theme || !["blue", "dark", "discord", "green", "orange", "red", "space", "yellow"].includes(options.theme)) { + XpLog.warn("charts()", "Invalid theme provided, defaulting to discord"); + options.theme = "discord"; + } + if (!options.type || !["bar", "doughnut", "pie"].includes(options.type)) { + XpLog.warn("charts()", "Invalid type provided, defaulting to bar chart"); + options.type = "bar"; + } let colors = { background: "#FFFFFF", barColor: "#FFFFFF", @@ -48,14 +54,14 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi if (users.length < 2) throw new XpFatal({function: "charts()", message: "Not enough users to create a chart"}); users.sort((a, b) => b.position - a.position); - GlobalFonts.registerFromPath(options?.font || join(__dirname, "Fonts", "BalooBhaijaan-Regular.otf"), "Sans Serif"); + GlobalFonts.registerFromPath(options?.font || join(__dirname, "Fonts", "BalooBhaijaan-Regular.woff2"), "Sans Serif"); switch (options.theme) { case "blue": colors = { background: "#1e1e3c", barColor: "#747fff", - pieColors: ["#747fff", "#55b9f3", "#4dc7ec", "#3ad5e5", "#32e3dd", "#2cf2d4", "#26ffd2", "#30edb4", "#3cda96", "#48c878"], + pieColors: ["#747fff", "#2832C2", "#59788E", "#00d2e7", "#281E5D", "#a9f5ff", "#000e3f", "#30edc2", "#186c84", "#0098ff"], textColor: "#FFFFFF" }; break; @@ -64,7 +70,7 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi colors = { background: "#1e1e1e", barColor: "#747474", - pieColors: ["#747474", "#8f8f8f", "#a8a8a8", "#c1c1c1", "#dadada", "#f4f4f4", "#ffffff", "#ffffff", "#ffffff", "#ffffff"], + pieColors: ["#1B1D1F", "#454C53", "#72787F", "#999999", "#9EA4AA", "#CCCCCC", "#C9CDD2", "#DEDEDE", "#E8EBED", "#FFFFFF"], textColor: "#FFFFFF" }; break; @@ -73,7 +79,7 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi colors = { background: "#36393f", barColor: "#5865F2", - pieColors: ["#5865F2", "#57F287", "#FEE75C", "#ED4245", "#F47FFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF"], + pieColors: ["#5865F2", "#57F287", "#FEE75C", "#EB459E", "#ED4245", "#FFFFFF", "#000000", "#FAA61A", "#C04DF9", "#00AAFF"], textColor: "#FFFFFF" }; break; @@ -82,7 +88,7 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi colors = { background: "#1e321e", barColor: "#74ff7f", - pieColors: ["#74ff7f", "#55f3a0", "#4decb2", "#3dd5c3", "#32cdd5", "#2cc6e6", "#26bfee", "#30a8e6", "#3c91dd", "#487ad4"], + pieColors: ["#00FF00", "#008000", "#7FFF00", "#32CD32", "#228B22", "#006400", "#9ACD32", "#00FA9A", "#ADFF2F", "#7CFC00"], textColor: "#FFFFFF" }; break; @@ -91,7 +97,7 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi colors = { background: "#321e1e", barColor: "#ff9f74", - pieColors: ["#ff9f74", "#f3b055", "#ecbe4d", "#d5c63d", "#cdd532", "#c6e62c", "#bfe626", "#a8e630", "#91dd3c", "#7ad448"], + pieColors: ["#FF8C00", "#FF5E0E", "#FF4500", "#FF6347", "#E26310", "#F5761A", "#FD673A", "#FFA500", "#FF7F50", "#FFD700"], textColor: "#FFFFFF" }; break; @@ -100,7 +106,7 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi colors = { background: "#321e1e", barColor: "#ff7474", - pieColors: ["#ff7474", "#f35555", "#ec4d4d", "#d53d3d", "#cd3232", "#c62c2c", "#bf2626", "#a83030", "#913c3c", "#7a4848"], + pieColors: ["#FF0000", "#FF2400", "#FF4500", "#FF6347", "#FF7F50", "#FF8C00", "#FFA07A", "#FFA500", "#FFC0CB", "#FFD700"], textColor: "#FFFFFF" }; break; @@ -118,7 +124,7 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi colors = { background: "#32321e", barColor: "#ffff74", - pieColors: ["#ffff74", "#f3f355", "#ecf24d", "#d5eb3d", "#cde532", "#c6e02c", "#bfe626", "#a8df30", "#91d93c", "#7ad448"], + pieColors: ["#FFFD37", "#FFEF00", "#FDFF00", "#DAA520", "#F4C430", "#E4D00A", "#D2B55B", "#FFFFE0", "#FFFACD", "#F5DEB3"], textColor: "#FFFFFF" }; break; @@ -191,13 +197,15 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi } } + let chartAreaWidth = canvas.width - 20 * 2; + let chartAreaHeight = canvas.height - 20 * 2; switch (options.type) { case "bar": { const maxValueLabelWidth = context.measureText(maxLevel.toString()).width; - const chartAreaWidth = canvas.width - maxValueLabelWidth - 20 * 3 - 20 * 2; - const chartAreaHeight = canvas.height - 100 - 20 * 2; + chartAreaWidth = canvas.width - maxValueLabelWidth - 20 * 3 - 20 * 2; + chartAreaHeight = canvas.height - 100 - 20 * 2; const barWidth = chartAreaWidth / users.length - 20; @@ -259,17 +267,12 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi break; case "doughnut": { - const chartAreaWidth = canvas.width - 20 * 2; - const chartAreaHeight = canvas.height - 20 * 2; const totalLevelSum = users.reduce((sum, user) => sum + user.level, 0); - - const centerX = canvas.width / 2; - const centerY = canvas.height / 2; - const outerRadius = Math.min(chartAreaWidth, chartAreaHeight) / 3; // Adjust the divisor for a smaller outer radius const innerRadius = outerRadius * 0.6; // Adjust the multiplier for the size of the hole let startAngle = -Math.PI / 2; + const centerX = canvas.width / 2, centerY = canvas.height / 2; await Promise.all( users.map(async (user, index) => { @@ -288,44 +291,15 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi startAngle = endAngle; }) ); - - // Render legend - const legendX = 20; // Legend position from left - const legendY = canvas.height - 20 - users.length * 20; // Legend position from bottom - const legendSpacing = 20; // Vertical spacing between legend items - - context.fillStyle = "rgba(0,0,0,0.25)"; - context.fillRect(legendX - 5, legendY - 5, 200, users.length * legendSpacing + 5); - - context.font = "12px Sans Serif"; - users.forEach((user, index) => { - const legendText = user?.name || user.user; - const legendColor = colors.pieColors[index % colors.pieColors.length]; - const legendColorBoxX = legendX; - const legendItemY = legendY + index * legendSpacing; - - // Place colored squares to the right and usernames to the left - context.fillStyle = legendColor || "#FFFFFF"; - context.fillRect(legendColorBoxX, legendItemY, 15, 15); - - context.fillStyle = colors.textColor; - context.fillText(legendText, legendColorBoxX + 20, legendItemY + 11.5); - }); - } break; case "pie": { - const chartAreaWidth = canvas.width - 20 * 2; - const chartAreaHeight = canvas.height - 20 * 2; const totalLevelSum = users.reduce((sum, user) => sum + user.level, 0); - - const centerX = canvas.width / 2; - const centerY = canvas.height / 2; - const radius = Math.min(chartAreaWidth, chartAreaHeight) / 3; // Adjust the divisor for a smaller radius let startAngle = -Math.PI / 2; + const centerX = canvas.width / 2, centerY = canvas.height / 2; await Promise.all( users.map(async (user, index) => { @@ -342,8 +316,14 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi startAngle = endAngle; }) ); + } + break; - // Render legend + default: + throw new XpFatal({function: "charts()", message: "Invalid chart type provided"}); + } + + if (["doughnut", "pie"].includes(options.type)) { // Render legend const legendX = 20; // Legend position from left const legendY = canvas.height - 20 - users.length * 20; // Legend position from bottom const legendSpacing = 20; // Vertical spacing between legend items @@ -352,7 +332,7 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi context.fillRect(legendX - 5, legendY - 5, 200, users.length * legendSpacing + 5); context.font = "12px Sans Serif"; - users.forEach((user, index) => { + await Promise.all(users.map(async (user, index) => { const legendText = user?.name || user.user; const legendColor = colors.pieColors[index % colors.pieColors.length]; const legendColorBoxX = legendX; @@ -364,13 +344,8 @@ export async function charts(guildId: string, options: ChartOptions = {}): Promi context.fillStyle = colors.textColor; context.fillText(legendText, legendColorBoxX + 20, legendItemY + 11.5); - }); + })); - } - break; - - default: - throw new XpFatal({function: "charts()", message: "Invalid chart type provided"}); } return { diff --git a/src/connect.ts b/src/connect.ts index 8d5259e..2f03d20 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -16,7 +16,7 @@ export type ConnectionOptions = { * @async * @param {string} uri * @param {ConnectionOptions} options - * @link `Documentation:` https://simplyxp.js.org/docs/connect + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/connect * @returns {Promise} * @throws {XpFatal} If an invalid type is provided or if the value is not provided. */ @@ -35,8 +35,8 @@ export async function connect(uri: string, options: ConnectionOptions = {type: u switch (type) { case "mongodb": { - const {MongoClient} = await import("mongodb"), goodVersion = await checkPackageVersion("mongodb"); - if (!goodVersion) return XpLog.err("connect()", "MongoDB V4 or higher is required"); + const {MongoClient} = await import("mongodb"), goodVersion = await checkPackageVersion("mongodb", 4, 6); + if (!goodVersion) return XpLog.err("connect()", "MongoDB Version 4 to 6 is required"); const client = await MongoClient.connect(uri).catch((error) => { throw new XpFatal({function: "connect()", message: error.message}); @@ -49,10 +49,10 @@ export async function connect(uri: string, options: ConnectionOptions = {type: u case "sqlite": try { const [betterSqlite3, goodVersion] = await Promise.all([ - import("better-sqlite3"), checkPackageVersion("sqlite") + import("better-sqlite3"), checkPackageVersion("better-sqlite3", 7, 9) ]); - if (!goodVersion) return XpLog.err("connect()", "better-sqlite3 V7 or higher is required"); + if (!goodVersion) return XpLog.err("connect()", "better-sqlite3 Version 7 to 9 is required"); xp.database = new betterSqlite3.default(uri); xp.dbType = "sqlite"; @@ -61,7 +61,7 @@ export async function connect(uri: string, options: ConnectionOptions = {type: u ( user TEXT NOT NULL, guild TEXT NOT NULL, - name TEXT DEFAULT "Unknown", + name TEXT DEFAULT user, level INTEGER DEFAULT 0, xp INTEGER DEFAULT 0 )` @@ -103,7 +103,7 @@ async function getPackageManager(): Promise<"yarn" | "npm" | "pnpm"> { if (foundLockfiles.length === 1) { if (foundLockfiles[0] === "yarn.lock") { - XpLog.debug("getPackageManager()", "Using Yarn"); + XpLog.debug("getPackageManager()", "Using YARN"); return "yarn"; } else if (foundLockfiles[0] === "pnpm-lock.yaml" || foundLockfiles[0] === "pnpm-lock.json") { XpLog.debug("getPackageManager()", "Using PNPM"); @@ -117,32 +117,20 @@ async function getPackageManager(): Promise<"yarn" | "npm" | "pnpm"> { /** * Check database package versions * @private - * @param {"mongodb" | "sqlite"} type + * @param {string} type - NPM Package Name (lowercase) + * @param {number} min - Minimum Major Version Number + * @param {number} max - Maximum Major Version Number (Optional) * @returns {Promise} * @throws {XpFatal} If the package version is not supported */ -export async function checkPackageVersion(type: "mongodb" | "sqlite"): Promise { - switch (type) { - case "mongodb": - try { - const mongoPackage = await import("mongodb/package.json"); - return parseInt(mongoPackage.version.substring(0, 1)) >= 4; - } catch (_) { - XpLog.info("checkPackageVersion()", "Installing MongoDB [5.x] | Please wait..."); - execSync(`${await getPackageManager()} add mongodb@5.x.x`); - XpLog.warn("checkPackageVersion()", "Installed MongoDB. Please restart!"); - return process.exit(1); - } - - case "sqlite": - try { - const sqlitePackage = await import("better-sqlite3/package.json"); - return parseInt(sqlitePackage.version.substring(0, 1)) >= 7; - } catch (_) { - XpLog.info("checkPackageVersion()", "Installing better-sqlite3 [V8] | Please wait..."); - execSync(`${await getPackageManager()} add better-sqlite3@8.x.x`); - XpLog.warn("checkPackageVersion()", "Installed better-sqlite3. Please restart!"); - return process.exit(1); - } +export async function checkPackageVersion(type: string, min: number, max?: number): Promise { + try { + const chosenPackage = await import(`${type}/package.json`); + return parseInt(chosenPackage.version.substring(0, 1)) >= min && (max ? parseInt(chosenPackage.version.substring(0, 1)) <= max : true); + } catch (_) { + XpLog.info("checkPackageVersion()", `Installing ${type} [V${max || min}] | Please wait...`); + execSync(`${await getPackageManager()} add ${type}@${max || min}.x.x`); + XpLog.warn("checkPackageVersion()", `Installed ${type}. Please restart!`); + return process.exit(1); } } \ No newline at end of file diff --git a/src/create.ts b/src/create.ts index 7811ac7..d1b8df2 100644 --- a/src/create.ts +++ b/src/create.ts @@ -7,7 +7,7 @@ import {UserResult} from "./functions/database"; * @param {string} userId * @param {string} guildId * @param {string} username - * @link `Documentation:` https://simplyxp.js.org/docs/create + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/create * @returns {Promise} * @throws {XpFatal} If invalid parameters are provided */ diff --git a/src/deprecated/rank.ts b/src/deprecated/rank.ts index 5a00e54..abc8526 100644 --- a/src/deprecated/rank.ts +++ b/src/deprecated/rank.ts @@ -10,7 +10,7 @@ import {rankCard, RankCardOptions} from "../cards"; * @param {string} userId * @param {string} _guildId * @param {RankCardOptions?} options - * @link `Documentation:` https://simplyxp.js.org/docs/deprecated/rank + * @link `Documentation:` https://simplyxp.js.org/docs/next/deprecated/rank * @returns {Promise<{attachment: Buffer, description: string, name: string}>} * @throws {XpFatal} - If parameters are not provided correctly */ diff --git a/src/fetch.ts b/src/fetch.ts index cc5c02c..6a10a9d 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -8,12 +8,12 @@ import {UserResult} from "./functions/database"; * @async * @param {string} userId * @param {string} guildId - * @param {string} username - Username to use if auto_create is enabled - * @link `Documentation:` https://simplyxp.js.org/docs/fetch + * @param {string?} username - Username to use if auto_create is enabled + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/fetch * @returns {Promise<{name: string | null, user: string, guild: string, level: number, position: number, xp: number}>} * @throws {XpFatal} If invalid parameters are provided, or if the user data is not found. */ -export async function fetch(userId: string, guildId: string, username: string): Promise { +export async function fetch(userId: string, guildId: string, username?: string): Promise { if (!userId) throw new XpFatal({function: "create()", message: "User ID was not provided"}); if (!guildId) throw new XpFatal({function: "create()", message: "Guild ID was not provided"}); diff --git a/src/functions/database.ts b/src/functions/database.ts index 43d031a..72dab38 100644 --- a/src/functions/database.ts +++ b/src/functions/database.ts @@ -78,7 +78,7 @@ export class db { /** * Gets a collection from the database. * @param {collection} collection - The collection to get. - * @link https://simplyxp.js.org/docs/handlers/database#getCollection Documentation + * @link https://simplyxp.js.org/docs/next/handlers/database#getCollection Documentation * @returns {Collection} The collection. * @throws {XpFatal} Throws an error if there is no database connection, or database type is invalid. */ @@ -93,7 +93,7 @@ export class db { * * @async * @param {UserOptions | LevelRoleOptions} query - The document to create. - * @link https://simplyxp.js.org/docs/handlers/database#createOne Documentation + * @link https://simplyxp.js.org/docs/next/handlers/database#createOne Documentation * @returns {Promise} The created document. * @throws {XpFatal} Throws an error if there is no database connection. */ @@ -115,12 +115,35 @@ export class db { return result as UserResult | LevelRoleResult; } + /** + * Deletes multiple documents from the database. + * @async + * @param {UserOptions | LevelRoleOptions} query - The documents to delete. + * @link https://simplyxp.js.org/docs/next/handlers/database#deleteMany Documentation + * @returns {Promise} `true` if the documents were successfully deleted, otherwise `false`. + * @throws {XpFatal} Throws an error if there is no database connection. + */ + static async deleteMany(query: UserOptions | LevelRoleOptions): Promise { + if (!xp.database) throw new XpFatal({function: "deleteMany()", message: "No database connection"}); + let result: Document; + + switch (xp.dbType) { + case "mongodb": + result = (xp.database as MongoClient).db().collection(query.collection).deleteMany(query.data).catch(error => handleError(error, "deleteMany()")) as Document; + break; + case "sqlite": + if (query.collection === "simply-xps") result = (xp.database as Database).prepare("DELETE FROM \"simply-xps\" WHERE guild = ?").run(query.data.guild); + else result = (xp.database as Database).prepare("DELETE FROM \"simply-xp-levelroles\" WHERE guild = ?").run(query.data.guild); + } + return !!result; + } + /** * Deletes one document from the database. * * @async * @param {UserOptions | LevelRoleOptions} query - The document to delete. - * @link https://simplyxp.js.org/docs/handlers/database#deleteOne Documentation + * @link https://simplyxp.js.org/docs/next/handlers/database#deleteOne Documentation * @returns {Promise} `true` if the document was successfully deleted, otherwise `false`. * @throws {XpFatal} Throws an error if there is no database connection. */ @@ -144,7 +167,7 @@ export class db { * * @async * @param {UserOptions | LevelRoleOptions} query - The query to search for the document. - * @link https://simplyxp.js.org/docs/handlers/database#findOne Documentation + * @link https://simplyxp.js.org/docs/next/handlers/database#findOne Documentation * @returns {Promise} The found document. * @throws {XpFatal} Throws an error if there is no database connection. */ @@ -170,7 +193,7 @@ export class db { * * @async * @param {UserOptions | LevelRoleOptions} query - The query to search for multiple documents. - * @link https://simplyxp.js.org/docs/handlers/database#find Documentation + * @link https://simplyxp.js.org/docs/next/handlers/database#find Documentation * @returns {Promise} An array of found documents. * @throws {XpFatal} Throws an error if there is no database connection. */ @@ -198,7 +221,7 @@ export class db { * @param {UserOptions | LevelRoleOptions} filter - The document to update. * @param {UserOptions | LevelRoleOptions} update - The document update data. * @param {object} [options] - MongoDB options for updating the document. - * @link https://simplyxp.js.org/docs/handlers/database#updateOne Documentation + * @link https://simplyxp.js.org/docs/next/handlers/database#updateOne Documentation * @returns {Promise} The updated document. * @throws {XpFatal} Throws an error if there is no database connection. */ @@ -211,7 +234,7 @@ export class db { break; case "sqlite": - if (filter.collection === "simply-xps" && update.collection === "simply-xps") (xp.database as Database).prepare("UPDATE \"simply-xps\" SET name = ?, xp = ?, level = ? WHERE guild = ? AND user = ?").run(update.data?.name, update.data.xp, update.data.level, filter.data.guild, filter.data.user); + if (filter.collection === "simply-xps" && update.collection === "simply-xps") (xp.database as Database).prepare("UPDATE \"simply-xps\" SET xp = ?, level = ?"+ (update.data?.name ? ", name = ?" : "") +" WHERE guild = ? AND user = ?").run(update.data?.name ? [update.data.xp, update.data.level, update.data.name, filter.data.guild, filter.data.user] : [update.data.xp, update.data.level, filter.data.guild, filter.data.user]); else if (filter.collection === "simply-xp-levelroles" && update.collection === "simply-xp-levelroles") (xp.database as Database).prepare("UPDATE \"simply-xp-levelroles\" SET role = ? WHERE guild = ? AND level = ?").run(update.data.roles, filter.data.guild, filter.data.level); else throw new XpFatal({ function: "updateOne()", message: "Collection mismatch, expected same collection on both filter and update." diff --git a/src/functions/utilities.ts b/src/functions/utilities.ts index edc8dab..2c0331e 100644 --- a/src/functions/utilities.ts +++ b/src/functions/utilities.ts @@ -27,7 +27,7 @@ interface NewClientOptions { * * @param {number} value. * @param {"xp" | "level"} type - Type to convert from (Default: level). - * @link `Documentation:` https://simplyxp.js.org/docs/utilities/convert + * @link `Documentation:` https://simplyxp.js.org/docs/next/utilities/convert * @returns {number} - The converted value. (XP to level or level to XP) * @throws {XpFatal} If an invalid type is provided or if the value is not provided. */ @@ -47,7 +47,7 @@ export function convertFrom(value: number, type: "xp" | "level" = "level"): numb /** * Updates the options of the XP client. * @param {NewClientOptions} clientOptions - The new options to update. - * @link `Documentation:` https://simplyxp.js.org/docs/utilities/updateOptions + * @link `Documentation:` https://simplyxp.js.org/docs/next/utilities/updateOptions * @returns {void} - Nothing. * @throws {XpFatal} If an invalid option is provided. */ @@ -71,32 +71,32 @@ export function updateOptions(clientOptions: NewClientOptions): void { if (database) { xp.database = database; + const dbInfo = xp.dbType === "mongodb" ? + {name: "MongoDB", type: "mongodb", min: 4, max: 6} : + {name: "Better-SQLite3", type: "better-sqlite3", min: 7, max: 9}; - if (xp.dbType === "mongodb") { - checkPackageVersion("mongodb").then((result) => { - if (!result) throw new XpFatal({ - function: "updateOptions()", message: "MongoDB V4 or higher is required" - }); + checkPackageVersion(dbInfo.type, dbInfo.min, dbInfo.max).then((result) => { + if (!result) throw new XpFatal({ + function: "updateOptions()", message: `${dbInfo.name} V${dbInfo.min} up to V${dbInfo.max} is required.` + }); + switch (xp.dbType) { + case "mongodb": (xp.database as MongoClient).db().command({ping: 1}).catch(() => { xp.database = undefined; throw new XpFatal({function: "updateOptions()", message: "Invalid MongoDB connection"}); }); - }); - - } else if (xp.dbType === "sqlite") { - checkPackageVersion("sqlite").then((result) => { - if (!result) throw new XpFatal({ - function: "updateOptions()", message: "SQLite V7 or higher is required" - }); + break; + case "sqlite": try { (xp.database as Database).prepare("SELECT 1").get(); } catch (error) { throw new XpFatal({function: "updateOptions()", message: "Invalid SQLite connection"}); } - }); - } + break; + } + }); } } } \ No newline at end of file diff --git a/src/leaderboard.ts b/src/leaderboard.ts index 6ee17bf..0dcb2f3 100644 --- a/src/leaderboard.ts +++ b/src/leaderboard.ts @@ -10,7 +10,7 @@ import {db} from "./functions/database"; * @property {number} level - User level * @property {number} xp - User XP */ -export interface User { +export interface User { guild: string; user: string; name?: string | null; @@ -24,17 +24,25 @@ export interface User { * @async * @param {string} guildId - Guild ID * @param {number} limit - Limit of users to return - * @link `Documentation:` https://simplyxp.js.org/docs/leaderboard + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/leaderboard * @returns {Promise} Array of all users in the leaderboard * @throws {XpFatal} If guild ID is not provided or limit is less than 1 */ export async function leaderboard(guildId: string, limit?: number): Promise { if (!guildId) throw new XpFatal({function: "leaderboard()", message: "Guild ID was not provided"}); - if (limit && limit < 1) throw new XpFatal({function: "leaderboard()", message: "Limit must be greater than 0"}); + if (limit && !(limit >= 1)) throw new XpFatal({ + function: "leaderboard()", message: "Limit must be a number greater than 0" + }); - const users = (await db.find({collection: "simply-xps", data: {guild: guildId}}) as Array).sort((a, b) => b.xp - a.xp); - users.forEach((user, index) => user.position = index + 1); + const users = (await db.find({ + collection: "simply-xps", data: {guild: guildId} + }) as Array).sort((a, b) => b.xp - a.xp); - if (limit) return users.slice(0, limit); - return users; + + await Promise.all(users.map(async (user, index) => { + user.position = index + 1; + return user; + })); + + return users.slice(0, limit); } \ No newline at end of file diff --git a/src/migrate.ts b/src/migrate.ts index ce1269a..2739b1e 100644 --- a/src/migrate.ts +++ b/src/migrate.ts @@ -15,7 +15,7 @@ export class migrate { * Effortlessly migrate from discord-xp to simply-xp. * @async * @param {boolean} deleteOld - Delete old data after migration - * @link `Documentation:` https://simplyxp.js.org/docs/migrate/discord_xp + * @link `Documentation:` https://simplyxp.js.org/docs/next/classes/migrate#migratediscord_xp * @returns {Promise} - Returns true if migration is successful * @throws {XpLog.err} - If migration fails. */ @@ -49,7 +49,7 @@ export class migrate { * @async * @param {"mongodb"|"sqlite"} dbType * @param {Database | MongoClient} connection - * @link `Documentation:` https://simplyxp.js.org/docs/migrate/database + * @link `Documentation:` https://simplyxp.js.org/docs/next/classes/migrate#migratefromdb * @returns {Promise} - Returns true if migration is successful * @throws {XpFatal} - If parameters are not provided correctly */ @@ -65,10 +65,9 @@ export class migrate { switch (dbType) { case "mongodb": try { - if (!await checkPackageVersion("mongodb")) return XpLog.err("migrate.fromDB()", "MongoDB V4 or higher is required"); + if (!await checkPackageVersion("mongodb", 4, 6)) return XpLog.err("migrate.fromDB()", "MongoDB V4 up to V6 is required"); - results = (connection as MongoClient).db().collection("simply-xps").find().toArray() as Document as UserResult[]; - XpLog.debug("migrate.fromDB()", `FOUND ${results.length} DOCUMENTS`); + results = await (connection as MongoClient).db().collection("simply-xps").find().toArray() as Document as UserResult[]; } catch (error) { XpLog.err("migrate.fromDB()", error as string); @@ -78,10 +77,8 @@ export class migrate { case "sqlite": try { - if (!await checkPackageVersion("sqlite")) return XpLog.err("migrate.fromDB()", "better-sqlite3 V7 or higher is required"); - + if (!await checkPackageVersion("better-sqlite3", 7, 8)) return XpLog.err("migrate.fromDB()", "better-sqlite3 V7 up to V8 is required"); results = (connection as Database).prepare("SELECT * FROM `simply-xps`").all() as UserResult[]; - XpLog.debug("migrate.fromDB()", `FOUND ${results.length} ROWS`); } catch (error) { XpLog.err("migrate.fromDB()", error as string); @@ -90,6 +87,8 @@ export class migrate { break; } + XpLog.debug("migrate.fromDB()", `FOUND ${results.length} RESULTS`); + await Promise.all(results.map(async (user) => { if (!await db.findOne({collection: "simply-xps", data: {guild: user.guild, user: user.user}})) { return db.createOne({ diff --git a/src/reset.ts b/src/reset.ts index 90a8f1d..4549a98 100644 --- a/src/reset.ts +++ b/src/reset.ts @@ -9,7 +9,7 @@ import {db} from "./functions/database"; * @param {string} guildId * @param {boolean?} erase - Erase user entry from the database * @param {string?} username - Username to use if auto_create is enabled - * @link `Documentation:` https://simplyxp.js.org/docs/reset + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/reset * @returns {Promise} * @throws {XpFatal} If an invalid type is provided or if the value is not provided. */ diff --git a/src/roleSetup.ts b/src/roleSetup.ts index 326e0a9..d8b1bd2 100644 --- a/src/roleSetup.ts +++ b/src/roleSetup.ts @@ -8,7 +8,6 @@ import {XpFatal} from "./functions/xplogs"; * @property {string[] | string} roles - The role(s) to add */ export interface RoleSetupObject { - guild?: string; level: number; roles: string[] | string; } @@ -23,7 +22,7 @@ export class roleSetup { * @async * @param {string} guildId - The guild ID * @param {RoleSetupObject} options - Level/role options - * @link `Documentation:` https://simplyxp.js.org/docs/roleSetup/add + * @link `Documentation:` https://simplyxp.js.org/docs/next/classes/roleSetup#roleSetupadd * @returns {Promise} - True if successful * @throws {XpFatal} If an invalid type is provided or value is not provided. */ @@ -54,7 +53,7 @@ export class roleSetup { * @async * @param {string} guildId - The guild ID * @param {number} levelNumber - The level number - * @link `Documentation:` https://simplyxp.js.org/docs/roleSetup/find + * @link `Documentation:` https://simplyxp.js.org/docs/next/classes/roleSetup#roleSetupfind * @returns {Promise} - The level role object * @throws {XpFatal} If an invalid type is provided or value is not provided. */ @@ -75,7 +74,7 @@ export class roleSetup { * @async * @param {string} guildId - The guild ID * @param {number} levelNumber - The level number - * @link `Documentation:` https://simplyxp.js.org/docs/roleSetup/remove + * @link `Documentation:` https://simplyxp.js.org/docs/next/classes/roleSetup#roleSetupremove * @returns {Promise} - True if successful * @throws {XpFatal} If an invalid type is provided or value is not provided. */ diff --git a/src/set.ts b/src/set.ts index 7e5cef0..4d9c30d 100644 --- a/src/set.ts +++ b/src/set.ts @@ -10,7 +10,7 @@ import {xp} from "../xp"; * @param {string} guildId * @param {number} level * @param {string} username - Username to use if auto_create is enabled - * @link `Documentation:` https://simplyxp.js.org/docs/setlevel + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/setlevel * @returns {Promise} - Object of user data on success * @throws {XpFatal} - If parameters are not provided correctly */ @@ -59,7 +59,7 @@ interface XPResult extends UserResult { * @param {string} guildId * @param {number} xpData * @param {string} username - Username to use if auto_create is enabled - * @link `Documentation:` https://simplyxp.js.org/docs/setxp + * @link `Documentation:` https://simplyxp.js.org/docs/next/functions/setxp * @returns {Promise} - Object of user data on success * @throws {XpFatal} - If parameters are not provided correctly */ diff --git a/tsconfig.json b/tsconfig.json index e7601d4..1648ea7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,7 @@ "lib" ], "include": [ - "./src/fonts/BalooBhaijaan-Regular.otf", + "./src/fonts/BalooBhaijaan-Regular.woff2", "./src/**/*", "xp.ts" ], diff --git a/xp.ts b/xp.ts index 4d75e3e..107b74e 100644 --- a/xp.ts +++ b/xp.ts @@ -22,6 +22,8 @@ export {db} from "./src/functions/database"; export {charts} from "./src/charts"; +export {compareCard, leaderboardCard, rankCard} from "./src/cards"; + export {connect} from "./src/connect"; export {convertFrom, updateOptions} from "./src/functions/utilities"; @@ -34,8 +36,6 @@ export {leaderboard} from "./src/leaderboard"; export {migrate} from "./src/migrate"; -export {rankCard, leaderboardCard} from "./src/cards"; - export {reset} from "./src/reset"; export {roleSetup} from "./src/roleSetup";