diff --git a/.gitignore b/.gitignore index d0a6dd3..509ca1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -/node_modules -.DS_Store -/package-lock.json +/node_modules +.DS_Store +/package-lock.json +index.html +index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 34923b8..5858233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,30 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -- Nothing yet :) + +- Added putRequest method ## 2.1.0 - 2021-3-23 + ### Added + - Added postRequest method (by [@Aras14HD](https://github.com/delightedCrow/HabiticaMagic/pull/4)) ## 2.0.1 - 2019-11-15 + ### Changed + - Updating HabiticaUser.class to return "mage" instead of "wizard" for users with a mage class. ## 2.0.0 - 2019-11-2 + ### Added + - So. Much. Documentation. - This CHANGELOG! - The Demo. @@ -24,8 +32,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Just... everything good. ### Changed + - Pretty much everything about HabiticaAPIManager. Nothing is compatible with the last version. sorry :( ## 1.0.0 - 2019-10-31 + ### Added + - EVERYTHING (this was the initial HabiticaMagicJS release) diff --git a/LICENSE b/LICENSE index 43a81bc..f28afd4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2019 Jess Coulter - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2019 Jess Coulter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 97bf9b9..e94d7f7 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,12 @@ For the most comprehensive and up-to-date documentation on HabiticaMagicJS funct ### HabiticaAPIManager -````javascript +```javascript var apiManager = new HabiticaAPIManager("Your x-client ID", "en@pirate"); -```` +``` The `HabiticaAPIManager` class is the primary way you'll interact with HabiticaMagicJS. Its constructor takes two parameters: + - **x-client-id**: REQUIRED. Your x-client-id. [See Habitica's documentation here for what your x-client should be](https://habitica.fandom.com/wiki/Guidance_for_Comrades#X-Client_Header). - **language**: OPTIONAL. The language you want Habitica's content in. [See possible values here](https://habitica.com/apidoc/#api-Content-ContentGet). The default is `"en"` for english. @@ -37,24 +38,25 @@ Check out this example of making multiple API requests and chaining them togethe let manager = new HabiticaAPIManager(xclient); // first we're going to do an API call to fetch the Habitica content data -manager.fetchContentData() -// once our content data fetch succeeds we can add the fetchUser() -// API call to the promise chain. -.then(() => { - return manager.fetchUser(userID); -}) -// Now that our fetchUser call has finished we're done with API calls -// (for now!) and we can send the resulting HabiticaUser object to our -// renderUserTemplate function to display. -.then((user) => { - this.renderUserTemplate(user); -}) -// The nice thing about promise chains like this is that if an error -// happens AT ANY POINT in the chain here we'll automatically skip -// to this catch block, which will render our error template -.catch((error) => { - this.renderErrorTemplate(error); -}); +manager + .fetchContentData() + // once our content data fetch succeeds we can add the fetchUser() + // API call to the promise chain. + .then(() => { + return manager.fetchUser(userID); + }) + // Now that our fetchUser call has finished we're done with API calls + // (for now!) and we can send the resulting HabiticaUser object to our + // renderUserTemplate function to display. + .then((user) => { + this.renderUserTemplate(user); + }) + // The nice thing about promise chains like this is that if an error + // happens AT ANY POINT in the chain here we'll automatically skip + // to this catch block, which will render our error template + .catch((error) => { + this.renderErrorTemplate(error); + }); ``` #### Generic Get Requests @@ -70,12 +72,11 @@ HabiticaMagicJS provides several fetch functions for getting specific API data ( var apiManager = new HabiticaAPIManager("Your x-client ID"); let apiURL = "https://habitica.com/api/v3/status"; -apiManager.getRequest(apiURL) -.then((rawData) => { - // process the raw string of data into a JSON object - let statusData = JSON.parse(rawData).data; - // Now you know the API status! - console.log(statusData); +apiManager.getRequest(apiURL).then((rawData) => { + // process the raw string of data into a JSON object + let statusData = JSON.parse(rawData).data; + // Now you know the API status! + console.log(statusData); }); ``` @@ -85,13 +86,14 @@ apiManager.getRequest(apiURL) var apiManager = new HabiticaAPIManager("Your x-client ID"); let apiURL = "https://habitica.com/api/v3/groups/party"; -apiManager.authGetRequest(apiURL, "USER ID", "USER API TOKEN") -.then((rawData) => { - // process the raw string of data into a JSON object - let partyData = JSON.parse(rawData).data; - // now you can party! - console.log(partyData); -}); +apiManager + .authGetRequest(apiURL, "USER ID", "USER API TOKEN") + .then((rawData) => { + // process the raw string of data into a JSON object + let partyData = JSON.parse(rawData).data; + // now you can party! + console.log(partyData); + }); ``` #### Generic Post Request @@ -105,10 +107,10 @@ var apiManager = new HabiticaAPIManager("Your x-client ID"); let apiURL = "https://habitica.com/api/v3/tasks/user"; let task = { - type: "TASK TYPE", - text: "TASK NAME", -} -apiManager.postRequest(apiURL, "USER ID", "USER API TOKEN", task) + type: "TASK TYPE", + text: "TASK NAME", +}; +apiManager.postRequest(apiURL, "USER ID", "USER API TOKEN", task); ``` #### Before Fetching HabiticaUsers: Getting the Habitica Content @@ -135,7 +137,9 @@ apiManager.fetchUser("USER-ID") shield: "shield_base_0" } ``` + #### Example: fetchUser() After Content Fetch: details filled in! + ```javascript // fetching the content first apiManager.fetchContentData() @@ -178,23 +182,23 @@ These classes store the API returned by Habitica in a property (`.apiData`) and - There is often a lot of complexity in the way the Habitica API returns data, which `HabiticaUser` smoothes over - just look at the following example for getting a user's gems. ##### Example: getting a user's gems + ```javascript // Assuming you've already created an apiManager and fetched // Habitica the content data -apiManager.fetchUserWithTasks("USER ID", "API TOKEN") -.then((user) => { - // you can always get to the raw API object data from - // the .apiData property - console.log(user.apiData); - - // If you were to use the old-fashioned way of finding out how - // many gems a user had you'd have to know the Habitica API - // returns gems in a data property called "balance", and that - // you had to multiply that value by 4. - console.log(user.apiData.balance * 4); // gives you # of gems... - - // But really you should just call: - console.log(user.gems); // what it does is obvious! +apiManager.fetchUserWithTasks("USER ID", "API TOKEN").then((user) => { + // you can always get to the raw API object data from + // the .apiData property + console.log(user.apiData); + + // If you were to use the old-fashioned way of finding out how + // many gems a user had you'd have to know the Habitica API + // returns gems in a data property called "balance", and that + // you had to multiply that value by 4. + console.log(user.apiData.balance * 4); // gives you # of gems... + + // But really you should just call: + console.log(user.gems); // what it does is obvious! }); ``` @@ -204,29 +208,28 @@ apiManager.fetchUserWithTasks("USER ID", "API TOKEN") // Assuming you've already created an apiManager and fetched // Habitica the content data -apiManager.fetchUserWithTasks("USER ID", "API TOKEN") -.then((user) => { - // you can always get to the raw API object data from - // the .apiData property - console.log(user.apiData); - - // user.tasks is actually a HabiticaUserTasksManager object that - // lets you do neat things like see the amount of damage your - // undone dailies will to! - console.log(user.tasks.dailyStats.dailyDamageToSelf); - - // want a list of this user's todos, due today? - console.log(user.tasks.todosDueToday); - - // user stats will be computed for you! Yay! - console.log(user.stats) - // output of console.log(user.stats): - // { - // totals: {str: 0, con: 0, int: 0, per: 0}, - // armor: {str: 0, con: 0, int: 0, per: 0}, - // buffs: {str: 0, con: 0, int: 0, per: 0}, - // points: {str: 0, con: 0, int: 0, per: 0} - // } +apiManager.fetchUserWithTasks("USER ID", "API TOKEN").then((user) => { + // you can always get to the raw API object data from + // the .apiData property + console.log(user.apiData); + + // user.tasks is actually a HabiticaUserTasksManager object that + // lets you do neat things like see the amount of damage your + // undone dailies will to! + console.log(user.tasks.dailyStats.dailyDamageToSelf); + + // want a list of this user's todos, due today? + console.log(user.tasks.todosDueToday); + + // user stats will be computed for you! Yay! + console.log(user.stats); + // output of console.log(user.stats): + // { + // totals: {str: 0, con: 0, int: 0, per: 0}, + // armor: {str: 0, con: 0, int: 0, per: 0}, + // buffs: {str: 0, con: 0, int: 0, per: 0}, + // points: {str: 0, con: 0, int: 0, per: 0} + // } }); ``` @@ -237,6 +240,7 @@ apiManager.fetchUserWithTasks("USER ID", "API TOKEN") ### Build Scripts To run all the build scripts: + ```bash npm run build ``` @@ -252,17 +256,21 @@ npm run build:minify Compiled files will be output in the `/dist` directory. To modify the build process, edit `minify.js`. ### Generating the Developer Docs + The Developer documentation is generated from [JSDoc](https://jsdoc.app/index.html) formatted comments in the code, and created using [documentation.js](https://documentation.js.org/). To rebuild the documentation file after updating comments: First, install dependencies locally. + ```bash npm install ``` Then run the following command to build the html docs: + ```bash npm run build:docs ``` + --- ## Contributing diff --git a/dist/HabiticaMagic-v2.1.0.js b/dist/HabiticaMagic-v2.1.0.js index 6fbb02c..0358c0a 100644 --- a/dist/HabiticaMagic-v2.1.0.js +++ b/dist/HabiticaMagic-v2.1.0.js @@ -1,416 +1,416 @@ -/* - * HabiticaMagic.js - * https://github.com/delightedCrow/HabiticaMagic - * - * A convenient way to interact with the Habitica API - * (https://habitica.com/apidoc/). - * - * Copyright © 2019 JSC (@delightedCrow) & PJM (@ArrayOfFrost) - * - * MIT Licensed - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - -/** - * A class representing the list of tasks for a HabiticaUser. - * @param {Array} data - the list of tasks returned from the api call - */ -class HabiticaUserTasksManager { - constructor(data) { - /** - * The list of tasks. - * @type {Array} - */ - this.apiData = data; - } - - /** - * The list of tasks. - * @type {Array} - */ - get taskList() { - return this.apiData; - } - - /** - * The list of Todo tasks due by the end of today. - * @type {Array} - */ - get todosDueToday() { - return this.todosDueOnDate(moment().endOf('day')); - } - - /** - * The list of Todo tasks due by the end of a specific date. - * @param {Date} dueDate the date to compare tasks due dates to. - * @returns {Array} - */ - todosDueOnDate(dueDate) { - var todos = []; - - for (var i=0; i 0) { // avoided! - stealthRemaining --; - stats.dailiesEvaded ++; - } else { // calculate damage! - - stats.dueCount ++; - - var taskDamage = (task.value < min) ? min : task.value; - taskDamage = (taskDamage > max) ? max : taskDamage; - taskDamage = Math.abs(Math.pow(0.9747, taskDamage)); - - // if a subtask is completed, decrease the task damage proportionately - if (task.checklist.length > 0 ) { - var subtaskDamage = (taskDamage/task.checklist.length); - for (var j=0; j} data - the list of tasks returned from the api call + */ +class HabiticaUserTasksManager { + constructor(data) { + /** + * The list of tasks. + * @type {Array} + */ + this.apiData = data; + } + + /** + * The list of tasks. + * @type {Array} + */ + get taskList() { + return this.apiData; + } + + /** + * The list of Todo tasks due by the end of today. + * @type {Array} + */ + get todosDueToday() { + return this.todosDueOnDate(moment().endOf('day')); + } + + /** + * The list of Todo tasks due by the end of a specific date. + * @param {Date} dueDate the date to compare tasks due dates to. + * @returns {Array} + */ + todosDueOnDate(dueDate) { + var todos = []; + + for (var i=0; i 0) { // avoided! + stealthRemaining --; + stats.dailiesEvaded ++; + } else { // calculate damage! + + stats.dueCount ++; + + var taskDamage = (task.value < min) ? min : task.value; + taskDamage = (taskDamage > max) ? max : taskDamage; + taskDamage = Math.abs(Math.pow(0.9747, taskDamage)); + + // if a subtask is completed, decrease the task damage proportionately + if (task.checklist.length > 0 ) { + var subtaskDamage = (taskDamage/task.checklist.length); + for (var j=0; j { - var data = JSON.parse(rawData).data; - this.content = data; - }); - } - - /** - * Fetches a HabiticaUser instance from the api, containing publicly visible user data. - * @param {HabiticaUserID} userID - The ID of the habitica user. - * @returns {Promise} Promise provides a HabiticaUser instance. - */ - fetchUser(userID) { - const baseURL = "https://habitica.com/api/v3/members/" + userID; - return this.getRequest(baseURL) - .then((rawData) => { - var data = JSON.parse(rawData).data; - let user = new HabiticaUser(this.replaceKeysWithContent(data)); - return user; - }); - } - - // AUTHENTICATED HELPER FUNCTIONS - - /** - * Fetches a HabiticaUser instance, including personal information. - * @param {HabiticaUserID} userID - * @param {HabiticaAPIToken} userAPIToken - * @returns {Promise} Promise provides a HabiticaUser instance. - */ - fetchAuthenticatedUser(userID, userAPIToken) { - const url = "https://habitica.com/api/v3/user"; - return this.authGetRequest(url, userID, userAPIToken) - .then((rawData) => { - var data = JSON.parse(rawData).data; - let user = new HabiticaUser(this.replaceKeysWithContent(data)); - return user; - }); - } - - /** - * Fetches the list of tasks for a given user. - * @param {HabiticaUserID} userID - * @param {HabiticaAPIToken} userAPIToken - * @returns {Promise} - */ - fetchUserTasks(userID, userAPIToken) { - const url = "https://habitica.com/api/v3/tasks/user"; - return this.authGetRequest(url, userID, userAPIToken) - .then((rawData) => { - var data = JSON.parse(rawData).data; - let tasks = new HabiticaUserTasksManager(data); - return tasks; - }); - } - - /** - * Fetches a user and their list of tasks. - * @param {HabiticaUserID} userID - * @param {HabiticaAPIToken} userAPIToken - * @returns {Promise} Promise provides a HabiticaUser instance, with a populated tasks manager. - */ - fetchUserWithTasks(userID, userAPIToken) { - var user; - return this.fetchAuthenticatedUser(userID, userAPIToken) - .then((newUser) => { - user = newUser; - return this.fetchUserTasks(userID, userAPIToken); - }) - .then((tasks) => { - user.tasks = tasks; - return user; - }); - } - - // API REQUEST FUNCTIONS - - /** - * Make an authenticated GET request to the Habitica API. Data object returned varies based on the API url called. - * @param {string} baseURL - the url of the api call. - * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. - * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. - * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. - * @returns {Promise} Promise containing the raw API response data as a string. - */ - authGetRequest(baseURL, userID, userAPIToken, queryParams={}) { - let url = this.getQueryStringURL(baseURL, queryParams); - - let promise = new Promise((resolve, reject) => { - let req = new XMLHttpRequest(); - req.open("GET", url); - - req.onerror = function() { - reject(this.statusText); - }; - - req.onload = function() { - if (this.status == 200) { - resolve(this.responseText); - } else { - reject(this.responseText); - } - } - - req.setRequestHeader("x-api-user", userID); - req.setRequestHeader("x-api-key", userAPIToken); - req.setRequestHeader("x-client", this.xclient); - req.send(); - }); - - return promise; - } - - /** - * Make a GET request to the Habitica API. - * Data object returned varies based on the API url called. - * For accessing personal data endpoints, use {@link HabiticaAPIManager#authGetRequest|authGetRequest} - * @param {string} baseURL - the url of the api call. - * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. - * @returns {Promise} Promise containing the raw API response data as a string. - */ - getRequest(baseURL, queryParams={}) { - let url = this.getQueryStringURL(baseURL, queryParams); - - let promise = new Promise((resolve, reject) => { - let req = new XMLHttpRequest(); - req.open("GET", url); - - req.onerror = function() { - reject(this.statusText); - }; - - req.onload = function() { - if (this.status == 200) { - resolve(this.responseText); - } else { - reject(this.responseText); - } - } - req.setRequestHeader("x-client", this.xclient); - req.send(); - }); - - return promise; - } + constructor(xclient, language = "en") { + /** + * The language code to retrieve content for. See https://habitica.com/apidoc/#api-Content-ContentGet for list of allowed values. + * @type {string} + */ + this.language = language; + /** + * The xclient header string for this application. See https://habitica.fandom.com/wiki/Guidance_for_Comrades#X-Client_Header for details. + * @type {string} + */ + this.xclient = xclient; + /** + * The data cache for looking up Habitica content such as items, quests, or appearance information. + * @type {object} + */ + this.content = {}; + } + + // UNAUTHENTICATED HELPER FUNCTIONS + + /** + * Load Habitica content from the api. Populates the {@link HabiticaAPIManager#content|content} attribute. + * @returns {Promise} + */ + fetchContentData() { + const baseURL = "https://habitica.com/api/v3/content"; + return this.getRequest(baseURL, { language: this.language }).then( + (rawData) => { + var data = JSON.parse(rawData).data; + this.content = data; + } + ); + } + + /** + * Fetches a HabiticaUser instance from the api, containing publicly visible user data. + * @param {HabiticaUserID} userID - The ID of the habitica user. + * @returns {Promise} Promise provides a HabiticaUser instance. + */ + fetchUser(userID) { + const baseURL = "https://habitica.com/api/v3/members/" + userID; + return this.getRequest(baseURL).then((rawData) => { + var data = JSON.parse(rawData).data; + let user = new HabiticaUser(this.replaceKeysWithContent(data)); + return user; + }); + } + + // AUTHENTICATED HELPER FUNCTIONS + + /** + * Fetches a HabiticaUser instance, including personal information. + * @param {HabiticaUserID} userID + * @param {HabiticaAPIToken} userAPIToken + * @returns {Promise} Promise provides a HabiticaUser instance. + */ + fetchAuthenticatedUser(userID, userAPIToken) { + const url = "https://habitica.com/api/v3/user"; + return this.authGetRequest(url, userID, userAPIToken).then((rawData) => { + var data = JSON.parse(rawData).data; + let user = new HabiticaUser(this.replaceKeysWithContent(data)); + return user; + }); + } + + /** + * Fetches the list of tasks for a given user. + * @param {HabiticaUserID} userID + * @param {HabiticaAPIToken} userAPIToken + * @returns {Promise} + */ + fetchUserTasks(userID, userAPIToken) { + const url = "https://habitica.com/api/v3/tasks/user"; + return this.authGetRequest(url, userID, userAPIToken).then((rawData) => { + var data = JSON.parse(rawData).data; + let tasks = new HabiticaUserTasksManager(data); + return tasks; + }); + } + + /** + * Fetches a user and their list of tasks. + * @param {HabiticaUserID} userID + * @param {HabiticaAPIToken} userAPIToken + * @returns {Promise} Promise provides a HabiticaUser instance, with a populated tasks manager. + */ + fetchUserWithTasks(userID, userAPIToken) { + var user; + return this.fetchAuthenticatedUser(userID, userAPIToken) + .then((newUser) => { + user = newUser; + return this.fetchUserTasks(userID, userAPIToken); + }) + .then((tasks) => { + user.tasks = tasks; + return user; + }); + } + + // API REQUEST FUNCTIONS + + /** + * Make an authenticated GET request to the Habitica API. Data object returned varies based on the API url called. + * @param {string} baseURL - the url of the api call. + * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. + * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. + * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. + * @returns {Promise} Promise containing the raw API response data as a string. + */ + authGetRequest(baseURL, userID, userAPIToken, queryParams = {}) { + let url = this.getQueryStringURL(baseURL, queryParams); + + let promise = new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("GET", url); + + req.onerror = function () { + reject(this.statusText); + }; + + req.onload = function () { + if (this.status == 200) { + resolve(this.responseText); + } else { + reject(this.responseText); + } + }; + + req.setRequestHeader("x-api-user", userID); + req.setRequestHeader("x-api-key", userAPIToken); + req.setRequestHeader("x-client", this.xclient); + req.send(); + }); + + return promise; + } + + /** + * Make a GET request to the Habitica API. + * Data object returned varies based on the API url called. + * For accessing personal data endpoints, use {@link HabiticaAPIManager#authGetRequest|authGetRequest} + * @param {string} baseURL - the url of the api call. + * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. + * @returns {Promise} Promise containing the raw API response data as a string. + */ + getRequest(baseURL, queryParams = {}) { + let url = this.getQueryStringURL(baseURL, queryParams); + + let promise = new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("GET", url); + + req.onerror = function () { + reject(this.statusText); + }; + + req.onload = function () { + if (this.status == 200) { + resolve(this.responseText); + } else { + reject(this.responseText); + } + }; + req.setRequestHeader("x-client", this.xclient); + req.send(); + }); + + return promise; + } /** - * Make a POST request to the Habitica API. - * Data object returned varies based on the API url called. - * @param {string} baseURL - the url of the api call. - * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. - * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. - * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. - * @returns {Promise} Promise containing the raw API response data as a string. - */ - postRequest(baseURL, userID, userAPIToken, queryParams={}) { - let promise = new Promise((resolve, reject) => { - let req = new XMLHttpRequest(); - req.open("POST", baseURL); - - req.onerror = function() { - reject(this.responseText); - }; - - req.onload = function() { - if (req.status === 201 || req.status === 200) { - resolve(this.responseText); - } else { - reject(this.responseText); - } - } + * Make a POST request to the Habitica API. + * Data object returned varies based on the API url called. + * @param {string} baseURL - the url of the api call. + * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. + * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. + * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. + * @returns {Promise} Promise containing the raw API response data as a string. + */ + postRequest(baseURL, userID, userAPIToken, queryParams = {}) { + let promise = new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("POST", baseURL); + + req.onerror = function () { + reject(this.responseText); + }; + + req.onload = function () { + if (req.status === 201 || req.status === 200) { + resolve(this.responseText); + } else { + reject(this.responseText); + } + }; req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - req.setRequestHeader("x-client", this.xclient); - req.setRequestHeader("x-api-user", userID); - req.setRequestHeader("x-api-key", userAPIToken); - req.send(JSON.stringify(queryParams)); - }); - return promise - } - - // CLASS HELPER FUNCTIONS - - /** - * Updates the data object keys with values from the the {@link HabiticaAPIManager#content|content}. - * @param {object} data - See {@link HabiticaUser#apiData|HabiticaUser.apiData} - * @returns {object} The same data object passed in, after it is updated. - */ - replaceKeysWithContent(data) { - // if we haven't fetched any content data, nothing we can do here - if (Object.entries(this.content).length == 0) { - return data; - } - - // replace equipped and costume gear with full content version - for (var section of [data.items.gear.equipped, data.items.gear.costume]) { - for (var key in section) { - let armorName = section[key]; - let armor = this.content.gear.flat[armorName]; - section[key] = armor; - } - } - // replace party quest key with actual quest - if (data.party.quest.key) { - data.party.quest.data = this.content.quests[data.party.quest.key]; - } - return data; - } - - /** - * Convert a base url and query parameters into a full url with querystring. - * @param {string} baseURL the URI of the API endpoint. - * @param {object} queryParams key-value pairs in an object to be parameterized. - * @returns {string} the full url with querystring. - */ - getQueryStringURL(baseURL, queryParams) { - let params = Object.entries(queryParams); - if (params.length < 1) { - return baseURL; - } - - return baseURL + "?" + - params.map(kv => kv.map(encodeURIComponent).join("=")) - .join("&"); - } -} + req.setRequestHeader("x-client", this.xclient); + req.setRequestHeader("x-api-user", userID); + req.setRequestHeader("x-api-key", userAPIToken); + req.send(JSON.stringify(queryParams)); + }); + return promise; + } + + /** + * Make a PUT request to the Habitica API. + * Data object returned varies based on the API url called. + * @param {string} baseURL - the url of the api call. + * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. + * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. + * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. + * @returns {Promise} Promise containing the raw API response data as a string. + */ + putRequest(baseURL, userID, userAPIToken, queryParams = {}) { + let promise = new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("PUT", baseURL); + + req.onerror = function () { + reject(this.responseText); + }; + + req.onload = function () { + if (req.status === 201 || req.status === 200) { + resolve(this.responseText); + } else { + reject(this.responseText); + } + }; + req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + req.setRequestHeader("x-client", this.xclient); + req.setRequestHeader("x-api-user", userID); + req.setRequestHeader("x-api-key", userAPIToken); + req.send(JSON.stringify(queryParams)); + }); + return promise; + } + + // CLASS HELPER FUNCTIONS + /** + * Updates the data object keys with values from the the {@link HabiticaAPIManager#content|content}. + * @param {object} data - See {@link HabiticaUser#apiData|HabiticaUser.apiData} + * @returns {object} The same data object passed in, after it is updated. + */ + replaceKeysWithContent(data) { + // if we haven't fetched any content data, nothing we can do here + if (Object.entries(this.content).length == 0) { + return data; + } + + // replace equipped and costume gear with full content version + for (var section of [data.items.gear.equipped, data.items.gear.costume]) { + for (var key in section) { + let armorName = section[key]; + let armor = this.content.gear.flat[armorName]; + section[key] = armor; + } + } + // replace party quest key with actual quest + if (data.party.quest.key) { + data.party.quest.data = this.content.quests[data.party.quest.key]; + } + return data; + } + + /** + * Convert a base url and query parameters into a full url with querystring. + * @param {string} baseURL the URI of the API endpoint. + * @param {object} queryParams key-value pairs in an object to be parameterized. + * @returns {string} the full url with querystring. + */ + getQueryStringURL(baseURL, queryParams) { + let params = Object.entries(queryParams); + if (params.length < 1) { + return baseURL; + } + + return ( + baseURL + + "?" + + params.map((kv) => kv.map(encodeURIComponent).join("=")).join("&") + ); + } +} /** * The User's ID. See https://habitica.fandom.com/wiki/API_Options#User_ID_.28UID.29 diff --git a/dist/HabiticaMagic-v2.1.0.min.js b/dist/HabiticaMagic-v2.1.0.min.js index 593ab92..e55673d 100644 --- a/dist/HabiticaMagic-v2.1.0.min.js +++ b/dist/HabiticaMagic-v2.1.0.min.js @@ -28,4 +28,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -class HabiticaUserTasksManager{constructor(t){this.apiData=t}get taskList(){return this.apiData}get todosDueToday(){return this.todosDueOnDate(moment().endOf("day"))}todosDueOnDate(t){for(var e=[],a=0;a0)a--,e.dailiesEvaded++;else{e.dueCount++;var r=l.value<-47.27?-47.27:l.value;if(r=r>21.27?21.27:r,r=Math.abs(Math.pow(.9747,r)),l.checklist.length>0)for(var n=r/l.checklist.length,o=0;o{var e=JSON.parse(t).data;this.content=e})}fetchUser(t){const e="https://habitica.com/api/v3/members/"+t;return this.getRequest(e).then(t=>{var e=JSON.parse(t).data;return new HabiticaUser(this.replaceKeysWithContent(e))})}fetchAuthenticatedUser(t,e){return this.authGetRequest("https://habitica.com/api/v3/user",t,e).then(t=>{var e=JSON.parse(t).data;return new HabiticaUser(this.replaceKeysWithContent(e))})}fetchUserTasks(t,e){return this.authGetRequest("https://habitica.com/api/v3/tasks/user",t,e).then(t=>{var e=JSON.parse(t).data;return new HabiticaUserTasksManager(e)})}fetchUserWithTasks(t,e){var a;return this.fetchAuthenticatedUser(t,e).then(s=>(a=s,this.fetchUserTasks(t,e))).then(t=>(a.tasks=t,a))}authGetRequest(t,e,a,s={}){let i=this.getQueryStringURL(t,s);return new Promise((t,s)=>{let r=new XMLHttpRequest;r.open("GET",i),r.onerror=function(){s(this.statusText)},r.onload=function(){200==this.status?t(this.responseText):s(this.responseText)},r.setRequestHeader("x-api-user",e),r.setRequestHeader("x-api-key",a),r.setRequestHeader("x-client",this.xclient),r.send()})}getRequest(t,e={}){let a=this.getQueryStringURL(t,e);return new Promise((t,e)=>{let s=new XMLHttpRequest;s.open("GET",a),s.onerror=function(){e(this.statusText)},s.onload=function(){200==this.status?t(this.responseText):e(this.responseText)},s.setRequestHeader("x-client",this.xclient),s.send()})}postRequest(t,e,a,s={}){return new Promise((i,r)=>{let n=new XMLHttpRequest;n.open("POST",t),n.onerror=function(){r(this.responseText)},n.onload=function(){201===n.status||200===n.status?i(this.responseText):r(this.responseText)},n.setRequestHeader("Content-Type","application/json;charset=UTF-8"),n.setRequestHeader("x-client",this.xclient),n.setRequestHeader("x-api-user",e),n.setRequestHeader("x-api-key",a),n.send(JSON.stringify(s))})}replaceKeysWithContent(t){if(0==Object.entries(this.content).length)return t;for(var e of[t.items.gear.equipped,t.items.gear.costume])for(var a in e){let t=e[a],s=this.content.gear.flat[t];e[a]=s}return t.party.quest.key&&(t.party.quest.data=this.content.quests[t.party.quest.key]),t}getQueryStringURL(t,e){let a=Object.entries(e);return a.length<1?t:t+"?"+a.map(t=>t.map(encodeURIComponent).join("=")).join("&")}} \ No newline at end of file +class HabiticaUserTasksManager{constructor(t){this.apiData=t}get taskList(){return this.apiData}get todosDueToday(){return this.todosDueOnDate(moment().endOf("day"))}todosDueOnDate(t){for(var e=[],s=0;s0)s--,e.dailiesEvaded++;else{e.dueCount++;var i=l.value<-47.27?-47.27:l.value;if(i=i>21.27?21.27:i,i=Math.abs(Math.pow(.9747,i)),l.checklist.length>0)for(var n=i/l.checklist.length,o=0;o{var e=JSON.parse(t).data;this.content=e})}fetchUser(t){const e="https://habitica.com/api/v3/members/"+t;return this.getRequest(e).then(t=>{var e=JSON.parse(t).data;return new HabiticaUser(this.replaceKeysWithContent(e))})}fetchAuthenticatedUser(t,e){return this.authGetRequest("https://habitica.com/api/v3/user",t,e).then(t=>{var e=JSON.parse(t).data;return new HabiticaUser(this.replaceKeysWithContent(e))})}fetchUserTasks(t,e){return this.authGetRequest("https://habitica.com/api/v3/tasks/user",t,e).then(t=>{var e=JSON.parse(t).data;return new HabiticaUserTasksManager(e)})}fetchUserWithTasks(t,e){var s;return this.fetchAuthenticatedUser(t,e).then(a=>(s=a,this.fetchUserTasks(t,e))).then(t=>(s.tasks=t,s))}authGetRequest(t,e,s,a={}){let r=this.getQueryStringURL(t,a);return new Promise((t,a)=>{let i=new XMLHttpRequest;i.open("GET",r),i.onerror=function(){a(this.statusText)},i.onload=function(){200==this.status?t(this.responseText):a(this.responseText)},i.setRequestHeader("x-api-user",e),i.setRequestHeader("x-api-key",s),i.setRequestHeader("x-client",this.xclient),i.send()})}getRequest(t,e={}){let s=this.getQueryStringURL(t,e);return new Promise((t,e)=>{let a=new XMLHttpRequest;a.open("GET",s),a.onerror=function(){e(this.statusText)},a.onload=function(){200==this.status?t(this.responseText):e(this.responseText)},a.setRequestHeader("x-client",this.xclient),a.send()})}postRequest(t,e,s,a={}){return new Promise((r,i)=>{let n=new XMLHttpRequest;n.open("POST",t),n.onerror=function(){i(this.responseText)},n.onload=function(){201===n.status||200===n.status?r(this.responseText):i(this.responseText)},n.setRequestHeader("Content-Type","application/json;charset=UTF-8"),n.setRequestHeader("x-client",this.xclient),n.setRequestHeader("x-api-user",e),n.setRequestHeader("x-api-key",s),n.send(JSON.stringify(a))})}putRequest(t,e,s,a={}){return new Promise((r,i)=>{let n=new XMLHttpRequest;n.open("PUT",t),n.onerror=function(){i(this.responseText)},n.onload=function(){201===n.status||200===n.status?r(this.responseText):i(this.responseText)},n.setRequestHeader("Content-Type","application/json;charset=UTF-8"),n.setRequestHeader("x-client",this.xclient),n.setRequestHeader("x-api-user",e),n.setRequestHeader("x-api-key",s),n.send(JSON.stringify(a))})}replaceKeysWithContent(t){if(0==Object.entries(this.content).length)return t;for(var e of[t.items.gear.equipped,t.items.gear.costume])for(var s in e){let t=e[s],a=this.content.gear.flat[t];e[s]=a}return t.party.quest.key&&(t.party.quest.data=this.content.quests[t.party.quest.key]),t}getQueryStringURL(t,e){let s=Object.entries(e);return s.length<1?t:t+"?"+s.map(t=>t.map(encodeURIComponent).join("=")).join("&")}} \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index c741881..0000000 --- a/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-slate \ No newline at end of file diff --git a/docs/assets/fonts/EOT/SourceCodePro-Bold.eot b/docs/assets/fonts/EOT/SourceCodePro-Bold.eot old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/EOT/SourceCodePro-Regular.eot b/docs/assets/fonts/EOT/SourceCodePro-Regular.eot old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/LICENSE.txt b/docs/assets/fonts/LICENSE.txt old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/OTF/SourceCodePro-Bold.otf b/docs/assets/fonts/OTF/SourceCodePro-Bold.otf old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/OTF/SourceCodePro-Regular.otf b/docs/assets/fonts/OTF/SourceCodePro-Regular.otf old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf b/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf b/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff b/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff b/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff b/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff b/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 b/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 b/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 b/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 b/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 old mode 100755 new mode 100644 diff --git a/docs/assets/fonts/source-code-pro.css b/docs/assets/fonts/source-code-pro.css old mode 100755 new mode 100644 diff --git a/docs/demo/demo.css b/docs/demo/demo.css deleted file mode 100644 index 3e83f23..0000000 --- a/docs/demo/demo.css +++ /dev/null @@ -1,259 +0,0 @@ -body { - margin: 80px auto; - width: 600px; - background-color: #252525; - color: #E3E3E3; - font-family: 'Rubik', sans-serif; - font-weight: 400; - font-size: 1em; - text-shadow: -1px 2px 2px rgba(0, 0, 0, 0.6); - line-height: 1.5em; - -webkit-font-smoothing: antialiased; -} - -a:visited { - color: #8F70C8; -} - -a { - color: #70ACC8; -} - -.template { - display: none; -} - -fieldset { - padding: 0px; - margin: 0px; - border: none; - margin: 30px 0; -} -label { - font-weight: 300; - font-size: 1.2em; -} -input, button { - font-size: 1.3em; - font-family: 'Rubik', sans-serif; - font-weight: 400; - padding: 0.2em; - color: #E3E3E3; - background-color: #2E2D2D; - border: none; - padding: 10px; - box-sizing: content-box; - border-radius: 10px -} -input { - width: 480px; -} -button { - cursor:pointer; -} -button:hover { - color: #50B5E9; -} - -#profile-content { - box-sizing: border-box; - margin-top: 30px; - padding: 20px; - background-color: #2E2D2D; - border-radius: 25px; - text-align: left; -} - #profile-content .main-stats { - padding: 0px 15px; - margin-top: -10px; - text-transform: uppercase; - box-sizing: border-box; - } - - #profile-content .main-stats .middot-separator { - margin: 0 10px; - } - #profile-content .main-stats .small-stat-icon { - position: relative; - top: 5px; - margin-right: 10px; - filter: drop-shadow(1px 1px 4px rgba(10, 10, 10, 0.3)); - } - #profile-content .main-stats .level { - float: left; - } - #profile-content .main-stats .wealth-group { - float: right; - } - #profile-content .main-stats .wealth-group .unit { - margin-left: 10px; - } - #profile-content .bio { - padding: 10px 20px 15px 20px; - } - #profile-content .bio span { - font-weight: 600; - margin: 2px; - } - #profile-content .title-header h2 { - display: inline-block; - margin-bottom: 0px; - } - #profile-content .title-icon { - width: 45px; - display: inline-block; - margin-bottom: -3px; - padding-right: 10px; - } - #profile-content .title-header .spacer { - width: 100%; - height: 0px; - margin-bottom: 15px; - border-bottom: 3px solid rgba(151, 151, 151, 0.2); - } - #profile-content .small-stats { - margin-left: 15px; - float: left; - } - #profile-content .rings { - width: 100%; - margin: 0 auto; - } - #profile-content .warning { - line-height: 1.4em; - padding: 15px; - } - #profile-content .warning i { - font-size: 50px; - display: block; - float: left; - margin: 5px 25px 5px 5px; - } - #profile-content .death-warning { - border: 1px solid #FF6165; - } - #profile-content .title { - text-transform: uppercase; - } - #profile-content .stat-ring { - margin: 0 20px 0 0; - width: 160px; - text-align: center; - position: relative; - float: left; - } - #profile-content .stat-ring .current-stat { - font-weight: bold; - } - #profile-content .stat-ring .max-stat { - font-weight: 300; - } - #profile-content .circular-chart { - width: 120px; - margin: 0 auto; - display: block; - transform: rotate(-90deg); - transform-origin: center center; - filter: drop-shadow(2px 2px 4px rgba(10, 10, 10, 0.2)); - } - #profile-content .circle { - fill: none; - stroke-width: 4; - stroke-linecap: round; - animation: progress 1s ease-out forwards; - } - #profile-content .shadow-circle { - stroke: #585858; - fill: none; - stroke-width: 4; - stroke-linecap: round; - stroke-opacity: 0.4; - } - #profile-content .health { - stroke: #FF6165; - } - #profile-content .experience { - stroke: #FFB445; - } - #profile-content .mana { - stroke: #50B5E9; - } - #profile-content .stat-icon { - position: absolute; - top: calc(50% - 23px); - left: calc(50% - 23px); - width: 45px; - height: 45px; - filter: drop-shadow(2px 2px 4px rgba(10, 10, 10, 0.5)); } - -@keyframes progress { - 0% { - stroke-dasharray: 0 100; } } - #profile-content .big-stat-group { - margin: 20px 0; } - #profile-content .big-stat-group .title { - margin-bottom: 10px; } - #profile-content .big-stat { - font-size: 49px; - padding-right: 10px; - position: relative; - top: 4px; } - #profile-content .stat-group { - border-left: 2px solid rgba(151, 151, 151, 0.2); - padding: 10px 0 4px 0; - } - #profile-content .stat-group i { - padding-right: 15px; - position: relative; - } - -.clearfix::after { - content: ""; - clear: both; - display: table; } - -.habitica-icon { - display: inline-block; } - -/* The following icons were designed for HabitRPG and are licensed under CC-BY-NC-SA 3.0 */ - -.hi-gold { - width: 24px; - height: 24px; - background: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%2212%22%20fill%3D%22%23FFA623%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M6.3%2017.7c-3.1-3.1-3.1-8.2%200-11.3%203.1-3.1%208.2-3.1%2011.3%200%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M17.7%206.3c3.1%203.1%203.1%208.2%200%2011.3-3.1%203.1-8.2%203.1-11.3%200%22%20opacity%3D%22.25%22%2F%3E%3Cpath%20fill%3D%22%23BF7D1A%22%20d%3D%22M12%202C6.5%202%202%206.5%202%2012s4.5%2010%2010%2010%2010-4.5%2010-10S17.5%202%2012%202zm0%2018c-4.4%200-8-3.6-8-8s3.6-8%208-8%208%203.6%208%208-3.6%208-8%208z%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23BF7D1A%22%20d%3D%22M13%209v2h-2V9H9v6h2v-2h2v2h2V9z%22%20opacity%3D%22.75%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") center center no-repeat; -} - -.hi-health { - /* width: 24px; - height: 24px; */ - background: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20fill%3D%22%23F74E52%22%20d%3D%22M2%204.5L6.167%202%2012%205.167%2017.833%202%2022%204.5V12l-4.167%205.833L12%2022l-5.833-4.167L2%2012z%22%2F%3E%3Cpath%20fill%3D%22%23FF6165%22%20d%3D%22M7.333%2016.667L3.667%2011.5V5.417l2.5-1.5L12%207.083l5.833-3.166%202.5%201.5V11.5l-3.666%205.167L12%2019.917z%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M12%2014.083l4.667%202.584L12%2019.917z%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23B52428%22%20d%3D%22M12%2014.083l-4.667%202.584L12%2019.917z%22%20opacity%3D%22.35%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M7.333%2016.667L3.667%2011.5%2012%2014.083z%22%20opacity%3D%22.25%22%2F%3E%3Cpath%20fill%3D%22%23B52428%22%20d%3D%22M16.667%2016.667l3.666-5.167L12%2014.083z%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23B52428%22%20d%3D%22M12%2014.083l5.833-10.166%202.5%201.5V11.5z%22%20opacity%3D%22.35%22%2F%3E%3Cpath%20fill%3D%22%23B52428%22%20d%3D%22M12%2014.083L6.167%203.917l-2.5%201.5V11.5z%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M12%2014.083L6.167%203.917%2012%207.083z%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M12%2014.083l5.833-10.166L12%207.083z%22%20opacity%3D%22.25%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M9.167%2014.833l-3-4.166V6.833h.083L12%209.917l5.75-3.084h.083v3.834l-3%204.166L12%2016.917z%22%20opacity%3D%22.5%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") center center no-repeat; -} - -.hi-mana { - background: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20fill%3D%22%232995CD%22%20d%3D%22M22%2015l-10%209-10-9L12%200z%22%2F%3E%3Cpath%20fill%3D%22%2350B5E9%22%20d%3D%22M4.6%2014.7l7.4-3v9.6z%22%2F%3E%3Cpath%20fill%3D%22%231F709A%22%20d%3D%22M12%2011.7l7.4%203-7.4%206.6z%22%20opacity%3D%22.25%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M12%2011.7V3.6l7.4%2011.1z%22%20opacity%3D%22.25%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M4.6%2014.7L12%203.6v8.1z%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M7.2%2014.3L12%207.2l4.8%207.1-4.8%204.3z%22%20opacity%3D%22.5%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") center center no-repeat; -} - -.hi-experience { - background: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20fill%3D%22%23FFA623%22%20d%3D%22M16%2016l8-4-8-4-4-8-4%208-8%204%208%204%204%208z%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M4.5%2012l5-2.5L12%2012zM12%2019.5l-2.5-5L12%2012zM19.5%2012l-5%202.5L12%2012zM12%204.5l2.5%205L12%2012z%22%20opacity%3D%22.25%22%2F%3E%3Cpath%20fill%3D%22%23BF7D1A%22%20d%3D%22M19.5%2012l-5-2.5L12%2012z%22%20opacity%3D%22.25%22%2F%3E%3Cpath%20fill%3D%22%23BF7D1A%22%20d%3D%22M12%2019.5l2.5-5L12%2012z%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M4.5%2012l5%202.5L12%2012zM12%204.5l-2.5%205L12%2012z%22%20opacity%3D%22.5%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M10.8%2013.2L8.5%2012l2.3-1.2L12%208.5l1.2%202.3%202.3%201.2-2.3%201.2-1.2%202.3z%22%20opacity%3D%22.5%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") center center no-repeat; -} - -.hi-class { - width: 27px; - height: 27px; -} - -.hi-rogue { - background: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%0A%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%3Cpath%20fill%3D%22%234F2A93%22%20d%3D%22M6.492%2035.154l7.358-7.359c1.05-4.274%202.135-8.752%202.22-9.226.072-.495.235-1.597%201.132-2.653.453-.53%201.008-.966%201.707-1.35L39.353%203.301l6.69-1.557-1.494%206.599-11.334%2020.542c-.359.655-.768%201.185-1.248%201.615l-.083.073c-1.065.906-2.133%201.064-2.707%201.15-.447.081-5.02%201.19-9.185%202.212l-7.36%207.36.868%204.688-9.87-1.826-1.828-9.871%204.69.868z%22%2F%3E%0A%3Cpath%20fill%3D%22%234F2A93%22%20d%3D%22M36.05%2041.079l-7.358-7.359c-4.274-1.05-8.753-2.135-9.226-2.22-.495-.071-1.597-.234-2.653-1.13-.53-.454-.966-1.01-1.35-1.708L4.197%208.218%202.641%201.527%209.24%203.021%2029.78%2014.355c.656.36%201.186.768%201.616%201.248l.073.084c.906%201.064%201.064%202.133%201.15%202.707.081.446%201.19%205.019%202.212%209.184l7.36%207.361%204.688-.868-1.826%209.87-9.871%201.827.868-4.689z%22%2F%3E%0A%3Cpath%20fill%3D%22%23C6B6E4%22%20d%3D%22M18.141%2027.193c.22.399.432.664.647.848l7.643-2.722L6.48%205.368c-.164.188.048.728.39%201.377%22%2F%3E%0A%3Cpath%20fill%3D%22%238966C7%22%20d%3D%22M18.792%2028.043c.39.332.78.386%201.181.447.622.091%2010.277%202.468%2010.277%202.468l.91-.91-4.73-4.728-7.638%202.723z%22%2F%3E%0A%3Cpath%20fill%3D%22%237A54C0%22%20d%3D%22M28.306%2017.029c.398.219.663.432.847.647l-2.721%207.642L6.482%205.37c.184-.163.714.036%201.353.363%22%2F%3E%0A%3Cpath%20fill%3D%22%236133B4%22%20d%3D%22M29.155%2017.68c.332.39.386.78.447%201.181.092.622%202.469%2010.276%202.469%2010.276l-.91.91-4.73-4.729%202.724-7.638z%22%2F%3E%0A%3Cpath%20fill%3D%22%238966C7%22%20d%3D%22M35.402%2036.111l-1.494-1.494.467-3.177%201.494%201.495zM38.39%2039.1l-1.494-1.495.468-3.176%201.494%201.494zM32.07%2029.137l.81.81-.467%203.174-2.164-2.162%22%2F%3E%0A%3Cpath%20fill%3D%22%236133B4%22%20d%3D%22M33.908%2034.617l-1.495-1.495.468-3.176%201.494%201.494zM36.896%2037.605l-1.494-1.494.468-3.176%201.494%201.494zM39.885%2040.594L38.39%2039.1l.467-3.177%202.849%202.849z%22%2F%3E%0A%3Cpath%20fill%3D%22%23C6B6E4%22%20d%3D%22M30.25%2030.959l.162-1.657%201.66-.165-.165%201.659zM39.632%2038.52l-.637%203.436%202.037-2.037zM43.069%2037.882l-3.437.637%201.4%201.4zM41.032%2039.92l-2.037%202.036%203.437-.636zM42.432%2041.32l.637-3.437-2.037%202.037z%22%2F%3E%0A%3C%2Fg%3E%0A%3C%2Fsvg%3E") center center no-repeat; -} - -.hi-warrior { - background: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewBox%3D%220%200%2048%2048%22%3E%0A%20%20%20%20%3Cdefs%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20id%3D%22a%22%20d%3D%22M31.392%209.115l-6.791%207.124v6.314l6.791-6.791V9.115z%22%2F%3E%0A%20%20%20%20%3C%2Fdefs%3E%0A%20%20%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%20transform%3D%22translate%280%201%29%22%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F06166%22%20d%3D%22M18.39%2028.762l6.334%202.92c.526.243.868.732.999%201.297.219.948.883%203.156.937%204.33a.88.88%200%200%201-1.24.844l-10.588-5.07a1.781%201.781%200%200%201-.762-.763L9%2021.733a.88.88%200%200%201%20.844-1.24c1.173.054%203.335.683%204.33.937.56.142%201.054.472%201.296.998l2.92%206.334z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23C82B2B%22%20d%3D%22M3.073%2044.085l2.732%202.733%207.085-.557.364-4.656%204.069-4.067%207.401%203.522%205.536-1.49-2.322-9.99%2015.514-14.477L46.422.741l-.007.002.003-.003V.734L32.057%203.702%2017.58%2019.218l-9.99-2.322L6.1%2022.43l3.522%207.403L5.555%2033.9l-4.654.367-.559%207.084%202.732%202.733z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F06166%22%20d%3D%22M8.927%2041.769l-3.544-3.544%208.767-8.765%203.542%203.544z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F06166%22%20d%3D%22M3.377%2040.231l.255-3.235%203.236-.255%203.544%203.546-.255%203.233-3.234.258z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23FFB6B8%22%20d%3D%22M33.935%206.526l.3%206.391%208.108-8.108c-.15-.14-6.26.565-8.408%201.717%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F27B86%22%20d%3D%22M33.787%206.602L16.66%2024.568l4.39%201.538%2013.186-13.189-.3-6.392c-.048.027-.148.077-.148.077%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F27B86%22%20d%3D%22M16.66%2024.567l-.148.164%201.864%204.045%202.67-2.67-4.386-1.539zM40.624%2013.22l-6.392-.3%208.109-8.108c.14.15-.565%206.259-1.717%208.408%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23E5414D%22%20d%3D%22M40.548%2013.368L22.582%2030.496l-1.538-4.39L34.233%2012.92l6.391.3a5.032%205.032%200%200%200-.076.148%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23E5414D%22%20d%3D%22M22.582%2030.495l-.163.148-4.045-1.864%202.67-2.67%201.538%204.386z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23FFB6B8%22%20d%3D%22M6.867%2036.742l-.75%202.742-2.486-2.487zM10.406%2040.28l-2.742.75%202.486%202.486z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23FFB6B8%22%20d%3D%22M6.867%2036.742l3.546%203.546-2.748.741-1.547-1.545z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F27B86%22%20d%3D%22M3.382%2040.237l2.742-.75-2.486-2.486zM6.921%2043.776l.75-2.742%202.486%202.486z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F27B86%22%20d%3D%22M3.382%2040.237l3.547%203.546.74-2.748-1.545-1.547z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23E5414D%22%20d%3D%22M14.701%2036.01l-3.552-3.553%202.143-2.14%203.552%203.55z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F06166%22%20d%3D%22M12.56%2038.151L9.008%2034.6l2.142-2.14%203.552%203.55z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23E5414D%22%20d%3D%22M10.418%2040.293L6.866%2036.74l2.142-2.14%203.552%203.55z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23FFB6B8%22%20d%3D%22M18.395%2028.764l-2.92-6.334a1.629%201.629%200%200%200-.46-.576%202.12%202.12%200%200%200-.836-.422c-.995-.254-3.157-.883-4.33-.937a.871.871%200%200%200-.727.336l4.258%203.634%203.13%206.192%201.89-1.89-.005-.003z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F06166%22%20d%3D%22M13.38%2024.466L9.12%2020.83a.871.871%200%200%200-.117.904l5.07%2010.587c.085.165.197.313.327.444l2.109-2.108-3.13-6.192z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23F27B86%22%20d%3D%22M26.674%2037.312c-.054-1.173-.684-3.337-.937-4.33a2.12%202.12%200%200%200-.423-.836%201.629%201.629%200%200%200-.576-.46l-6.334-2.92-.002-.005-1.891%201.891%206.192%203.13%203.635%204.258a.874.874%200%200%200%20.336-.728%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23E5414D%22%20d%3D%22M22.703%2033.781l3.634%204.259a.871.871%200%200%201-.904.117l-10.587-5.07a1.766%201.766%200%200%201-.444-.327l2.109-2.108%206.192%203.13z%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Cmask%20id%3D%22b%22%20fill%3D%22%23fff%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cuse%20xlink%3Ahref%3D%22%23a%22%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2Fmask%3E%0A%20%20%20%20%20%20%20%20%3Cpath%20fill%3D%22%23FFB6B8%22%20d%3D%22M29.472%2023.28h.987V5.828h-.987zM24.601%2027.692h2.92V10.24h-2.92z%22%20mask%3D%22url%28%23b%29%22%2F%3E%0A%20%20%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E") center center no-repeat; -} - -.hi-mage { - background: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%0A%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%3Cpath%20fill%3D%22%231F6EA2%22%20d%3D%22M24.462%201.151c-3.695%203.714-7.321%2014.62-9.236%2020.913-.84-1.062-1.799-2.076-3.158-3.326-4.806%203.514-7.357%207.25-10.646%2011.816%208.916%207.926%2011.233%209.256%2022.995%2016.005l.045-.025h.001l.045.025c11.76-6.749%2014.079-8.08%2022.994-16.005-3.287-4.566-5.841-8.302-10.645-11.816-1.361%201.25-2.317%202.264-3.158%203.326-1.915-6.294-5.543-17.2-9.236-20.913h-.001z%22%2F%3E%0A%3Cpath%20fill%3D%22%23278ABF%22%20d%3D%22M24.464%2032.108l3.561-1.265c6.075-.605%208.425-2.281%209.426-3.498l6.053%202.707.002.013a.45.45%200%200%201-.105.286%204.97%204.97%200%200%201-.32.33c-3.405%203.315-18.237%2011.981-18.617%2012.202V32.108z%22%2F%3E%0A%3Cpath%20fill%3D%22%2353B4E5%22%20d%3D%22M36.406%2023.041c.23-.188.292-.34.674-.336%201.183.067%205.169%205.175%206.064%206.461.194.278.366.61.36.885l-6.053-2.707c.236-.367.19-.92-.355-1.252-.818-.501-2.529.949-2.959.284-.599-.927%202.04-3.147%202.27-3.335%22%2F%3E%0A%3Cpath%20fill%3D%22%232AA0CF%22%20d%3D%22M23.591%207.487c-5.39%2015.419-8.227%2023.817-8.227%2023.817l9.097%204.76V6.839c-.36%200-.718.216-.87.648%22%2F%3E%0A%3Cpath%20fill%3D%22%23278ABF%22%20d%3D%22M25.332%207.487c-.152-.432-.51-.648-.87-.648v29.225l9.097-4.76s-2.837-8.398-8.227-23.817%22%2F%3E%0A%3Cpath%20fill%3D%22%234DB2D6%22%20d%3D%22M24.462%2032.108L20.9%2030.843c-6.075-.605-8.342-2.352-9.425-3.498L5.42%2030.052l-.002.013a.45.45%200%200%200%20.106.286c.09.104.197.215.318.33%203.406%203.315%2018.238%2011.981%2018.619%2012.202V32.108z%22%2F%3E%0A%3Cpath%20fill%3D%22%236BC4E9%22%20d%3D%22M12.519%2023.041c-.23-.188-.292-.34-.674-.336-1.183.067-5.169%205.175-6.063%206.461-.194.278-.367.61-.361.885l6.053-2.707c-.236-.367-.19-.92.356-1.252.817-.501%202.528.949%202.958.284.6-.927-2.039-3.147-2.27-3.335%22%2F%3E%0A%3Cpath%20fill%3D%22%23A9DBF6%22%20d%3D%22M24.463%2034.998v-5.101l-7.21-4.13-1.48%204.049z%22%2F%3E%0A%3Cpath%20fill%3D%22%2384CFF2%22%20d%3D%22M24.463%2034.998v-5.101l7.207-4.13%201.483%204.049z%22%2F%3E%0A%3C%2Fg%3E%0A%3C%2Fsvg%3E") center center no-repeat; -} - -.hi-healer { - background: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%0A%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%3Cpath%20fill%3D%22%23CF8229%22%20d%3D%22M24.61%2047.604s-1.789-.735-2.585-1.138l-.183-.09c-5.13-2.58-10.54-5.782-13.881-12.108-3.777-7.15-3.05-18.565-2.955-19.843.075-1.003.378-3.464.378-3.464s1.941-.858%202.901-1.311l13.864-7.012c.764-.363%202.462-1.114%202.462-1.114s1.697.75%202.463%201.114l13.86%207.01c.966.457%203.05%201.315%203.05%201.315s.157%202.467.232%203.465c.094%201.275.822%2012.69-2.955%2019.84-3.343%206.33-8.755%209.53-13.876%2012.103l-.19.096c-.795.402-2.584%201.137-2.584%201.137%22%2F%3E%0A%3Cpath%20fill%3D%22%23E29E45%22%20d%3D%22M12.694%2016.147l11.059-5.695c.27-.129.564-.192.858-.192V5.829c-.294%200-.588.063-.858.192L9.888%2013.032a1.985%201.985%200%200%200-.727.603l3.533%202.523v-.011M24.612%2010.259V5.827z%22%2F%3E%0A%3Cpath%20fill%3D%22%23FDC67E%22%20d%3D%22M23.752%206.008L9.89%2013.02c-.655.31-1.09.945-1.145%201.665-.034.466-.81%2011.487%202.531%2017.814%202.782%205.266%207.36%208.048%2012.242%2010.504l.19.096c.284.142.593.213.902.213V5.816c-.293%200-.585.064-.858.192%22%2F%3E%0A%3Cpath%20fill%3D%22%23FFA623%22%20d%3D%22M25.512%2043.099l.19-.096c4.885-2.454%209.462-5.238%2012.244-10.505%203.341-6.326%202.565-17.347%202.53-17.813a2.009%202.009%200%200%200-1.143-1.665L25.468%206.008a1.999%201.999%200%200%200-.858-.192v37.496c.31%200%20.618-.071.902-.213%22%2F%3E%0A%3Cpath%20fill%3D%22%23FFDDB5%22%20d%3D%22M12.694%2016.134l11.059-5.694c.27-.129.564-.192.858-.192V5.816c-.294%200-.587.064-.858.192L9.889%2013.02a1.985%201.985%200%200%200-.728.603l3.533%202.523v-.012%22%2F%3E%0A%3Cpath%20fill%3D%22%23FDC67E%22%20d%3D%22M39.333%2013.02L25.468%206.008a1.999%201.999%200%200%200-.858-.192v4.431c.294%200%20.588.065.858.192l11.065%205.702%203.527-2.519a1.973%201.973%200%200%200-.727-.602%22%2F%3E%0A%3Cpath%20fill%3D%22%23FFA623%22%20d%3D%22M24.214%2038.858c-4.197-2.128-7.383-4.438-9.389-8.235-2.239-4.238-2.229-11.63-2.131-14.477L9.16%2013.623a2%202%200%200%200-.417%201.062c-.015.207-.174%202.498-.057%205.531.148%203.798.731%208.765%202.59%2012.282%202.782%205.267%207.357%208.05%2012.242%2010.505l.19.096c.284.142.593.213.902.213v-4.33c-.098%200-.197-.04-.397-.124%22%2F%3E%0A%3Cpath%20fill%3D%22%23E59025%22%20d%3D%22M34.395%2030.623c-2.008%203.804-5.203%206.113-9.406%208.243-.188.078-.282.117-.378.117v4.33c.31%200%20.618-.072.9-.214l.19-.096c4.885-2.455%209.462-5.238%2012.244-10.504%201.857-3.518%202.443-8.485%202.59-12.282.118-3.034-.044-5.325-.06-5.532a1.995%201.995%200%200%200-.414-1.064l-3.527%202.521c.129%203.53-.07%2010.566-2.139%2014.481%22%2F%3E%0A%3Cpath%20fill%3D%22%23FFE4C9%22%20d%3D%22M16.076%2024.564l5.69-2.845%202.845%202.845zM24.61%2033.098l-2.845-5.688%202.846-2.846z%22%2F%3E%0A%3Cpath%20fill%3D%22%23E59025%22%20d%3D%22M33.145%2024.564l-5.689%202.846-2.845-2.846zM24.61%2016.03l2.846%205.689-2.845%202.845z%22%2F%3E%0A%3Cpath%20fill%3D%22%23E59025%22%20d%3D%22M33.145%2024.564l-5.689-2.845-2.845%202.845zM24.61%2033.098l2.846-5.688-2.845-2.846z%22%2F%3E%0A%3Cpath%20fill%3D%22%23FFE4C9%22%20d%3D%22M16.076%2024.564l5.69%202.846%202.845-2.846zM24.61%2016.03l-2.845%205.689%202.846%202.845z%22%2F%3E%0A%3C%2Fg%3E%0A%3C%2Fsvg%3E") center center no-repeat; -} diff --git a/docs/demo/demo.js b/docs/demo/demo.js deleted file mode 100644 index 6f432f8..0000000 --- a/docs/demo/demo.js +++ /dev/null @@ -1,68 +0,0 @@ -// putting everything in a demo object because namespaces are cool -var demo = { - start: function() { - // this is the element in the DOM where we're going to append - // all of our templates when we have new data - this.contentElement = document.getElementById("profile-content"); - - // compiling our nunjucks templates so that we can render these - // babies when we get some sweet API data later. - // You don't have to use nunjucks templates I just think it makes the - // HTML look super nice. - this.template = nunjucks.compile(document.getElementById("profile-template").innerHTML); - this.errorTemplate = nunjucks.compile(document.getElementById("error-template").innerHTML); - - // time to fetch our user for the first time! - this.fetchUser(); - }, - - fetchUser: function() { - let userID = document.getElementById("userID-field").value; - let xclient = "6c2c57d5-67c3-4edf-9a74-2d6d70aa4c56-HabiticaMagicDemo"; - let manager = new HabiticaAPIManager(xclient); - - // First we fetch the Habitica Content so that the Habitica user - // we get later will have all their armor & quest data fully - // populated. We could keep the manager object around instead of - // creating a new one every time we go to fetch a user (and then - // we wouldn't have to fetch the data every time), but since the - // Habitica content data will be cached by the browser anyway this - // isn't too big a deal. - manager.fetchContentData() - // once our content data fetch succeeds we can add the fetchUser() - // call to the promise chain. - .then(() => { - return manager.fetchUser(userID); - }) - // Now that our fetchUser call has finished we're done with API calls - // and we can send the resulting HabiticaUser object to our - // renderUserTemplate function. - .then((user) => { - this.renderUserTemplate(user); - }) - // The nice thing about promise chains like this is that if an error - // happens AT ANY POINT in the chain here we'll automatically skip - // to this catch block, which will render our error template - .catch((error) => { - this.renderErrorTemplate(error); - }); - }, - - // If we get an error we'll render the error temlate with the error - // data we just got - renderErrorTemplate: function(error) { - console.log(error); - this.contentElement.innerHTML = this.errorTemplate.render({data: error}); - }, - - // If we get a user we'll render the user template with the user data - renderUserTemplate: function(user) { - console.log(user); - this.contentElement.innerHTML = this.template.render({user: user}); - } -}; - -// call our demo start when the DOM is ready! -document.addEventListener("DOMContentLoaded", (event) => { - demo.start(); -}) diff --git a/docs/index.html b/docs/index.html index bfb547a..062ec1b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,5 +1,5 @@ - + HabiticaMagicJS 2.1.0 | Documentation @@ -20,11 +20,34 @@

