// This code is licensed under the same terms as Habitica: // https://raw.githubusercontent.com/HabitRPG/habitrpg/develop/LICENSE /* ========================================== */ /* [Users] Required script data to fill in */ /* ========================================== */ const USER_ID = "PasteYourUserIdHere"; const API_TOKEN = "PasteYourApiTokenHere"; // Do not share this to anyone const WEB_APP_URL = "PasteGeneratedWebAppUrlHere"; /* ========================================== */ /* [Users] Required customizations to fill in */ /* ========================================== */ const CREATE_DRAINING_POTION = 1; // Change this 1 to a 0 if you don't want the Draining Potion (you lose Mana) const CREATE_FATIGUING_POTION = 1; // Change this 1 to a 0 if you don't want the Fatiguing Potion (you lose Health) const CREATE_STUPEFYING_POTION = 1; // Change this 1 to a 0 if you don't want the Stupefying Potion (you lose Experience) /* ========================================== */ /* [Users] Optional customizations to fill in */ /* ========================================== */ // Do you want to get private message notifications? (examples include if you're already at max Mana or exceeded the limit for daily potion usage) // If you don't want them, change the 1 to a 0 in the line below const NOTIFICATIONS_ON = 1; /* ========================================== */ /* [Users] Do not edit code below this line */ /* ========================================== */ const AUTHOR_ID = "0034eb14-b4d8-494e-8386-d3f33cff7922"; const SCRIPT_NAME = "Anti-Potions"; const HEADERS = { "x-client" : AUTHOR_ID + " - " + SCRIPT_NAME, "x-api-user" : USER_ID, "x-api-key" : API_TOKEN, } const scriptProperties = PropertiesService.getScriptProperties(); // Constants can have properties changed const ANTI_MP_POTION_TEXT = "**Draining Potion** ![Orange-brown potion](https://raw.githubusercontent.com/Mike-Antonacci/Habitica-custom-potions/main/draining%20potion%20transparent%20large%20wide%20432.png)"; const ANTI_MP_POTION_ALIAS = "antiMPpotion"; const ANTI_MP_POTION_NOTES = "Pay 30 MP, gain 25 GP (Instant Use). Maximum 4 per day."; const ANTI_MP_POTION_VALUE = "0"; const ANTI_MP_POTION_LEVEL_LOCK = 11; const MAX_DAILY_ANTI_MP_POTION_USAGE = 4; const MSG_ANTI_MP_POTION_LEVEL_LOCK_FAIL = "You must be at least level 11 to use this potion."; const MSG_ANTI_MP_POTION_DAILY_USAGE_EXCEEDED = "You can only use this potion four times each day."; const MSG_INSUFFICIENT_MP = "Insufficient Mana to use this potion." const ANTI_HP_POTION_TEXT = "**Fatiguing Potion** ![Teal potion](https://raw.githubusercontent.com/Mike-Antonacci/Habitica-custom-potions/main/fatiguing%20potion%20transparent%20large%20wide%20432.png)"; const ANTI_HP_POTION_ALIAS = "antiHPpotion"; const ANTI_HP_POTION_NOTES = "Pay 15 HP, gain 25 GP (Instant Use). Maximum 4 per day."; const ANTI_HP_POTION_VALUE = "0"; const MAX_DAILY_ANTI_HP_POTION_USAGE = 4; const MSG_ANTI_HP_POTION_DAILY_USAGE_EXCEEDED = "You can only use this potion four times each day."; const MSG_INSUFFICIENT_HP = "Insufficient Health to use this potion." const ANTI_XP_POTION_TEXT = "**Stupefying Potion** ![Indigo potion](https://raw.githubusercontent.com/Mike-Antonacci/Habitica-custom-potions/main/stupefying%20potion%20transparent%20large%20wide%20432.png)"; const ANTI_XP_POTION_ALIAS = "antiXPpotion"; const ANTI_XP_POTION_NOTES = "Pay 150 XP, gain 25 GP (Instant Use). Maximum 4 per day."; const ANTI_XP_POTION_VALUE = "0"; const ANTI_XP_POTION_LEVEL_LOCK = 16; const MAX_DAILY_ANTI_XP_POTION_USAGE = 4; const MSG_ANTI_XP_POTION_LEVEL_LOCK_FAIL = "You must be at least level 16 to use this potion."; const MSG_ANTI_XP_POTION_DAILY_USAGE_EXCEEDED = "You can only use this potion four times each day."; const MSG_INSUFFICIENT_XP = "Insufficient Experience to use this potion." const ANTI_MP_POTION_BUTTON = { "text": ANTI_MP_POTION_TEXT, "type": "reward", "alias": ANTI_MP_POTION_ALIAS, "notes": ANTI_MP_POTION_NOTES, "value": ANTI_MP_POTION_VALUE, } const ANTI_HP_POTION_BUTTON = { "text": ANTI_HP_POTION_TEXT, "type": "reward", "alias": ANTI_HP_POTION_ALIAS, "notes": ANTI_HP_POTION_NOTES, "value": ANTI_HP_POTION_VALUE, } const ANTI_XP_POTION_BUTTON = { "text": ANTI_XP_POTION_TEXT, "type": "reward", "alias": ANTI_XP_POTION_ALIAS, "notes": ANTI_XP_POTION_NOTES, "value": ANTI_XP_POTION_VALUE, } const CRON_COUNT_KEY = "CRON_COUNT_KEY"; const ANTI_MP_USAGE_KEY = "ANTI_MP_USAGE_KEY"; const ANTI_HP_USAGE_KEY = "ANTI_HP_USAGE_KEY"; const ANTI_XP_USAGE_KEY = "ANTI_XP_USAGE_KEY"; const TIMESTAMP_KEY = "TIMESTAMP_KEY"; var cronCountKey = ""; var AntiMpUsageKey = ""; var AntiHpUsageKey = ""; var AntiXpUsageKey = ""; var timestampKey = ""; function doOneTimeSetup() { if ((CREATE_DRAINING_POTION == 1) && (CREATE_FATIGUING_POTION == 1) && (CREATE_STUPEFYING_POTION == 1)) { api_createNewTaskForUser([ANTI_XP_POTION_BUTTON, ANTI_HP_POTION_BUTTON, ANTI_MP_POTION_BUTTON]); } else if ((CREATE_DRAINING_POTION == 1) && (CREATE_FATIGUING_POTION == 1) && (CREATE_STUPEFYING_POTION == 0)) { api_createNewTaskForUser([ANTI_HP_POTION_BUTTON, ANTI_MP_POTION_BUTTON]); } else if ((CREATE_DRAINING_POTION == 1) && (CREATE_FATIGUING_POTION == 0) && (CREATE_STUPEFYING_POTION == 1)) { api_createNewTaskForUser([ANTI_XP_POTION_BUTTON, ANTI_MP_POTION_BUTTON]); } else if ((CREATE_DRAINING_POTION == 0) && (CREATE_FATIGUING_POTION == 1) && (CREATE_STUPEFYING_POTION == 1)) { api_createNewTaskForUser([ANTI_XP_POTION_BUTTON, ANTI_HP_POTION_BUTTON]); } else if ((CREATE_DRAINING_POTION == 1) && (CREATE_FATIGUING_POTION == 0) && (CREATE_STUPEFYING_POTION == 0)) { api_createNewTaskForUser([ANTI_MP_POTION_BUTTON]); } else if ((CREATE_DRAINING_POTION == 0) && (CREATE_FATIGUING_POTION == 1) && (CREATE_STUPEFYING_POTION == 0)) { api_createNewTaskForUser([ANTI_HP_POTION_BUTTON]); } else if ((CREATE_DRAINING_POTION == 0) && (CREATE_FATIGUING_POTION == 0) && (CREATE_STUPEFYING_POTION == 1)) { api_createNewTaskForUser([ANTI_XP_POTION_BUTTON]); } // Next, create the webhook const options = { "scored" : true, } const payload = { "url" : WEB_APP_URL, "label" : SCRIPT_NAME + " Webhook", "type" : "taskActivity", "options" : options, } apiMult_createNewWebhookNoDuplicates(payload); // set script properties so they carry over to next session initScriptProperties(); } // do things when the webhook runs function doPost(e) { const dataContents = JSON.parse(e.postData.contents); const type = dataContents.type; const task = dataContents.task; // Sanitize task alias if ((task.alias == undefined) || (task.alias == null)) { task.alias = ""; } if ( (type == "scored") && ( (task.alias == ANTI_MP_POTION_ALIAS) || (task.alias == ANTI_HP_POTION_ALIAS) || (task.alias == ANTI_XP_POTION_ALIAS) ) ) { var timestampKey = TIMESTAMP_KEY; var timeStart = Number(scriptProperties.getProperty(timestampKey)); if ( (timeStart == 0) || (timeStart == "") || (timeStart == undefined) || (timeStart == null) ) { timeStart = Date.now(); } var timeEnd = Date.now(); // Rate limiting: If it's been less than 30 seconds since they last clicked one of these buttons, do nothing if ( (timeEnd - timeStart) >= 30000 ) { // Run all the things that run regardless of the potion const response = api_getAuthenticatedUserProfile("stats"); user = JSON.parse(response).data; var cronCountKey = CRON_COUNT_KEY; var AntiMpUsageKey = ANTI_MP_USAGE_KEY; var AntiHpUsageKey = ANTI_HP_USAGE_KEY; var AntiXpUsageKey = ANTI_XP_USAGE_KEY; var cronCount = Number(scriptProperties.getProperty(cronCountKey)); var AntiMpUsage = Number(scriptProperties.getProperty(AntiMpUsageKey)); var AntiHpUsage = Number(scriptProperties.getProperty(AntiHpUsageKey)); var AntiXpUsage = Number(scriptProperties.getProperty(AntiXpUsageKey)); var hp = user.stats.hp; var exp = user.stats.exp; var mp = user.stats.mp; var gp = user.stats.gp; var lvl = user.stats.lvl; // If they've Cronned, reset all counters if (cronCount != user.flags.cronCount) { cronCount = user.flags.cronCount; AntiMpUsage = 0; AntiHpUsage = 0; AntiXpUsage = 0; } // If it was the Anti MP Potion alias, do Anti MP Potion (Draining Potion) things if (task.alias == ANTI_MP_POTION_ALIAS){ // If level lock fails, send failure message if (lvl < ANTI_MP_POTION_LEVEL_LOCK) { api_sendPrivateMessage({"message" : MSG_ANTI_MP_POTION_LEVEL_LOCK_FAIL, "toUserId" : USER_ID}); } else { // Check if they've exceeded daily usage. If yes, send failure message. if (AntiMpUsage >= MAX_DAILY_ANTI_MP_POTION_USAGE) { api_sendPrivateMessage({"message" : MSG_ANTI_MP_POTION_DAILY_USAGE_EXCEEDED, "toUserId" : USER_ID}); } else { // Check if they have sufficient Mana. Send failure message if not. if (mp < 30) { api_sendPrivateMessage({"message" : MSG_INSUFFICIENT_MP, "toUserId" : USER_ID}); } else { // Run potion as normal since all checks passed. api_updateUser({"stats.hp" : hp, "stats.mp" : mp - 30, "stats.exp" : exp, "stats.gp" : gp + 25}); AntiMpUsage ++; } } } } // If it was the Anti HP Potion alias, do Anti HP Potion (Fatiguing Potion) things else if (task.alias == ANTI_HP_POTION_ALIAS) { // Check if they've exceeded daily usage. If yes, send failure message. if (AntiHpUsage >= MAX_DAILY_ANTI_HP_POTION_USAGE) { api_sendPrivateMessage({"message" : MSG_ANTI_HP_POTION_DAILY_USAGE_EXCEEDED, "toUserId" : USER_ID}); } else { // Check if they have sufficient Health. Send failure message if not. if (hp < 15) { api_sendPrivateMessage({"message" : MSG_INSUFFICIENT_HP, "toUserId" : USER_ID}); } else { // Run potion as normal since all checks passed. api_updateUser({"stats.hp" : hp - 15, "stats.mp" : mp, "stats.exp" : exp, "stats.gp" : gp + 25}); AntiHpUsage ++; } } } // If it was the Anti XP Potion alias, do Anti XP Potion (Stupefying Potion) things else if (task.alias == ANTI_XP_POTION_ALIAS) { // If level lock fails, send failure message if (lvl < ANTI_XP_POTION_LEVEL_LOCK) { api_sendPrivateMessage({"message" : MSG_ANTI_XP_POTION_LEVEL_LOCK_FAIL, "toUserId" : USER_ID}); } else { // Check if they've exceeded daily usage. If yes, send failure message. if (AntiXpUsage >= MAX_DAILY_ANTI_XP_POTION_USAGE) { api_sendPrivateMessage({"message" : MSG_ANTI_XP_POTION_DAILY_USAGE_EXCEEDED, "toUserId" : USER_ID}); } else { // Check if they have sufficient Experience. Send failure message if not. if (exp < 150) { api_sendPrivateMessage({"message" : MSG_INSUFFICIENT_XP, "toUserId" : USER_ID}); } else { // Run potion as normal since all checks passed. api_updateUser({"stats.hp" : hp, "stats.mp" : mp, "stats.exp" : exp - 150, "stats.gp" : gp + 25}); AntiXpUsage ++; } } } } // Save values to non-volatile memory, but only from within the rate-limited if-block (we don't want to save the timestamp unless the script ran) timeStart = timeEnd; scriptProperties.setProperty(cronCountKey, cronCount); scriptProperties.setProperty(AntiMpUsageKey, AntiMpUsage); scriptProperties.setProperty(AntiHpUsageKey, AntiHpUsage); scriptProperties.setProperty(AntiXpUsageKey, AntiXpUsage); scriptProperties.setProperty(timestampKey, timeStart); } } return HtmlService.createHtmlOutput(); } // Create custom reward buttons function api_createNewTaskForUser(payload) { var params = { "method" : "post", "headers" : HEADERS, "contentType" : "application/json", "payload" : JSON.stringify(payload), // Rightmost button goes on top "muteHttpExceptions" : true, } var url = "https://habitica.com/api/v3/tasks/user"; UrlFetchApp.fetch(url, params); } // Create a webhook if no duplicate exists function apiMult_createNewWebhookNoDuplicates(payload) { const response = api_getWebhooks(); const webhooks = JSON.parse(response).data; var duplicateExists = 0; for (var i in webhooks) { if (webhooks[i].label == payload.label) { duplicateExists = 1; } } // If webhook to be created doesn't exist yet if (!duplicateExists) { api_createNewWebhook(payload); } } // Used to see existing webhooks, and therefore if there's a duplicate function api_getWebhooks() { const params = { "method" : "get", "headers" : HEADERS, "muteHttpExceptions" : true, } const url = "https://habitica.com/api/v3/user/webhook"; return UrlFetchApp.fetch(url, params); } // Creates a webhook (as part of the "don't make it if there's a duplicate" function) function api_createNewWebhook(payload) { const params = { "method" : "post", "headers" : HEADERS, "contentType" : "application/json", "payload" : JSON.stringify(payload), "muteHttpExceptions" : true, } const url = "https://habitica.com/api/v3/user/webhook"; return UrlFetchApp.fetch(url, params); } // Sets initial properties that will be used/saved later. function initScriptProperties() { var timestampInit = Date.now(); scriptProperties.setProperty(CRON_COUNT_KEY, 0); scriptProperties.setProperty(ANTI_MP_USAGE_KEY, 0); scriptProperties.setProperty(ANTI_HP_USAGE_KEY, 0); scriptProperties.setProperty(ANTI_XP_USAGE_KEY, 0); scriptProperties.setProperty(TIMESTAMP_KEY, timestampInit); } // Gets user info so I can use it, especially stats like mana, experience, and level function api_getAuthenticatedUserProfile(userFields) { const params = { "method" : "get", "headers" : HEADERS, "muteHttpExceptions" : true, } var url = "https://habitica.com/api/v3/user"; if (userFields != "") { url += "?userFields=" + userFields; } return UrlFetchApp.fetch(url, params); } // Changes stats function api_updateUser(payload) { const params = { "method" : "put", "headers" : HEADERS, "contentType" : "application/json", "payload" : JSON.stringify(payload), "muteHttpExceptions" : true, } const url = "https://habitica.com/api/v3/user"; return UrlFetchApp.fetch(url, params); } // Send a notification as a private message, only if they're enabled function api_sendPrivateMessage(payload) { switch (NOTIFICATIONS_ON){ // Check if notifications are on, send message if yes case 0: break; case 1: const params = { "method" : "post", "headers" : HEADERS, "contentType" : "application/json", "payload" : JSON.stringify(payload), "muteHttpExceptions" : true, } const url = "https://habitica.com/api/v3/members/send-private-message"; return UrlFetchApp.fetch(url, params); break; } }