// 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 */ /* ========================================== */ /* ========================================== */ /* [Users] Optional customizations to fill in */ /* ========================================== */ // Do you want to get private message notifications if you've already entered Bad Day Mode? // 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 = "Bad Day Mode"; const HEADERS = { "x-client" : AUTHOR_ID + " - " + SCRIPT_NAME, "x-api-user" : USER_ID, "x-api-key" : API_TOKEN, } const BAD_DAY_MODE_TEXT = "**Bad Day Mode**"; const BAD_DAY_MODE_ALIAS = "badDayMode"; const BAD_DAY_MODE_NOTES = "Declare today a 'day of low expectations', fully healing you, buffing your CON to 9999, and Stealthing all your Dailies so you take no damage if you don't complete them. This wears off tomorrow at Cron."; const BAD_DAY_MODE_VALUE = "0"; const BAD_DAY_MODE_BUTTON = { "text": BAD_DAY_MODE_TEXT, "type": "reward", "alias": BAD_DAY_MODE_ALIAS, "notes": BAD_DAY_MODE_NOTES, "value": BAD_DAY_MODE_VALUE, } function doOneTimeSetup() { // These are not "else if" statements on purpose api_createNewTaskForUser([BAD_DAY_MODE_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); } // 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") { if (task.alias == BAD_DAY_MODE_ALIAS) { // Get user info const responseUser = api_getAuthenticatedUserProfile("stats,tasksOrder,items.gear.equipped"); user = JSON.parse(responseUser).data; // Get equipment stats info const responseContent = apiFree_getAllAvailableContentObjects(); content = JSON.parse(responseContent).data; var totalDailies = user.tasksOrder.dailys.length; var stealthedDailies = user.stats.buffs.stealth; if (!stealthedDailies) {stealthedDailies = 0}; var constitutionBuffs = user.stats.buffs.con; var unbuffedConstitution = calculateUnbuffedConstitution(); var hp = user.stats.hp; // Check if they're already in Bad Day Mode by seeing if CON is really high and all Dailies are Stealthed and HP is high if ( ( constitutionBuffs > 8000 ) && ( stealthedDailies >= totalDailies ) && ( hp >= 45 ) ) { api_sendPrivateMessage({"message" : "You are already in Bad Day Mode, so clicking the button additional times has no effect.", "toUserId" : USER_ID}); } else { // Full heal api_updateUser({"stats.hp" : 50}); // Set CON to 9999 var buffCon = 9999 - unbuffedConstitution; api_updateUser({"stats.buffs.con" : buffCon}); // Stealth all Dailies api_updateUser({"stats.buffs.stealth" : totalDailies}); } } } 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); } // 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); } function apiFree_getAllAvailableContentObjects() { const params = { "method" : "get", "muteHttpExceptions" : true, } const url = "https://habitica.com/api/v3/content"; 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; } } function calculateUnbuffedConstitution() { const levelConRaw = Math.floor(user.stats.lvl / 2); const levelCon = (levelConRaw > 50) ? 50 : levelConRaw; var totalEquipmentAndClassCon = 0; const allocatedCon = user.stats.con; // Get CON from equipped gear totalEquipmentAndClassCon += calcEquipmentAndClassCon(content.gear.flat[user.items.gear.equipped.weapon]); totalEquipmentAndClassCon += calcEquipmentAndClassCon(content.gear.flat[user.items.gear.equipped.shield]); totalEquipmentAndClassCon += calcEquipmentAndClassCon(content.gear.flat[user.items.gear.equipped.head]); totalEquipmentAndClassCon += calcEquipmentAndClassCon(content.gear.flat[user.items.gear.equipped.armor]); totalEquipmentAndClassCon += calcEquipmentAndClassCon(content.gear.flat[user.items.gear.equipped.headAccessory]); totalEquipmentAndClassCon += calcEquipmentAndClassCon(content.gear.flat[user.items.gear.equipped.eyewear]); totalEquipmentAndClassCon += calcEquipmentAndClassCon(content.gear.flat[user.items.gear.equipped.body]); totalEquipmentAndClassCon += calcEquipmentAndClassCon(content.gear.flat[user.items.gear.equipped.back]); return levelCon + totalEquipmentAndClassCon + allocatedCon; } function calcEquipmentAndClassCon(equipment) { var equipmentAndClassCon = 0; if (equipment != undefined) { equipmentAndClassCon += equipment.con; if ( (equipment.klass == user.stats.class) || ( (equipment.klass == "special") && (equipment.specialClass == user.stats.class) ) ) { equipmentAndClassCon += equipment.con / 2; } } return equipmentAndClassCon; } // 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); }