HabiticaMagicJS

placeholder='Filter' id='filter-input' class='col12 block input' + spellcheck='false' + autocapitalize='off' + autocorrect='off' type='text' />
@@ -382,6 +391,116 @@

HabiticaMagicJS

+
+ + +
+ +

+ HabiticaUserID +

+ + +
+ + +

The User's ID. See https://habitica.fandom.com/wiki/APIOptions#User_ID.28UID.29

+ +
HabiticaUserID
+ +

+ Type: + string +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +

+ HabiticaAPIToken +

+ + +
+ + +

The User's API Token. See https://habitica.fandom.com/wiki/API_Options#API_Token

+ +
HabiticaAPIToken
+ +

+ Type: + string +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
@@ -448,6 +567,8 @@

+ +
Instance Members
@@ -495,6 +616,8 @@

+ + @@ -547,6 +670,8 @@

+ + @@ -599,6 +724,8 @@

+ + @@ -657,6 +784,8 @@

+ +

@@ -726,6 +855,8 @@

+ +

@@ -802,6 +933,8 @@

+ + @@ -878,6 +1011,8 @@

+ + @@ -954,6 +1089,8 @@

+ + @@ -1051,6 +1188,8 @@

+ + @@ -1132,6 +1271,8 @@

+ + @@ -1230,16 +1371,18 @@

+ + -
+
- replaceKeysWithContent(data) + putRequest(baseURL, userID, userAPIToken, queryParams)
-
+
- getQueryStringURL(baseURL, queryParams) + replaceKeysWithContent(data)
-
- - - - - - - - - - - -
+
+
+
+ + getQueryStringURL(baseURL, queryParams) +
+
+ +
+
@@ -1554,6 +1695,8 @@

+ +
Instance Members
@@ -1601,6 +1744,8 @@

+ + @@ -1653,6 +1798,8 @@

+ + @@ -1705,6 +1852,8 @@

+ + @@ -1757,6 +1906,8 @@

+ + @@ -1809,6 +1960,8 @@

+ + @@ -1861,6 +2014,8 @@

+ + @@ -1913,6 +2068,8 @@

+ + @@ -1965,6 +2122,8 @@

+ + @@ -2017,6 +2176,8 @@

+ + @@ -2069,6 +2230,8 @@

+ + @@ -2121,6 +2284,8 @@

+ + @@ -2173,6 +2338,8 @@

+ + @@ -2225,6 +2392,8 @@

+ + @@ -2277,6 +2446,8 @@

+ + @@ -2329,6 +2500,8 @@

+ + @@ -2381,6 +2554,8 @@

+ + @@ -2433,6 +2608,8 @@

+ + @@ -2486,6 +2663,8 @@

+ + @@ -2538,6 +2717,8 @@

+ + @@ -2590,6 +2771,8 @@

+ + @@ -2642,6 +2825,8 @@

+ + @@ -2694,6 +2879,8 @@

+ + @@ -2746,6 +2933,8 @@

+ + @@ -2798,6 +2987,8 @@

+ + @@ -2813,6 +3004,100 @@

+ + + + + +
+ + +
+ +

+ DailyStats +

+ + +
+ + + +
DailyStats
+ +

+ Type: + object +

+ + + + + + + + + + + + + +
Properties
+
+ +
+ dueCount (Number) + : Count of unfinished dailes that are due today. + + +
+ +
+ totalDamageToSelf (Number) + : Total damage user will receive from missed dailies and boss damage. + + +
+ +
+ dailyDamageToSelf (Number) + : Damage user will receive from missed dailies. + + +
+ +
+ bossDamage (Number) + : Damage entire party will receive from boss due to missed dailies. + + +
+ +
+ dailiesEvaded (Number) + : Number of unfinished dailes ignored due to Rogue sneakiness. + + +
+ +
+ + + + + + + + + + + + + + + + +
@@ -2871,6 +3156,8 @@

+ +
Instance Members
@@ -2918,6 +3205,8 @@

+ + @@ -2970,6 +3259,8 @@

+ + @@ -3022,6 +3313,8 @@

+ + @@ -3094,6 +3387,8 @@

+ +

@@ -3164,104 +3459,14 @@

- - -

-

- -
- - - - - - - -
- - -
- -

- DailyStats -

- - -
- - - -
DailyStats
- -

- Type: - object -

- - - - - - - - - - - - - -
Properties
-
- -
- dueCount (Number) - : Count of unfinished dailes that are due today. - - -
- -
- totalDamageToSelf (Number) - : Total damage user will receive from missed dailies and boss damage. - - -
- -
- dailyDamageToSelf (Number) - : Damage user will receive from missed dailies. - - -
- -
- bossDamage (Number) - : Damage entire party will receive from boss due to missed dailies. - - -
- -
- dailiesEvaded (Number) - : Number of unfinished dailes ignored due to Rogue sneakiness. - - -
- +
- - - - - - - - + diff --git a/minify.js b/minify.js index a40acb4..2579337 100644 --- a/minify.js +++ b/minify.js @@ -1,37 +1,37 @@ -const fs = require('fs'); -const minify = require('@node-minify/core'); -const terser = require('@node-minify/terser'); -const noCompress = require('@node-minify/no-compress'); -const package = require('./package.json'); - -let version = "v" + package.version; -let output = `dist/HabiticaMagic-${version}.js`; -let minOutput = `dist/HabiticaMagic-${version}.min.js`; -let files = [ - 'src/header.js', - 'src/HabiticaUserTasksManager.js', - 'src/HabiticaUser.js', - 'src/HabiticaAPIManager.js' -]; - -let preamble = fs.readFileSync('src/header.js', 'utf8'); - -// combine all the files -minify({ - compressor: noCompress, - input: files, - output: output -}) -// Run the minifier -.then(function(min) { - minify({ - compressor: terser, - input: output, - output: minOutput, - options: { - output: { - preamble: preamble - } - } - }); -}); +const fs = require('fs'); +const minify = require('@node-minify/core'); +const terser = require('@node-minify/terser'); +const noCompress = require('@node-minify/no-compress'); +const package = require('./package.json'); + +let version = "v" + package.version; +let output = `dist/HabiticaMagic-${version}.js`; +let minOutput = `dist/HabiticaMagic-${version}.min.js`; +let files = [ + 'src/header.js', + 'src/HabiticaUserTasksManager.js', + 'src/HabiticaUser.js', + 'src/HabiticaAPIManager.js' +]; + +let preamble = fs.readFileSync('src/header.js', 'utf8'); + +// combine all the files +minify({ + compressor: noCompress, + input: files, + output: output +}) +// Run the minifier +.then(function(min) { + minify({ + compressor: terser, + input: output, + output: minOutput, + options: { + output: { + preamble: preamble + } + } + }); +}); diff --git a/src/HabiticaAPIManager.js b/src/HabiticaAPIManager.js index fbd7bfa..1f8d5a9 100644 --- a/src/HabiticaAPIManager.js +++ b/src/HabiticaAPIManager.js @@ -5,258 +5,291 @@ * @param {string} [language="en"] - The language code to retrieve content for. See {@link HabiticaAPIManager#language|language} */ class HabiticaAPIManager { - constructor(xclient, language="en") { - /** - * The language code to retrieve content for. See https://habitica.com/apidoc/#api-Content-ContentGet for list of allowed values. - * @type {string} - */ - this.language = language; - /** - * The xclient header string for this application. See https://habitica.fandom.com/wiki/Guidance_for_Comrades#X-Client_Header for details. - * @type {string} - */ - this.xclient = xclient; - /** - * The data cache for looking up Habitica content such as items, quests, or appearance information. - * @type {object} - */ - this.content = {}; - } - - // UNAUTHENTICATED HELPER FUNCTIONS - - /** - * Load Habitica content from the api. Populates the {@link HabiticaAPIManager#content|content} attribute. - * @returns {Promise} - */ - fetchContentData() { - const baseURL = "https://habitica.com/api/v3/content"; - return this.getRequest(baseURL, {language: this.language}) - .then((rawData) => { - var data = JSON.parse(rawData).data; - this.content = data; - }); - } - - /** - * Fetches a HabiticaUser instance from the api, containing publicly visible user data. - * @param {HabiticaUserID} userID - The ID of the habitica user. - * @returns {Promise} Promise provides a HabiticaUser instance. - */ - fetchUser(userID) { - const baseURL = "https://habitica.com/api/v3/members/" + userID; - return this.getRequest(baseURL) - .then((rawData) => { - var data = JSON.parse(rawData).data; - let user = new HabiticaUser(this.replaceKeysWithContent(data)); - return user; - }); - } - - // AUTHENTICATED HELPER FUNCTIONS - - /** - * Fetches a HabiticaUser instance, including personal information. - * @param {HabiticaUserID} userID - * @param {HabiticaAPIToken} userAPIToken - * @returns {Promise} Promise provides a HabiticaUser instance. - */ - fetchAuthenticatedUser(userID, userAPIToken) { - const url = "https://habitica.com/api/v3/user"; - return this.authGetRequest(url, userID, userAPIToken) - .then((rawData) => { - var data = JSON.parse(rawData).data; - let user = new HabiticaUser(this.replaceKeysWithContent(data)); - return user; - }); - } - - /** - * Fetches the list of tasks for a given user. - * @param {HabiticaUserID} userID - * @param {HabiticaAPIToken} userAPIToken - * @returns {Promise} - */ - fetchUserTasks(userID, userAPIToken) { - const url = "https://habitica.com/api/v3/tasks/user"; - return this.authGetRequest(url, userID, userAPIToken) - .then((rawData) => { - var data = JSON.parse(rawData).data; - let tasks = new HabiticaUserTasksManager(data); - return tasks; - }); - } - - /** - * Fetches a user and their list of tasks. - * @param {HabiticaUserID} userID - * @param {HabiticaAPIToken} userAPIToken - * @returns {Promise} Promise provides a HabiticaUser instance, with a populated tasks manager. - */ - fetchUserWithTasks(userID, userAPIToken) { - var user; - return this.fetchAuthenticatedUser(userID, userAPIToken) - .then((newUser) => { - user = newUser; - return this.fetchUserTasks(userID, userAPIToken); - }) - .then((tasks) => { - user.tasks = tasks; - return user; - }); - } - - // API REQUEST FUNCTIONS - - /** - * Make an authenticated GET request to the Habitica API. Data object returned varies based on the API url called. - * @param {string} baseURL - the url of the api call. - * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. - * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. - * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. - * @returns {Promise} Promise containing the raw API response data as a string. - */ - authGetRequest(baseURL, userID, userAPIToken, queryParams={}) { - let url = this.getQueryStringURL(baseURL, queryParams); - - let promise = new Promise((resolve, reject) => { - let req = new XMLHttpRequest(); - req.open("GET", url); - - req.onerror = function() { - reject(this.statusText); - }; - - req.onload = function() { - if (this.status == 200) { - resolve(this.responseText); - } else { - reject(this.responseText); - } - } - - req.setRequestHeader("x-api-user", userID); - req.setRequestHeader("x-api-key", userAPIToken); - req.setRequestHeader("x-client", this.xclient); - req.send(); - }); - - return promise; - } - - /** - * Make a GET request to the Habitica API. - * Data object returned varies based on the API url called. - * For accessing personal data endpoints, use {@link HabiticaAPIManager#authGetRequest|authGetRequest} - * @param {string} baseURL - the url of the api call. - * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. - * @returns {Promise} Promise containing the raw API response data as a string. - */ - getRequest(baseURL, queryParams={}) { - let url = this.getQueryStringURL(baseURL, queryParams); - - let promise = new Promise((resolve, reject) => { - let req = new XMLHttpRequest(); - req.open("GET", url); - - req.onerror = function() { - reject(this.statusText); - }; - - req.onload = function() { - if (this.status == 200) { - resolve(this.responseText); - } else { - reject(this.responseText); - } - } - req.setRequestHeader("x-client", this.xclient); - req.send(); - }); - - return promise; - } + constructor(xclient, language = "en") { + /** + * The language code to retrieve content for. See https://habitica.com/apidoc/#api-Content-ContentGet for list of allowed values. + * @type {string} + */ + this.language = language; + /** + * The xclient header string for this application. See https://habitica.fandom.com/wiki/Guidance_for_Comrades#X-Client_Header for details. + * @type {string} + */ + this.xclient = xclient; + /** + * The data cache for looking up Habitica content such as items, quests, or appearance information. + * @type {object} + */ + this.content = {}; + } + + // UNAUTHENTICATED HELPER FUNCTIONS + + /** + * Load Habitica content from the api. Populates the {@link HabiticaAPIManager#content|content} attribute. + * @returns {Promise} + */ + fetchContentData() { + const baseURL = "https://habitica.com/api/v3/content"; + return this.getRequest(baseURL, { language: this.language }).then( + (rawData) => { + var data = JSON.parse(rawData).data; + this.content = data; + } + ); + } + + /** + * Fetches a HabiticaUser instance from the api, containing publicly visible user data. + * @param {HabiticaUserID} userID - The ID of the habitica user. + * @returns {Promise} Promise provides a HabiticaUser instance. + */ + fetchUser(userID) { + const baseURL = "https://habitica.com/api/v3/members/" + userID; + return this.getRequest(baseURL).then((rawData) => { + var data = JSON.parse(rawData).data; + let user = new HabiticaUser(this.replaceKeysWithContent(data)); + return user; + }); + } + + // AUTHENTICATED HELPER FUNCTIONS /** - * Make a POST request to the Habitica API. - * Data object returned varies based on the API url called. - * @param {string} baseURL - the url of the api call. - * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. - * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. - * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. - * @returns {Promise} Promise containing the raw API response data as a string. - */ - postRequest(baseURL, userID, userAPIToken, queryParams={}) { - let promise = new Promise((resolve, reject) => { - let req = new XMLHttpRequest(); - req.open("POST", baseURL); - - req.onerror = function() { - reject(this.responseText); - }; - - req.onload = function() { - if (req.status === 201 || req.status === 200) { - resolve(this.responseText); - } else { - reject(this.responseText); - } - } + * Fetches a HabiticaUser instance, including personal information. + * @param {HabiticaUserID} userID + * @param {HabiticaAPIToken} userAPIToken + * @returns {Promise} Promise provides a HabiticaUser instance. + */ + fetchAuthenticatedUser(userID, userAPIToken) { + const url = "https://habitica.com/api/v3/user"; + return this.authGetRequest(url, userID, userAPIToken).then((rawData) => { + var data = JSON.parse(rawData).data; + let user = new HabiticaUser(this.replaceKeysWithContent(data)); + return user; + }); + } + + /** + * Fetches the list of tasks for a given user. + * @param {HabiticaUserID} userID + * @param {HabiticaAPIToken} userAPIToken + * @returns {Promise} + */ + fetchUserTasks(userID, userAPIToken) { + const url = "https://habitica.com/api/v3/tasks/user"; + return this.authGetRequest(url, userID, userAPIToken).then((rawData) => { + var data = JSON.parse(rawData).data; + let tasks = new HabiticaUserTasksManager(data); + return tasks; + }); + } + + /** + * Fetches a user and their list of tasks. + * @param {HabiticaUserID} userID + * @param {HabiticaAPIToken} userAPIToken + * @returns {Promise} Promise provides a HabiticaUser instance, with a populated tasks manager. + */ + fetchUserWithTasks(userID, userAPIToken) { + var user; + return this.fetchAuthenticatedUser(userID, userAPIToken) + .then((newUser) => { + user = newUser; + return this.fetchUserTasks(userID, userAPIToken); + }) + .then((tasks) => { + user.tasks = tasks; + return user; + }); + } + + // API REQUEST FUNCTIONS + + /** + * Make an authenticated GET request to the Habitica API. Data object returned varies based on the API url called. + * @param {string} baseURL - the url of the api call. + * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. + * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. + * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. + * @returns {Promise} Promise containing the raw API response data as a string. + */ + authGetRequest(baseURL, userID, userAPIToken, queryParams = {}) { + let url = this.getQueryStringURL(baseURL, queryParams); + + let promise = new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("GET", url); + + req.onerror = function () { + reject(this.statusText); + }; + + req.onload = function () { + if (this.status == 200) { + resolve(this.responseText); + } else { + reject(this.responseText); + } + }; + + req.setRequestHeader("x-api-user", userID); + req.setRequestHeader("x-api-key", userAPIToken); + req.setRequestHeader("x-client", this.xclient); + req.send(); + }); + + return promise; + } + + /** + * Make a GET request to the Habitica API. + * Data object returned varies based on the API url called. + * For accessing personal data endpoints, use {@link HabiticaAPIManager#authGetRequest|authGetRequest} + * @param {string} baseURL - the url of the api call. + * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. + * @returns {Promise} Promise containing the raw API response data as a string. + */ + getRequest(baseURL, queryParams = {}) { + let url = this.getQueryStringURL(baseURL, queryParams); + + let promise = new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("GET", url); + + req.onerror = function () { + reject(this.statusText); + }; + + req.onload = function () { + if (this.status == 200) { + resolve(this.responseText); + } else { + reject(this.responseText); + } + }; + req.setRequestHeader("x-client", this.xclient); + req.send(); + }); + + return promise; + } + + /** + * Make a POST request to the Habitica API. + * Data object returned varies based on the API url called. + * @param {string} baseURL - the url of the api call. + * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. + * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. + * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. + * @returns {Promise} Promise containing the raw API response data as a string. + */ + postRequest(baseURL, userID, userAPIToken, queryParams = {}) { + let promise = new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("POST", baseURL); + + req.onerror = function () { + reject(this.responseText); + }; + + req.onload = function () { + if (req.status === 201 || req.status === 200) { + resolve(this.responseText); + } else { + reject(this.responseText); + } + }; req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - req.setRequestHeader("x-client", this.xclient); - req.setRequestHeader("x-api-user", userID); - req.setRequestHeader("x-api-key", userAPIToken); - req.send(JSON.stringify(queryParams)); - }); - return promise - } - - // CLASS HELPER FUNCTIONS - - /** - * Updates the data object keys with values from the the {@link HabiticaAPIManager#content|content}. - * @param {object} data - See {@link HabiticaUser#apiData|HabiticaUser.apiData} - * @returns {object} The same data object passed in, after it is updated. - */ - replaceKeysWithContent(data) { - // if we haven't fetched any content data, nothing we can do here - if (Object.entries(this.content).length == 0) { - return data; - } - - // replace equipped and costume gear with full content version - for (var section of [data.items.gear.equipped, data.items.gear.costume]) { - for (var key in section) { - let armorName = section[key]; - let armor = this.content.gear.flat[armorName]; - section[key] = armor; - } - } - // replace party quest key with actual quest - if (data.party.quest.key) { - data.party.quest.data = this.content.quests[data.party.quest.key]; - } - return data; - } - - /** - * Convert a base url and query parameters into a full url with querystring. - * @param {string} baseURL the URI of the API endpoint. - * @param {object} queryParams key-value pairs in an object to be parameterized. - * @returns {string} the full url with querystring. - */ - getQueryStringURL(baseURL, queryParams) { - let params = Object.entries(queryParams); - if (params.length < 1) { - return baseURL; - } - - return baseURL + "?" + - params.map(kv => kv.map(encodeURIComponent).join("=")) - .join("&"); - } -} + req.setRequestHeader("x-client", this.xclient); + req.setRequestHeader("x-api-user", userID); + req.setRequestHeader("x-api-key", userAPIToken); + req.send(JSON.stringify(queryParams)); + }); + return promise; + } + + /** + * Make a PUT request to the Habitica API. + * Data object returned varies based on the API url called. + * @param {string} baseURL - the url of the api call. + * @param {HabiticaUserID} userID - the ID of the user, needed for authentication. + * @param {HabiticaAPIToken} userAPIToken - the API Token for the user, needed for authentication. + * @param {object} [queryParams={}] - key-value pairs for any parameters needed by the api call. + * @returns {Promise} Promise containing the raw API response data as a string. + */ + putRequest(baseURL, userID, userAPIToken, queryParams = {}) { + let promise = new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("PUT", baseURL); + req.onerror = function () { + reject(this.responseText); + }; + + req.onload = function () { + if (req.status === 201 || req.status === 200) { + resolve(this.responseText); + } else { + reject(this.responseText); + } + }; + req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + req.setRequestHeader("x-client", this.xclient); + req.setRequestHeader("x-api-user", userID); + req.setRequestHeader("x-api-key", userAPIToken); + req.send(JSON.stringify(queryParams)); + }); + return promise; + } + + // CLASS HELPER FUNCTIONS + + /** + * Updates the data object keys with values from the the {@link HabiticaAPIManager#content|content}. + * @param {object} data - See {@link HabiticaUser#apiData|HabiticaUser.apiData} + * @returns {object} The same data object passed in, after it is updated. + */ + replaceKeysWithContent(data) { + // if we haven't fetched any content data, nothing we can do here + if (Object.entries(this.content).length == 0) { + return data; + } + + // replace equipped and costume gear with full content version + for (var section of [data.items.gear.equipped, data.items.gear.costume]) { + for (var key in section) { + let armorName = section[key]; + let armor = this.content.gear.flat[armorName]; + section[key] = armor; + } + } + // replace party quest key with actual quest + if (data.party.quest.key) { + data.party.quest.data = this.content.quests[data.party.quest.key]; + } + return data; + } + + /** + * Convert a base url and query parameters into a full url with querystring. + * @param {string} baseURL the URI of the API endpoint. + * @param {object} queryParams key-value pairs in an object to be parameterized. + * @returns {string} the full url with querystring. + */ + getQueryStringURL(baseURL, queryParams) { + let params = Object.entries(queryParams); + if (params.length < 1) { + return baseURL; + } + + return ( + baseURL + + "?" + + params.map((kv) => kv.map(encodeURIComponent).join("=")).join("&") + ); + } +} /** * The User's ID. See https://habitica.fandom.com/wiki/API_Options#User_ID_.28UID.29 diff --git a/src/HabiticaUser.js b/src/HabiticaUser.js index ff5cdd6..10d8750 100644 --- a/src/HabiticaUser.js +++ b/src/HabiticaUser.js @@ -1,253 +1,253 @@ -/** - * A class representing the Habitica User. - * @class - * @param {object} data the json data object returned from the habitica API. - */ -class HabiticaUser { - constructor(data) { - this.apiData = data; - this.stats = this._calculateStats(); - } - /** - * The number of Subscriber Gems the user owns. - * @type {number} - */ - get gems() { - return this.apiData.balance * 4; - } - /** - * The number of Mystic Hourglasses the user owns. - * @type {number} - */ - get hourglasses() { - return this.apiData.purchased.plan.consecutive.trinkets; - } - /** - * The amount of Gold the user owns. - * @type {number} - */ - get gold() { - return Math.round(this.apiData.stats.gp); - } - /** - * The amount of Gold the user owns as a nicely formatted string. - * @type {number} - */ - get goldCompact() { - const formatter = new Intl.NumberFormat('lookup', { - notation: 'compact', - compactDisplay: 'short', - }); - return formatter.format(this.gold); - } - /** - * The experience level of the user. - * @type {number} - */ - get level() { - return this.apiData.stats.lvl; - } - /** - * The displayed name of the user. - * @type {string} - */ - get displayName() { - return this.apiData.profile.name; - } - /** - * The character class of the user. - * @type {string} - */ - get className() { - // the mage class is defined as "wizard" in the API, but referred - // to everywhere else in Habitica as mage, so we're gonna return mage - // here as that's what users on the front end would expect to see - // https://habitica.fandom.com/wiki/Guidance_for_Comrades#Class_Name_.28State_Mage_Instead_of_Wizard.29 - if (this.apiData.stats.class == "wizard") { - return "mage"; - } - return this.apiData.stats.class; - } - /** - * The bio of the user (may contain markdown formatting). - * @type {string} - */ - get bio() { - return this.apiData.profile.blurb; - } - /** - * The experience points the user has gained so far this level. - * @type {Number} - */ - get experience() { - return Math.floor(this.apiData.stats.exp); - } - /** - * The experience points needed to reach the next level. - * @type {Number} - */ - get experienceToLevel() { - return Math.round(this.apiData.stats.toNextLevel); - } - /** - * The amount of remaining Mana the user currently has. - * @type {Number} - */ - get mana() { - return Math.floor(this.apiData.stats.mp); - } - /** - * The maximum amount of mana the user can have. - * @type {Number} - */ - get manaMax() { - return Math.round(this.apiData.stats.maxMP); - } - /** - * The amount of remaining health the user currently has. - * @type {Number} - */ - get health() { - return Math.floor(this.apiData.stats.hp); - } - /** - * The maximum amount of health the user can have. - * @type {Number} - */ - get healthMax() { - return Math.round(this.apiData.stats.maxHealth); - } - /** - * The number of dailies this user can skip without taking damage. (Rogue Skill) - * @type {Number} - */ - get stealth() { - return this.apiData.stats.buffs.stealth; - } - /** - * The set of items this user has equipped. - * @type {object} - */ - get armor() { - return this.apiData.items.gear.equipped; - } - /** - * The set of costume items this user has equipped. - * @type {object} - */ - get costume() { - return this.apiData.items.gear.costume; - } - /** - * The visual set of items the user has on (either {@link HabiticaUser#armor|armor} - * or {@link HabiticaUser#costume|costume} depending on the users preferences). - * @type {object} - */ - get outfit() { - return this.apiData.preferences.costume == true ? this.costume : this.armor; - } - /** - * Flag to check if user is in the Inn. - * @type {Boolean} - */ - get isSleeping() { - return this.apiData.preferences.sleep; - } - - /** - * @private - */ - _calculateStats() { - var stats = { - totals: {str: 0, con: 0, int: 0, per: 0}, - armor: {str: 0, con: 0, int: 0, per: 0}, - buffs: { - str: this.apiData.stats.buffs.str, - con: this.apiData.stats.buffs.con, - int: this.apiData.stats.buffs.int, - per: this.apiData.stats.buffs.per - }, - points: { - str: this.apiData.stats.str, - con: this.apiData.stats.con, - int: this.apiData.stats.int, - per: this.apiData.stats.per - } - } - - // calculate armor stats from each piece of armor - for (var key in this.armor) { - let item = this.armor[key]; - for (var stat in stats.armor) { - stats.armor[stat] += item[stat]; - // apply class bonus if user is wearing special class gear - if (this.className === item.klass || - this.className === item.specialClass) { - stats.armor[stat] += .5 * item[stat]; - } - } - } - - // add up all stats for total, including level bonus - let levelBonus = Math.floor(this.level / 2); - for (var stat in stats.totals) { - stats.totals[stat] = stats.armor[stat] + - stats.buffs[stat] + - stats.points[stat] + - levelBonus; - } - - return stats; - } - - /** - * This user's constitution bonus against daily damage. - * @type {Number} - */ - get constitutionBonus() { - let bonus = 1 - (this.stats.totals.con / 250); - return (bonus < 0.1) ? 0.1 : bonus; - } - - /** - * The quest the user is currently on, if any. - * @type {object} - */ - get quest() { - return (this.apiData.party.quest); - } - - /** - * Flag to check if user is on a quest. - * @type {Boolean} - */ - get isOnQuest() { - if (this.quest.data != null) { - return true; - } - return false; - } - - /** - * Flag to check if user is on a boss quest. - * @type {Boolean} - */ - get isOnBossQuest() { - if (this.isOnQuest && this.quest.data.boss != null) { - return true; - } - return false; - } - - set tasks(userTaskManager) { - this._taskManager = userTaskManager; - this.tasks.calculateDailyStatsFor(this); - } - /** - * The object managing this users tasks, if they have been loaded. - * @type {HabiticaUserTasksManager} - */ - get tasks() { - return this._taskManager; - } -} +/** + * A class representing the Habitica User. + * @class + * @param {object} data the json data object returned from the habitica API. + */ +class HabiticaUser { + constructor(data) { + this.apiData = data; + this.stats = this._calculateStats(); + } + /** + * The number of Subscriber Gems the user owns. + * @type {number} + */ + get gems() { + return this.apiData.balance * 4; + } + /** + * The number of Mystic Hourglasses the user owns. + * @type {number} + */ + get hourglasses() { + return this.apiData.purchased.plan.consecutive.trinkets; + } + /** + * The amount of Gold the user owns. + * @type {number} + */ + get gold() { + return Math.round(this.apiData.stats.gp); + } + /** + * The amount of Gold the user owns as a nicely formatted string. + * @type {number} + */ + get goldCompact() { + const formatter = new Intl.NumberFormat('lookup', { + notation: 'compact', + compactDisplay: 'short', + }); + return formatter.format(this.gold); + } + /** + * The experience level of the user. + * @type {number} + */ + get level() { + return this.apiData.stats.lvl; + } + /** + * The displayed name of the user. + * @type {string} + */ + get displayName() { + return this.apiData.profile.name; + } + /** + * The character class of the user. + * @type {string} + */ + get className() { + // the mage class is defined as "wizard" in the API, but referred + // to everywhere else in Habitica as mage, so we're gonna return mage + // here as that's what users on the front end would expect to see + // https://habitica.fandom.com/wiki/Guidance_for_Comrades#Class_Name_.28State_Mage_Instead_of_Wizard.29 + if (this.apiData.stats.class == "wizard") { + return "mage"; + } + return this.apiData.stats.class; + } + /** + * The bio of the user (may contain markdown formatting). + * @type {string} + */ + get bio() { + return this.apiData.profile.blurb; + } + /** + * The experience points the user has gained so far this level. + * @type {Number} + */ + get experience() { + return Math.floor(this.apiData.stats.exp); + } + /** + * The experience points needed to reach the next level. + * @type {Number} + */ + get experienceToLevel() { + return Math.round(this.apiData.stats.toNextLevel); + } + /** + * The amount of remaining Mana the user currently has. + * @type {Number} + */ + get mana() { + return Math.floor(this.apiData.stats.mp); + } + /** + * The maximum amount of mana the user can have. + * @type {Number} + */ + get manaMax() { + return Math.round(this.apiData.stats.maxMP); + } + /** + * The amount of remaining health the user currently has. + * @type {Number} + */ + get health() { + return Math.floor(this.apiData.stats.hp); + } + /** + * The maximum amount of health the user can have. + * @type {Number} + */ + get healthMax() { + return Math.round(this.apiData.stats.maxHealth); + } + /** + * The number of dailies this user can skip without taking damage. (Rogue Skill) + * @type {Number} + */ + get stealth() { + return this.apiData.stats.buffs.stealth; + } + /** + * The set of items this user has equipped. + * @type {object} + */ + get armor() { + return this.apiData.items.gear.equipped; + } + /** + * The set of costume items this user has equipped. + * @type {object} + */ + get costume() { + return this.apiData.items.gear.costume; + } + /** + * The visual set of items the user has on (either {@link HabiticaUser#armor|armor} + * or {@link HabiticaUser#costume|costume} depending on the users preferences). + * @type {object} + */ + get outfit() { + return this.apiData.preferences.costume == true ? this.costume : this.armor; + } + /** + * Flag to check if user is in the Inn. + * @type {Boolean} + */ + get isSleeping() { + return this.apiData.preferences.sleep; + } + + /** + * @private + */ + _calculateStats() { + var stats = { + totals: {str: 0, con: 0, int: 0, per: 0}, + armor: {str: 0, con: 0, int: 0, per: 0}, + buffs: { + str: this.apiData.stats.buffs.str, + con: this.apiData.stats.buffs.con, + int: this.apiData.stats.buffs.int, + per: this.apiData.stats.buffs.per + }, + points: { + str: this.apiData.stats.str, + con: this.apiData.stats.con, + int: this.apiData.stats.int, + per: this.apiData.stats.per + } + } + + // calculate armor stats from each piece of armor + for (var key in this.armor) { + let item = this.armor[key]; + for (var stat in stats.armor) { + stats.armor[stat] += item[stat]; + // apply class bonus if user is wearing special class gear + if (this.className === item.klass || + this.className === item.specialClass) { + stats.armor[stat] += .5 * item[stat]; + } + } + } + + // add up all stats for total, including level bonus + let levelBonus = Math.floor(this.level / 2); + for (var stat in stats.totals) { + stats.totals[stat] = stats.armor[stat] + + stats.buffs[stat] + + stats.points[stat] + + levelBonus; + } + + return stats; + } + + /** + * This user's constitution bonus against daily damage. + * @type {Number} + */ + get constitutionBonus() { + let bonus = 1 - (this.stats.totals.con / 250); + return (bonus < 0.1) ? 0.1 : bonus; + } + + /** + * The quest the user is currently on, if any. + * @type {object} + */ + get quest() { + return (this.apiData.party.quest); + } + + /** + * Flag to check if user is on a quest. + * @type {Boolean} + */ + get isOnQuest() { + if (this.quest.data != null) { + return true; + } + return false; + } + + /** + * Flag to check if user is on a boss quest. + * @type {Boolean} + */ + get isOnBossQuest() { + if (this.isOnQuest && this.quest.data.boss != null) { + return true; + } + return false; + } + + set tasks(userTaskManager) { + this._taskManager = userTaskManager; + this.tasks.calculateDailyStatsFor(this); + } + /** + * The object managing this users tasks, if they have been loaded. + * @type {HabiticaUserTasksManager} + */ + get tasks() { + return this._taskManager; + } +} diff --git a/src/HabiticaUserTasksManager.js b/src/HabiticaUserTasksManager.js index e8b9da9..3d1fcd8 100644 --- a/src/HabiticaUserTasksManager.js +++ b/src/HabiticaUserTasksManager.js @@ -1,129 +1,129 @@ -/** - * A class representing the list of tasks for a HabiticaUser. - * @param {Array} data - the list of tasks returned from the api call - */ -class HabiticaUserTasksManager { - constructor(data) { - /** - * The list of tasks. - * @type {Array} - */ - this.apiData = data; - } - - /** - * The list of tasks. - * @type {Array} - */ - get taskList() { - return this.apiData; - } - - /** - * The list of Todo tasks due by the end of today. - * @type {Array} - */ - get todosDueToday() { - return this.todosDueOnDate(moment().endOf('day')); - } - - /** - * The list of Todo tasks due by the end of a specific date. - * @param {Date} dueDate the date to compare tasks due dates to. - * @returns {Array} - */ - todosDueOnDate(dueDate) { - var todos = []; - - for (var i=0; i 0) { // avoided! - stealthRemaining --; - stats.dailiesEvaded ++; - } else { // calculate damage! - - stats.dueCount ++; - - var taskDamage = (task.value < min) ? min : task.value; - taskDamage = (taskDamage > max) ? max : taskDamage; - taskDamage = Math.abs(Math.pow(0.9747, taskDamage)); - - // if a subtask is completed, decrease the task damage proportionately - if (task.checklist.length > 0 ) { - var subtaskDamage = (taskDamage/task.checklist.length); - for (var j=0; j} data - the list of tasks returned from the api call + */ +class HabiticaUserTasksManager { + constructor(data) { + /** + * The list of tasks. + * @type {Array} + */ + this.apiData = data; + } + + /** + * The list of tasks. + * @type {Array} + */ + get taskList() { + return this.apiData; + } + + /** + * The list of Todo tasks due by the end of today. + * @type {Array} + */ + get todosDueToday() { + return this.todosDueOnDate(moment().endOf('day')); + } + + /** + * The list of Todo tasks due by the end of a specific date. + * @param {Date} dueDate the date to compare tasks due dates to. + * @returns {Array} + */ + todosDueOnDate(dueDate) { + var todos = []; + + for (var i=0; i 0) { // avoided! + stealthRemaining --; + stats.dailiesEvaded ++; + } else { // calculate damage! + + stats.dueCount ++; + + var taskDamage = (task.value < min) ? min : task.value; + taskDamage = (taskDamage > max) ? max : taskDamage; + taskDamage = Math.abs(Math.pow(0.9747, taskDamage)); + + // if a subtask is completed, decrease the task damage proportionately + if (task.checklist.length > 0 ) { + var subtaskDamage = (taskDamage/task.checklist.length); + for (var j=0; j