diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3e5c25d7..f66979ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,6 +8,7 @@ on: push: paths: - Parsers/** + - Interaction/** # branches: [ "main" ] # pull_request: # branches: [ "main" ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0095156c..0e304820 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,35 +1,39 @@ # Contributing -## General requirements - -- API usage should follow the information found in the [readme](README.md#available-apisvariables-in-parsers) -- Pull request descriptions must be explicit and descriptive to what is being changed. - - Changes that are not within the scope of the description will result in the entire PR being rejected -- No updates should be made directly to the automatically generated ServiceNow folders. Updates to these files should directly to the app after being imported into a ServiceNow instance. -- Parser additions/updates must follow the [Parser template](#required-parser-template) below. -- Low effort/spam Pull Requests will be marked as spam accordingly. -- Filenames should not have special characters that are not allowed on normal file systems (eg. do not put ! in the file name). -- Parsers should not be spammy or activated unintentionally by a user in a way that would be deemed spammy. For example, a parser that is activated by a common word would likely be rejected. Instead, use a `!` notation as this is widely accepted as an activation character (eg. `!question` instead of just `question` as an activation phrase) -- We want the SlackerBot to be as close to base functionality as possible. The app (in ServiceNow) should not be updated with script includes that pertain to individual parsers, and such functionality should be entirely within a parser file so that users can choose which parsers they want to use without having extra script includes included. The only script includes that should be added to the app is if it's adding direct functionality to the bot itself (eg. the ability for the bot to parse emojis, or to send ephemeral messages, or accept blocks messages, etc.). +We appreciate your interest in contributing to the project! Please adhere to the following guidelines to ensure a smooth contribution process. + +## General Requirements + +- **API Usage**: Follow the guidelines outlined in the [README](README.md#available-apisvariables-in-parsers) regarding available APIs and variables in parsers. +- **Descriptive Pull Requests**: Ensure your pull request descriptions are explicit and clearly articulate the changes made. + - Pull requests that deviate from the scope outlined in the description may be rejected. +- **Automated Folders**: Do not make updates directly to the automatically generated ServiceNow folders. Any changes to these files should be made directly in the application after importing it into a ServiceNow instance. +- **Parser Guidelines**: Additions or updates to parsers must conform to the [required parser template](#required-parser-template) outlined below. +- **Quality Control**: Low-effort or spam pull requests will be marked as spam. +- **File Naming Conventions**: Avoid using special characters that are not allowed in standard file systems (e.g., do not include `!` in filenames). +- **Activation Phrase**: Parsers should not be inadvertently triggered in a spammy manner. For example, using common words as activation phrases may lead to rejection. Instead, use a `!` notation as an activation character (e.g., `!question` instead of just `question`). +- **Modular Functionality**: We aim to keep SlackerBot's functionality as streamlined as possible. Do not include script includes pertaining to individual parsers within the app. Any such functionality should reside entirely within the parser file, allowing users to choose which parsers to activate without unnecessary script includes. The only script includes that should be added to the app are those providing direct functionality to the bot (e.g., parsing emojis, sending ephemeral messages, or handling block messages). ## Required Parser Template -### For chat parsers +### For Chat Parsers -Eg. Run a script when a user says ____ +Example: Run a script when a user says ____ + +**Note**: Lines containing key-value pairs should have no leading spaces before the value. For example, `regex:!test` is correct, while `regex: !test` is incorrect and may cause errors. - Line 1 must always be `/*` -- Line 2 must always be `activation_example:` followed by a short description of how this parser would be activate -- Line 3 must always be `regex:` followed by a regex expression that validates if the parser should run. Do not include the opening and closing `/` -- Line 4 must always be `flags:` followed by the regex flags needed for line 3. If no flags are needed then leave the line as `flags:` -- *Optional*: `order:` that the parser should run (lower orders run first, null/empty orders run last). -- *Optional*: `stop_processing:` if set to `true`, will stop the parser from running any other parsers after this one. -- The file header must always end with `*/` -- The rest of the file should be the JavaScript (ES5) that does your desired parsing. +- Line 2 must always be `activation_example:` followed by a short description of how this parser would be activated. +- Line 3 must always be `regex:` followed by a regex expression that validates when the parser should run. Do not include the opening and closing `/`. +- Line 4 must always be `flags:` followed by the regex flags needed for Line 3. If no flags are required, leave the line as `flags:`. +- *Optional*: `order:` that specifies the order in which the parser should run (lower orders run first; null or empty orders run last). +- *Optional*: `stop_processing:` if set to `true`, will prevent any subsequent parsers from executing after this one. +- The file header must always end with `*/`. +- The remaining content of the file should be valid JavaScript (ES5) that implements the desired parsing logic. -Example acceptable chat parser file (From [Clap back.js](Parsers/Clap%20back.js)): +**Example of an Acceptable Chat Parser File (from [Clap back.js](Parsers/Clap%20back.js))**: -```js +```javascript /* activation_example:!clap your sentence regex:!clap @@ -40,29 +44,29 @@ stop_processing:false var sentence = current.text.replace(/!clap/gmi, "").trim().toUpperCase(); if (sentence == '') { - new x_snc_slackerbot.Slacker().send_chat(current, ':upside_down_face: gimme something to clap!', true); + new x_snc_slackerbot.Slacker().send_chat(current, ':upside_down_face: gimme something to clap!', true); } else { - new x_snc_slackerbot.Slacker().send_chat(current, sentence.split(' ').join(' :clap: '), false); + new x_snc_slackerbot.Slacker().send_chat(current, sentence.split(' ').join(' :clap: '), false); } ``` -### For reaction-added parsers +### For Reaction-Added Parsers -Eg. Run a script when a user adds a specific emoji as a reaction +Example: Run a script when a user adds a specific emoji as a reaction. -- Line 1 must always be /* -- Line 2 must always be `emoji:` followed by a comma separated list of emojis that will activate this parser -- Line 3 must always be */ -- The rest of the file should be the JavaScript (ES5) that does your desired parsing. +- Line 1 must always be `/*` +- Line 2 must always be `emoji:` followed by a comma-separated list of emojis that will activate this parser. +- Line 3 must always be `*/` +- The remaining content of the file should be valid JavaScript (ES5) that implements the desired parsing logic. -Example acceptable react parser file (From [Parrot wave starter.js](Parsers/Parrot%20wave%20starter.js) +**Example of an Acceptable Reaction Parser File (from [Parrot wave starter.js](Parsers/Parrot%20wave%20starter.js))**: -```js +```javascript /* emoji:parrotwave1 */ -for (var i = 2; i <= 7; i++){ - new x_snc_slackerbot.Slacker().send_reaction(current, 'parrotwave' + i); +for (var i = 2; i <= 7; i++) { + new x_snc_slackerbot.Slacker().send_reaction(current, 'parrotwave' + i); } ``` diff --git a/Interaction/Handle an SN quiz.js b/Interaction/Handle an SN quiz.js new file mode 100644 index 00000000..e3300bd8 --- /dev/null +++ b/Interaction/Handle an SN quiz.js @@ -0,0 +1,69 @@ +/* +action_id_regex:quiz_answer_ +parsers:Create a new SN Quiz +*/ + +(function (current) { + var slackerProvider = new x_snc_slackerbot.Slacker(); + var interactionPayload = JSON.parse(current.getValue('payload')); + + // Get selected option, correct option, the user ID that initiated the quiz, and explanation + var action = interactionPayload.actions[0]; + var selectedOption = action.value; + var parts = selectedOption.split("|"); + var userSelection = parts[0]; + var correctOption = parts[1]; + var originalUserId = parts[2]; + var explanation = parts[3]; + + // Verify if the interacting user is the same as the original user + if (interactionPayload.user.id !== originalUserId) return; + + // Apply styles to each button based on whether it was selected and whether it's correct + var blocks = interactionPayload.message.blocks; + var correctIndex = parseInt(correctOption.split('_')[1]) - 1; + + blocks[1].elements.forEach(function (button, index) { + if (userSelection === correctOption) { + button.style = (index === correctIndex) ? "primary" : "danger"; + } else { + if (button.value === selectedOption) { + button.style = "danger"; + } + } + }); + + var updateMessagePayload = { + "channel": interactionPayload.channel.id, + "ts": interactionPayload.message.ts, + "blocks": blocks, + "text": "UpdatingQuiz" + }; + + var responseMessageUpdated = slackerProvider.update_chat(updateMessagePayload); + + // Validate User Answer and send appropriate feedback + var current = { + "text": interactionPayload.message.blocks[0].text.text, + "ts": interactionPayload.message.ts, + "thread_ts": interactionPayload.container.message_ts, + "channel": interactionPayload.channel.id, + "user": { + "user_id": interactionPayload.user.id, + "name": interactionPayload.user.name + } + }; + + var questionText = interactionPayload.message.blocks[0].text.text; + var textLines = questionText.split("\n"); + var correctAnswerText = textLines[correctIndex + 4].trim(); + var correctAnswer = correctAnswerText.replace(/^[A-D]\)\s*/, ''); + + if (userSelection === correctOption) { + var correctMessage = "Well done! The correct answer is:\n*" + correctAnswer + "*\n\n" + explanation; + slackerProvider.send_chat(current, correctMessage, false); + } else { + var incorrectMessage = "Oops, not quite! Try Again."; + slackerProvider.send_chat(current, incorrectMessage, false); + } +})(current); diff --git a/Parsers/Admin tool - Verify.js b/Parsers/Admin tool - Verify.js index 3cba95be..a5d0ae58 100644 --- a/Parsers/Admin tool - Verify.js +++ b/Parsers/Admin tool - Verify.js @@ -1,35 +1,88 @@ /* -activation_example:!verify-admin @Astrid -regex:^!verify-admin\b +activation_example:!verify-admin @Astrid, !verify-admin -help +regex:^!(?:un)?verify-admin\u0020? flags:gi */ -if (current.channel == "GD51HTR46" || current.channel == "G9LAJG7G8" || current.channel == "G7M4AP6U8") { //admin channels on sndevs - var message_body = ''; - var verification_status = false; +if ( + current.channel == "GD51HTR46" || + current.channel == "G9LAJG7G8" || + current.channel == "G7M4AP6U8" +) { + //admin channels on sndevs + var messageBody = ""; + var userId = ""; + var description = ""; + var verificationStatus = null; + var slacker = new x_snc_slackerbot.Slacker(); // Grab user ID and then prep invocation if User-visible info provided - var invocation = current.text; - var user_id = invocation.replace('!verify-admin <@', ''); - user_id = user_id.replace(/>.*/, '').trim(); - invocation = invocation.replace('!verify-admin <@' + user_id + '>','').trim(); - - var grUser = new GlideRecord('x_snc_slackerbot_user'); - if(grUser.get('user_id',user_id) && Object.keys(grUser).indexOf('verified') != -1){ - if(invocation.length == 0){ - verification_status = !(grUser.getValue('verified') == 1 ? true : false); // Get verified and toggle - grUser.setValue('verified',verification_status); - message_body += 'Updated <@' + user_id + '>\'s verification status. They are now ' + (verification_status ? 'verified.' : 'unverified. To add a user-visible note instead of toggling verification, add text after `!verify-admin @User `. Example: `!verify-admin @Astrid Is upside down`'); + var invocation = current.text + .replace(/^!(?:un)?verify-admin\u0020?/gi, "") + .trim(); + var paramArr = invocation.split(" "); + + if (paramArr.length == 0) { + messageBody = + "!verify-admin must be called with a user tag, followed by an optional parameter and optional description. For example: `!verify-admin @Astrid -unv Is an Impasta`\n\nThe full list of accepted triggers can be found by sending `!verify-admin -help`"; + } + + if (paramArr.length == 1 && paramArr[0] == "-help") { + messageBody = + "Admin Tool - Verify user\nA parser for setting user verification and descriptions. *Note:* This parser can only be triggered from admin channels specified.\n\nUsage: `!verify-admin @username [-v|-unv] [message]`\nExamples:\n`!verify-admin @Astrid -unv Is an Impasta`\n`!verify-admin @Astrid Some Role, Some Company`\n`!verify-admin @Astrid -v`\n\nSupported flags:\n-v: Verify the user\n-unv: Remove verification from user\n-help: Show this message"; + } + + if (/^<@.*?>$/.test(paramArr[0])) { + messageBody = paramArr[0]; + userId = paramArr[0].replace(/[<>@]/g, ""); + paramArr.shift(); } else { - grUser.setValue('user_info', invocation); + messageBody = + "!verify-admin must be called with a user tag, followed by an optional parameter and optional description. For example: `!verify-admin @Astrid -unv Is an Impasta`\n\nThe full list of accepted triggers can be found by sending `!verify-admin -help`"; + slacker.send_chat(current, messageBody, true); + } + + if (paramArr.indexOf("-v") > -1 && paramArr.indexOf("-unv") > -1) { + messageBody = + "Please only provide one verify parameter in your message. Verification message ignored."; + slacker.send_chat(current, messageBody, true); + } else { + if (/^!unverify/.test(current.text)) { + verificationStatus = false; + } else if (paramArr.indexOf("-v") > -1) { + verificationStatus = true; + } else if (paramArr.indexOf("-unv") > -1) { + verificationStatus = false; + } else if (verificationStatus == null) { + verificationStatus = true; + } + + description = paramArr.join(" "); + var grUser = new GlideRecord("x_snc_slackerbot_user"); + if (grUser.get("user_id", userId)) { + verificationStatus != null + ? grUser.setValue("verified", verificationStatus) + : grUser.getValue("verified"); + if (description.length > 0) { + grUser.setValue("user_info", description); + } else { + description = grUser.getValue("user_info"); + } + grUser.update(); - message_body += 'Updated <@' + user_id + '>\'s user info message.'; + + messageBody = + "Updated <@" + + userId + + ">'s verification information with the following info:\n"; + messageBody += + "Verification status: " + + (verificationStatus ? "*Verified*" : "*Unverified*") + + "\n"; + messageBody += "User information:\n>" + description; + } else { + messageBody += `I'm afraid I can't do that as the ~limit~ user (<@${userId}>) does not exist.`; } - grUser.update(); - } - else { - message_body += 'I\'m afraid I can\'t do that. Either the user does not exist, or the logic to support this functionality is not yet on the instance.'; + slacker.send_chat(current, messageBody, true); } - - var send_chat = new x_snc_slackerbot.Slacker().send_chat(current, message_body, true); } diff --git a/Parsers/Admin tool - Whois.js b/Parsers/Admin tool - Whois.js index fa531d08..9f03d7dd 100644 --- a/Parsers/Admin tool - Whois.js +++ b/Parsers/Admin tool - Whois.js @@ -5,54 +5,74 @@ flags:gmi */ if (current.channel == "GD51HTR46" || current.channel == "G9LAJG7G8" || current.channel == "G7M4AP6U8") { //admin channels on sndevs - var rm = new sn_ws.RESTMessageV2(); - rm.setHttpMethod('GET'); - rm.setLogLevel('all'); - rm.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - rm.setRequestHeader('authorization', 'Bearer '+gs.urlEncode(gs.getProperty('x_snc_slackerbot.SlackerBot.token'))); - var bodyString = ''; -// bodyString += 'token=' + gs.urlEncode(gs.getProperty('x_snc_slackerbot.SlackerBot.token')); - var user_id = current.text; - user_id = user_id.replace('!whois <@', ''); - user_id = user_id.replace('>', '').trim(); - bodyString += 'user=' + gs.urlEncode(user_id); - rm.setEndpoint('https://slack.com/api/users.info'+'?'+bodyString); - var response = rm.execute(); - var response_body = JSON.parse(response.getBody()); + // Update desired keys if you want to change returned fields from payload + const desiredKeys = ['user.real_name', 'user.profile.pronouns', 'user.profile.email', 'user.tz_label', 'user.updated']; + const slacker = new x_snc_slackerbot.Slacker(); + let userId = current.text; + userId = userId.replace('!whois <@', ''); + userId = gs.urlEncode(userId.replace('>', '').trim()); - var message_body = ''; - for (var key in response_body.user){ - if (key == 'profile') { - message_body += 'profile: \n'; - for (var prof_key in response_body.user.profile) { - if (prof_key.indexOf('image_') != -1) continue; - if (prof_key == 'status_emoji_display_info'){ - message_body += ' status_emoji_display_info: \n'; - for (var stat_key in response_body.user.profile.status_emoji_display_info){ - message_body += ' ' + stat_key + ': ' + response_body.user.profile.status_emoji_display_info[stat_key] + '\n'; - } - } else message_body += ' ' + prof_key + ': ' + response_body.user.profile[prof_key] + '\n'; - } - } else message_body += key + ': ' + response_body.user[key] + '\n'; - } + const rm = new sn_ws.RESTMessageV2(); + rm.setHttpMethod('GET'); + rm.setLogLevel('all'); + rm.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + rm.setRequestHeader('authorization', `Bearer ${gs.urlEncode(gs.getProperty('x_snc_slackerbot.SlackerBot.token'))}`); + rm.setEndpoint(`https://slack.com/api/users.info?user=${userId}`); + const response = rm.execute(); + const responseBody = JSON.parse(response.getBody()); - message_body = message_body.match(/^(real_name|tz_label| {2}email):.*$/gm).join('\n'); + if (!responseBody.ok) { + slacker.send_chat(current, 'i dunno', false); + } else { + const messageArray = []; + let messageBody = ''; - // Add record details - var grUser = new GlideRecord('x_snc_slackerbot_user'); - if(grUser.get('user_id',user_id) && Object.keys(grUser).indexOf('verified') != -1){ // Backwards support until instance updates with new schema - message_body += '\nIdentity verified: ' + (grUser.getValue('verified') == 1 ? 'Yes' : 'No') + '\n'; - if(!gs.nil(grUser.getValue('admin_info'))){ - message_body += 'Admin information: ' + grUser.getValue('admin_info') + '\n'; - } - if(!gs.nil(grUser.getValue('user_info'))){ - message_body += 'User-visible information: ' + grUser.getValue('user_info') + '\n'; - } - } + for (let key of desiredKeys) { + let path = key.split('.'); + let obj = {}; + switch (path.length) { + case 1: + obj[path[0]] = responseBody[path[0]] ?? '{Not set}'; + break; + case 2: + obj[path[1]] = responseBody[path[0]][path[1]] ?? '{Not set}'; + break; + case 3: + obj[path[2]] = responseBody[path[0]][path[1]][path[2]] ?? '{Not set}'; + break; + default: + gs.warn(`Whois parser encountered unexpected path length for a key. Key is ${key}`); + } - if (response_body.user.name){ - var send_chat = new x_snc_slackerbot.Slacker().send_chat(current, '```' + message_body + '```', false); - } else { - var send_chat2 = new x_snc_slackerbot.Slacker().send_chat(current, 'i dunno', false); - } + // Handle conversions + if (path[1] == 'updated') { + let time = new GlideDateTime(); + time.setValue(responseBody[path[0]][path[1]] * 1000); + obj[path[1]] = `${time.getDisplayValue()} (${gs.getSession().getTimeZoneName()})`; + } + messageArray.push(obj); + } + + // Get verify data + const grUser = new GlideRecord('x_snc_slackerbot_user'); + if (grUser.get('user_id', userId)) { + messageArray.push({ + 'Identity verified': grUser.getValue('verified') == 1 ? 'Yes' : 'No' + }); + if (!gs.nil(grUser.getValue('admin_info'))) { + messageArray.push({ + 'Admin information': grUser.getValue('admin_info') + }); + } + if (!gs.nil(grUser.getValue('user_info'))) { + messageArray.push({ + 'User-visible information': grUser.getValue('user_info') + }); + } + } + + messageBody = messageArray.map(obj => `${Object.keys(obj)[0]}: ${Object.values(obj)[0]}`).join('\n'); + + slacker.send_chat(current, '```' + messageBody + '```', false); + } } diff --git a/Parsers/Animal Facts.js b/Parsers/Animal Facts.js index 3de02aac..1ed2aae4 100644 --- a/Parsers/Animal Facts.js +++ b/Parsers/Animal Facts.js @@ -6,6 +6,7 @@ flags:gmi var where = current.text.indexOf('!animal ') + 8; var term = current.text.substr(where).trim(); +term = gs.urlEncode(term); var chatReq = new sn_ws.RESTMessageV2(); chatReq.setEndpoint('https://api.api-ninjas.com/v1/animals?name=' + term); chatReq.setHttpMethod("GET"); @@ -65,4 +66,4 @@ if (Array.isArray(responseData) && responseData.length > 0) { } } else { new x_snc_slackerbot.Slacker().send_chat(current, "No fun facts for the provided animal.", false); -} \ No newline at end of file +} diff --git a/Parsers/App Update Script.js b/Parsers/App Update Script.js new file mode 100644 index 00000000..53c2304d --- /dev/null +++ b/Parsers/App Update Script.js @@ -0,0 +1,134 @@ +/* +activation_example:!appupdate +regex:(!appupdate|!update|!app-update|!bulkupdate|!bulk-update|!plugin|!plugins) +flags:gmi +*/ + +// NOTE! +// When you update eric_script, you must find/replace \n with \\n and ` with \` + +const eric_script = ` +/*----------------------------------------------------*/ +/* AUTO */ +/* Have a bunch of apps that need to be updated? */ +/* Run this and follow the directions in the output */ +/* It will automaticallybuild and run a batch */ +/* install of all of the needed updates. */ +/* */ +/* Latest code always available at */ +/* https://snwizard.com/update-apps */ +/* */ +/*----------------------------------------------------*/ + +//Want Demo Data with the app? +var loadDemoData = true; +var updateCheck = false; //this can take some time to run and adds a LOT of stuff to the log making the important bit harder to find + +if (updateCheck) new sn_appclient.UpdateChecker().checkAvailableUpdates(); + +var prevName; +var appsArray = []; +var grSSA = new GlideRecord("sys_store_app"); +grSSA.addEncodedQuery( + "install_dateISNOTEMPTY^hide_on_ui=false^vendor=ServiceNow^ORvendorISEMPTY" +); +grSSA.orderBy("name"); +grSSA.orderBy("version"); +grSSA.query(); +while (grSSA.next()) { + var curName = grSSA.getValue("name"); + var latestVersion = updateAvailable(grSSA); + if (curName == prevName) { + continue; + } + if (latestVersion) { + prevName = curName; + var appObject = { + displayName: curName, + id: grSSA.getUniqueValue(), + load_demo_data: loadDemoData, + type: "application", + requested_version: grSSA.getValue("latest_version"), + }; + appsArray.push(appObject); + } +} + +function updateAvailable(grSSA) { + var installedVersion = grSSA.getValue("version"); + var latestVersion = grSSA.getValue("latest_version"); + var installedArray = installedVersion.split("."); + var latestArray = latestVersion.split("."); + var len = Math.max(installedArray.length, latestArray.length); + for (var i = 0; i < len; i++) { + var installed = installedArray[i] ? parseInt(installedArray[i]) : 0; + var latest = latestArray[i] ? parseInt(latestArray[i]) : 0; + if (installed < latest) { + return true; + } else if (installed > latest) { + return false; + } + } + return false; +} +if (appsArray.length > 0) { + gs.info("\\n\\n------------------------------------------------\\n\\nLinks to track progress below the payload information\\n\\n(scroll down)\\n\\n-----------------------------------------------\\n\\n"); + + var appsPackages = {}; + appsPackages.packages = appsArray; + appsPackages.name = "Update Apps"; + var data = new global.JSON().encode(appsPackages); + + var baseUrl = gs.getProperty("glide.servlet.uri"); + var update = new sn_appclient.AppUpgrader().installBatch(data); + var updateObj = JSON.parse(update); + gs.info( + "\\n\\n------------------------------------------------\\n\\nOpen the Batch install link to monitor the installation progress. It may take some time for the apps to all populate in the related list. After all apps have populated the install will start and the State will change to In progress.\\n\\nBatch install:\\n" + + baseUrl + + "nav_to.do?uri=sys_batch_install_plan.do?sys_id=" + + updateObj.batch_installation_id + + "\\n\\nExecution tracker:\\n" + + baseUrl + + "nav_to.do?uri=sys_progress_worker.do?sys_id=" + + updateObj.execution_tracker_id + + "\\n\\n-----------------------------------------------\\n\\n" + ); + var grSBIP = new GlideRecord('sys_batch_install_plan'); +if (grSBIP.get(updateObj.batch_installation_id)) { + grSBIP.setValue('notes','It may take some time for the apps to all populate in the related list below (you can refresh the list as needed to see them populating). \\n\\nAfter all apps have populated the install will start and the State (above) will change to In progress. \\n\\nWhen the batch is done the state will update to Installed'); + grSBIP.update(); +} +} else { + gs.info( + "\\n\\n-----------------------------------------------\\n\\nAll apps appear to be up-to-date. \\n\\nIf you think this is incorrect please try running this script again with \`updateCheck\` set to \`true\`. This will check the store for any new updates.\\n(sometimes there are apps in the Application Manager that say that there are updates but you can't actually update them)\\n\\n-----------------------------------------------\\n\\n" + ); +} +`; + +// Using block kit because the script is too long for a regular message +const message = { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "<@U6E2TEKQ9> made a cool script for bulk updating apps!\n\n" + } + }, + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_preformatted", + "elements": [ + { + "type": "text", + "text": eric_script + } + ] + } + ] + } + ] +} +new x_snc_slackerbot.Slacker().send_chat(current, message, true); diff --git a/Parsers/BitcoinPrice.js b/Parsers/BitcoinPrice.js new file mode 100644 index 00000000..fbf6af00 --- /dev/null +++ b/Parsers/BitcoinPrice.js @@ -0,0 +1,18 @@ +/* +activation_example:!bitcoinprice +regex:!bitcoinprice\b +flags:gmi +*/ + +var input = current.text.trim(); +var quote = new sn_ws.RESTMessageV2(); +quote.setEndpoint('https://api.coindesk.com/v1/bpi/currentprice.json'); +quote.setHttpMethod('GET'); + +var chatResponse = quote.execute(); +var chatResponseBody = JSON.parse(chatResponse.getBody()); + +if (chatResponseBody?.bpi?.USD?.rate) { + var bitcoinpriceFormatted = chatResponseBody.bpi.USD.rate; + new x_snc_slackerbot.Slacker().send_chat(current, 'The current price of one bitcoin is: $' + bitcoinpriceFormatted + ' USD', false); +} diff --git a/Parsers/Calculator.js b/Parsers/Calculator.js new file mode 100644 index 00000000..358984ec --- /dev/null +++ b/Parsers/Calculator.js @@ -0,0 +1,49 @@ +/* +activation_example:!calculate +regex:!calculate (.+) +flags:gmi +*/ + +var input = current.text.trim(); + +// Extract the mathematical expression from the input +var match = input.match(/!calculate (.+)/); +var expression = match ? match[1] : ""; + +function evaluateExpression(expr) { + // Remove whitespace + expr = expr.replace(/\s+/g, ''); + + // Basic validation to allow only numbers and operators + if (!/^[\d+\-*/().]+$/.test(expr)) { + throw new Error("Invalid expression"); + } + + // Use Free API to calculate + var calc = new sn_ws.RESTMessageV2(); + var url = 'http://api.mathjs.org/v4/?expr=' + encodeURIComponent(expr); + calc.setEndpoint(url); + calc.setHttpMethod("GET"); + var chatResponse = calc.execute(); + var result = JSON.parse(chatResponse.getBody()); + + result = parseFloat(result); + + // Return the evaluated result + return result; + +} + +try { + var result = evaluateExpression(expression); + + // Round the result to 2 decimal places + var roundedResult = result.toFixed(2); + + // Send the calculated result back to the user + new x_snc_slackerbot.Slacker().send_chat(current, "The result is: " + roundedResult, false); + +} catch (error) { + // Handle errors if the expression is invalid + new x_snc_slackerbot.Slacker().send_chat(current, "Oops! I couldn't understand that. Please provide a valid mathematical expression.", false); +} diff --git a/Parsers/Check if user is verified.js b/Parsers/Check if user is verified.js index d0006dcf..70f2da7d 100644 --- a/Parsers/Check if user is verified.js +++ b/Parsers/Check if user is verified.js @@ -12,11 +12,11 @@ user_id = user_id.replace(/>.*/, '').trim(); var grUser = new GlideRecord('x_snc_slackerbot_user'); if(grUser.get('user_id',user_id) && Object.keys(grUser).indexOf('verified') != -1){ - verification_status = (grUser.getValue('verified') == 1 ? true : false) ? 'has been verified and confirmed by the Slack admins.' : 'has not been verified and confirmed by the Slack admins.'; + verification_status = (grUser.getValue('verified') == 1 ? true : false) ? '*has been verified* and confirmed by the Slack admins.' : '*has not been verified* and confirmed by the Slack admins.'; user_info = grUser.getValue('user_info') !== null ? grUser.getValue('user_info').toString() : ''; message_body += '' + grUser.getValue('name') + '\'s identity '+ verification_status; - if(!gs.nil(user_info)) message_body += '\nThe following information has been noted about this user: ' + user_info; + if(!gs.nil(user_info)) message_body += '\n\nThe following information has been noted about this user:\n\n> ' + user_info; } else { message_body += 'I\'m afraid I can\'t do that. Either the user does not exist, or the logic to support this functionality is not yet on the instance.'; diff --git a/Parsers/Convert Celsius to Fahrenheit.js b/Parsers/Convert Celsius to Fahrenheit.js deleted file mode 100644 index ee9f9ac1..00000000 --- a/Parsers/Convert Celsius to Fahrenheit.js +++ /dev/null @@ -1,13 +0,0 @@ -/* -activation_example:30c or 30 degrees celsius -regex:(?:^|\s)(-?\d{1,3}\.?\d{0,2})°?\s?(?:degrees)?\s?c(?:elsius|elcius)?\b -flags:gmi -*/ - -var regextest = /(?:^|\s)(-?\d{1,3}\.?\d{0,2})°?\s?(?:degrees)?\s?c(?:elsius|elcius)?\b/gmi; -var match = regextest.exec(current.text); -var numbertest = /-?\d{1,}\.?\d{0,}/; -var numbermatch = numbertest.exec(match[0]); -var ctof = (parseFloat(numbermatch[0]) * 9/5) + 32; -var originalnumber = parseFloat(numbermatch[0]).toFixed(2).toString().slice(-3) == '.00' ? parseFloat(numbermatch[0]).toFixed(2).toString().slice(0, -3) : parseFloat(numbermatch[0]).toFixed(2); -var send_chat = new x_snc_slackerbot.Slacker().send_chat(current, originalnumber + '°C is ' + ctof.toFixed(2) + ' degrees in freedom units (Fahrenheit).'); diff --git a/Parsers/Convert Fahrenheit to Celsius.js b/Parsers/Convert Fahrenheit to Celsius.js deleted file mode 100644 index 334e18df..00000000 --- a/Parsers/Convert Fahrenheit to Celsius.js +++ /dev/null @@ -1,13 +0,0 @@ -/* -activation_example:30f or 30 degrees Fahrenheit -regex:(?:^|\s)(-?\d{1,3}\.?\d{0,2})°?\s?(?:degrees)?\s?f(?:ahrenheit)?\b -flags:gmi -*/ - -var regextest = /(?:^|\s)(-?\d{1,3}\.?\d{0,2})°?\s?(?:degrees)?\s?f(?:ahrenheit)?\b/gmi; -var match = regextest.exec(current.text); -var numbertest = /-?\d{1,}\.?\d{0,}/; -var numbermatch = numbertest.exec(match[0]); -var ftoc = (parseFloat(numbermatch[0]) - 32) * 5/9; -var originalnumber = parseFloat(numbermatch[0]).toFixed(2).toString().slice(-3) == '.00' ? parseFloat(numbermatch[0]).toFixed(2).toString().slice(0, -3) : parseFloat(numbermatch[0]).toFixed(2); -var send_chat = new x_snc_slackerbot.Slacker().send_chat(current, originalnumber + '°F is ' + ftoc.toFixed(2) + ' degrees in sane units (Celsius).'); diff --git a/Parsers/Convert Miles to Kilometre.js b/Parsers/Convert Miles to Kilometre.js index 205e22fb..6fee9853 100644 --- a/Parsers/Convert Miles to Kilometre.js +++ b/Parsers/Convert Miles to Kilometre.js @@ -4,7 +4,7 @@ regex:(?:^|\s)(=?-?\d{1,5}\.?\d{0,8})\s?mi(le|les)\b flags:gmi */ -var regextest = /(?:^|\s)(=?-?\d{1,5}\.?\d{0,8})\s?mi(le|iles)\b/gmi; +var regextest = /(?:^|\s)(=?-?\d{1,5}\.?\d{0,8})\s?mi(le|les)\b/gmi; var match = regextest.exec(current.text); var numbertest = /-?\d{1,}\.?\d{0,}/; var numbermatch = numbertest.exec(match[0]); diff --git a/Parsers/Convert temperatures.js b/Parsers/Convert temperatures.js new file mode 100644 index 00000000..9d77d62a --- /dev/null +++ b/Parsers/Convert temperatures.js @@ -0,0 +1,32 @@ +/* +activation_example:30c or 30 degrees fahrenheit +regex:(?:^|\s)(-?\d{1,3}\.?\d{0,2})°?\s?(?:degrees)?\s?(?:c(?:elsius)?|f(?:ahrenheit)?)\b +flags:gmi +*/ + +const formatNumber = num => Number(num).toFixed(2).replace(/\.00$/, ''); +const conversions = []; + +// convert C to F +const celsiusRegex = /(-?\d{1,3}(?:\.\d{1,2})?)°?\s?(?:degrees?)?\s?c(?:elsius)?\b/gi; +const celsiusToFahrenheit = c => ((c * 9 / 5) + 32).toFixed(2); +let cMatch; +while ((cMatch = celsiusRegex.exec(current.text)) !== null) { + const celsius = parseFloat(cMatch); + const fahrenheit = celsiusToFahrenheit(celsius); + conversions.push(`${formatNumber(celsius)}°C is ${formatNumber(fahrenheit)} degrees in freedom units (Fahrenheit).`); +} + +// convert F to C +const fahrenheitRegex = /(-?\d{1,3}(?:\.\d{1,2})?)°?\s?(?:degrees?)?\s?f(?:ahrenheit)?\b/gi; +const fahrenheitToCelsius = f => ((f - 32) * 5 / 9).toFixed(2); +let fMatch; +while ((fMatch = fahrenheitRegex.exec(current.text)) !== null) { + const fahrenheit = parseFloat(fMatch); + const celsius = fahrenheitToCelsius(fahrenheit); + conversions.push(`${formatNumber(fahrenheit)}°F is ${formatNumber(celsius)} degrees in sane units (Celsius).`); +} + +const conversionMessage = conversions.join('\n'); + +new x_snc_slackerbot.Slacker().send_chat(current, conversionMessage); diff --git a/Parsers/Corporate Joke.js b/Parsers/Corporate Joke.js index cd1db911..cc5fec9d 100644 --- a/Parsers/Corporate Joke.js +++ b/Parsers/Corporate Joke.js @@ -6,6 +6,7 @@ flags:gmi var where = current.text.indexOf('!corporatejoke ') + 13; var term = current.text.substr(where).trim(); +term = gs.urlEncode(term); // Build the search URL using a public joke API var searchUrl = 'https://api.chucknorris.io/jokes/search?query=' + term; diff --git a/Parsers/Create a GitHub issue.js b/Parsers/Create a GitHub issue.js new file mode 100644 index 00000000..7c686a74 --- /dev/null +++ b/Parsers/Create a GitHub issue.js @@ -0,0 +1,206 @@ +/* +activation_example:!issue Slacker Moldy donut, !issue -help +regex:^!issue\u0020? +flags:i +*/ + +// Prepare variables +var clientId = gs.getProperty('slackerbot.issueapp.id','invalid_id'); +var providerId = gs.getProperty('slackerbot.issueapp.provider','invalid_id'); +var installId = gs.getProperty('slackerbot.issueapp.install_id','invalid_id'); +var repositoryOwner = gs.getProperty('slackerbot.issueapp.owner','ServiceNowDevProgram'); +var supportedRepos = getSupportedRepos(); +var message = ''; +var selectedRepository = ''; +var body, blockMsg; +var titleLength = 60; // Lets be brief since we'll reflect the description in the body too +var slacker = new x_snc_slackerbot.Slacker(); + +// Get message +var issueCall = current.text.replace(/^!issue\u0020?/i,'').trim(); +var callArr = issueCall.split(' '); + +// Validate message +if(callArr.length == 0){ + message = '!issue must be called with the name of a repo, followed by the issue description. For example: `!issue Slacker My donut has no hole`\n\nThe full list of repos and accepted triggers can be found by sending !issue -help'; +} + +if(callArr.length == 1){ + if(callArr[0] == '-help'){ + message = 'SNDevs Slacker Issue Reporter\nA parser for creating issues against supported repositories\n\nUsage: `!issue repo-name description`\nExample: `!issue syntax_macros Capture syntax for calculating distance with GlideGeoPoint`\n\nSupported Repositories:'; + for(var repo in supportedRepos){ + message += '\n\t`' + repo + '` - Accepted triggers:\n\t\t`' + supportedRepos[repo].join('`, `') + '`'; + } + } else { + message = '!issue must be called with the name of a repo, followed by the issue description. For example: `!issue Slacker My donut has no hole`\n\nThe full list of repos and accepted triggers can be found by sending !issue -help'; + } +} + +if(callArr.length >= 2){ + var repoPar = callArr[0].toLowerCase(); + for(var repo in supportedRepos){ + if(supportedRepos[repo].indexOf(repoPar) > -1){ + selectedRepository = repo; + callArr.shift(); + } + } +} + +// Validate if we should progress +if(message.length > 0 || selectedRepository.length == 0 || clientId == 'invalid_id' || providerId == 'invalid_id' || installId == 'invalid_id'){ + if(selectedRepository.length == 0 && callArr.length > 1 && callArr[0] != '-help'){ + message = 'The provided repository is not supported by the SNDevs Slacker Issue Reporter at this time. New repos can be added by submitting a pull request.'; + } else if(message.length == 0 && (clientId == 'invalid_id' || providerId == 'invalid_id' || installId == 'invalid_id')){ + message = 'A required property has not been configured. Please advise users in <#CKJ2TE0AK> so this can be addressed.'; + } + slacker.send_chat(current, message, false); +} else { + body = {}; + body.title = callArr.join(' ').substring(0,titleLength); + body.body = callArr.join(' '); + + var token = getJWT(clientId, providerId); + var accessToken = getAccessToken(installId, token); + + if(!accessToken){ + message = 'An issue occurred while generating the access token for GitHub. Please advise users in <#CKJ2TE0AK> so they can investigate.'; + slacker.send_chat(current, message, false); + } else { + var output = createIssue(repositoryOwner, selectedRepository, body, accessToken); + if(!output){ + message = 'An issue occured while creating an issue over the API. Please wait a few seconds and try again. If the issue persists, please advise users in <#CKJ2TE0AK> so they can investigate.'; + slacker.send_chat(current, message, false); + } else { + blockMsg = buildBlockMessage(output.number, output.html_url); + slacker.send_chat(current, blockMsg, false); + } + } +} + +/** + * Get list of supported repos + * @returns {Object.} Object of repos as keys and accepted triggers as array values + */ +function getSupportedRepos() { + // Make triggers lowercase so that we can do case-insensitive matching + var repoMap = { + 'code-snippets': ['code-snippets','snippets'], + 'SlackerBot': ['slackerbot','slacker','slackbot'], + 'UI-Builder-Conference-Notes-App': ['ui-builder-conference-notes-app','conference-notes','notes-app'], + 'Points-Thing': ['points-thing','pt','points'], + 'Plants': ['plants'], + 'example-instancescan-checks': ['example-instancescan-checks','checks','instancescan'], + 'syntax_macros': ['syntax_macros','macros'], + 'ServiceNow-GenAi-Prompt-Library': ['serviceNow-genai-prompt-library','genai','prompts','prompt','library','prompt-library'], + 'Hack4Good-Idea-Submission': ['hack4good','h4g'], + 'Hacktoberfest': ['hacktoberfest'] + } + + return repoMap; +} + +/** + * Generate JSON Web Token (JWT) for GitHub + * @param client {string} GitHub App Client ID + * @param provider {string} Sys_ID of JWT Provider + * @returns {string} Signed JWT + */ +function getJWT(client, provider){ + var jwtAPI = new sn_auth.GlideJWTAPI(); + var header = JSON.stringify({typ: 'JWT', alg: 'RSA256'}); + var exp = new GlideDateTime(); + exp.addSeconds(600); + var now = new GlideDateTime(); + now.addSeconds(-60); + var payloadObj = { + iat: Math.floor(now.getNumericValue() / 1000), + iss: client, + exp: Math.floor(exp.getNumericValue() / 1000) + }; + var payload = JSON.stringify(payloadObj); + + var jwt = jwtAPI.generateJWT(provider, header, payload); + return jwt; +} + +/** + * Get Installation Access Token to act on behalf of App within installed scope + * @param install {string} Installation ID for Organisation/User + * @param jwt {string} Signed JWT + * @returns {string|false} Installation Access Token + */ +function getAccessToken(install,jwt){ + var tokenRequest = new sn_ws.RESTMessageV2(); + tokenRequest.setEndpoint('https://api.github.com/app/installations/' + install + '/access_tokens'); + tokenRequest.setHttpMethod('POST'); + tokenRequest.setRequestHeader('Accept','application/vnd.github+json'); + tokenRequest.setRequestHeader('Authorization','Bearer ' + jwt); + + var tokenResp = tokenRequest.execute(); + if(tokenResp.getStatusCode() == 201){ + return (JSON.parse(tokenResp.getBody())).token; + } + return false; +} + +/** + * Create an issue in GitHub + * @param owner {string} Name of repo owner + * @param repo {string} Name of repo + * @param body {Object.} Issue payload + * @param token {string} Installation access token + * @returns Created issue payload + */ +function createIssue(owner, repo, body, token){ + var issueRequest = new sn_ws.RESTMessageV2(); + issueRequest.setEndpoint('https://api.github.com/repos/' + owner + '/' + repo + '/issues'); + issueRequest.setHttpMethod('POST'); + issueRequest.setRequestHeader('Accept','application/vnd.github+json'); + issueRequest.setRequestHeader('Authorization','Bearer ' + token); + issueRequest.setRequestBody(JSON.stringify(body)); + + var issueResp = issueRequest.execute(); + if(issueResp.getStatusCode() == 201){ + return JSON.parse(issueResp.getBody()); + } + return false; +} + +/** + * Build a block message for created issues + * @param number {integer} Issue number + * @param url {string} Issue URL + * @returns Slack Block message + */ +function buildBlockMessage(number, url){ + var blocks = { + 'blocks': [ + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': '*Issue #' + number + ' created* :tada:' + } + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': 'Click this button to view your issue' + }, + 'accessory': { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Open GitHub', + 'emoji': true + }, + 'style': 'primary', + 'url': url + } + } + ] + }; + blocks.text = 'Issue #' + number + ' created!'; + return blocks; +} diff --git a/Parsers/Create a new SN Quiz.js b/Parsers/Create a new SN Quiz.js new file mode 100644 index 00000000..f4ee9744 --- /dev/null +++ b/Parsers/Create a new SN Quiz.js @@ -0,0 +1,114 @@ +/* +activation_example:!quiz itsm +regex:!quiz\s+(.+) +flags:gmi +*/ + +(function(current) { + var slacker = new x_snc_slackerbot.Slacker(); + + // Capture the text and extract the quiz topic using regex + var text = current.text; + var match = text.match(/!quiz\s+(.+)/i); + + if (!match) { + slacker.send_chat(current, "Please provide a valid ServiceNow quiz topic, e.g., !quiz ITSM.", false); + return; + } + + var quizTopic = match[1].trim(); + var originalUserId = current.user.user_id; + + try { + // Openai Integration - Generate Quiz + var apiKey = gs.getProperty("openai.key"); + + var restMessage = new sn_ws.RESTMessageV2(); + restMessage.setEndpoint('https://api.openai.com/v1/chat/completions'); + restMessage.setHttpMethod('POST'); + restMessage.setRequestHeader('Authorization', 'Bearer ' + apiKey); + restMessage.setRequestHeader('Content-Type', 'application/json'); + + var requestBody = { + "model": "gpt-4o", + "messages": [{ + "role": "system", + "content": "You are an assistant that creates structured multiple-choice quiz questions about ServiceNow topics. Return the question and answers in JSON format." + }, + { + "role": "user", + "content": "Please generate one multiple-choice quiz question on the topic of " + quizTopic + ". The answers should not include any prefixes like 'A)', '1.', etc. Just provide the plain answer text. Return the result in the following JSON format:\n{\n \"question\": \"\",\n \"answers\": [\"option 1\", \"option 2\", \"option 3\", \"option 4\"],\n \"correct\": \"\",\n \"explanation\": \"\"\n}" + } + ], + "max_tokens": 200 + }; + + + restMessage.setRequestBody(JSON.stringify(requestBody)); + var response = restMessage.execute(); + + if (response.getStatusCode() !== 200) { + throw new Error('Received non-200 response from OpenAI: ' + response.getStatusCode()); + } + + var responseBody = response.getBody(); + var jsonResponse = JSON.parse(responseBody); + + if (!jsonResponse.choices || !jsonResponse.choices[0].message || !jsonResponse.choices[0].message.content) { + throw new Error('Unexpected response format from OpenAI'); + } + + var quizData = jsonResponse.choices[0].message.content; + var quizQuestion = JSON.parse(quizData); + + // Find the correct answer + var correctOptionIndex = ""; + quizQuestion.answers.forEach(function(answer, index) { + if (answer === quizQuestion.correct) { + correctOptionIndex = "option_" + (index + 1); + } + }); + + // Prepare Slack Block Kit for the quiz question and answers + var optionLetters = ['A', 'B', 'C', 'D']; + var blocks = [{ + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Hello, " + current.user.name + "! Test your ServiceNow skills and have fun! \n\n" + + "*Question:* " + quizQuestion.question + "\n\n" + + optionLetters[0] + ") " + quizQuestion.answers[0] + "\n" + + optionLetters[1] + ") " + quizQuestion.answers[1] + "\n" + + optionLetters[2] + ") " + quizQuestion.answers[2] + "\n" + + optionLetters[3] + ") " + quizQuestion.answers[3] + "\n\n" + + "_*Note:* Only the person who initiated the quiz can answer._" + } + }, + { + "type": "actions", + "elements": quizQuestion.answers.map(function(answer, index) { + return { + "type": "button", + "text": { + "type": "plain_text", + "text": optionLetters[index] + }, + + "value": "option_" + (index + 1) + "|" + correctOptionIndex + "|" + originalUserId + "|" + quizQuestion.explanation, + "action_id": "quiz_answer_" + (index + 1) + }; + }) + } + ]; + + // Send Slack message - Quiz + slacker.send_chat(current, { + "blocks": blocks + }, false); + + } catch (error) { + gs.error('[SLACKER - QUIZ] Error generating quiz: ' + error.message); + slacker.send_chat(current, "Sorry, there was an issue generating the quiz. Please try again later.", false); + } + +})(current); diff --git a/Parsers/Currency Converter.js b/Parsers/Currency Converter.js new file mode 100644 index 00000000..11194fa4 --- /dev/null +++ b/Parsers/Currency Converter.js @@ -0,0 +1,38 @@ +/* +activation_example:!convert to +regex:!convert (\d+) (\w+) to (\w+) +flags:gmi +*/ + +var input = current.text.trim(); // Get entire user input after trimming + +// Extract amount, fromCurrency, and toCurrency from the input +var match = input.match(/!convert (\d+) (\w+) to (\w+)/); +var amount = match ? match[1] : ""; +var fromCurrency = match ? match[2] : ""; +var toCurrency = match ? match[3] : ""; + +// Build the search URL using the Currency Converter API +var baseUrl = 'https://api.exchangeratesapi.io/latest'; +var searchUrl = baseUrl + '?base=' + fromCurrency; + +// Make the API request +var chatReq = new sn_ws.RESTMessageV2(); +chatReq.setEndpoint(searchUrl); +chatReq.setHttpMethod("GET"); +var chatResponse = chatReq.execute(); +var chatResponseBody = chatResponse.getBody(); + +// Parse the API response as JSON +var responseData = JSON.parse(chatResponseBody); + +// Check if the API response contains exchange rates +if (responseData.rates && responseData.rates[toCurrency]) { + var exchangeRate = responseData.rates[toCurrency]; + var convertedAmount = amount * exchangeRate; + + // Send a formatted message to Slack + new x_snc_slackerbot.Slacker().send_chat(current, `${amount} ${fromCurrency} is equal to ${convertedAmount.toFixed(2)} ${toCurrency}`, false); +} else { + new x_snc_slackerbot.Slacker().send_chat(current, "Sorry, I couldn't find the exchange rate for ${fromCurrency} to ${toCurrency}.", false); +} diff --git a/Parsers/Daily Motivation.js b/Parsers/Daily Motivation.js new file mode 100644 index 00000000..df97ba41 --- /dev/null +++ b/Parsers/Daily Motivation.js @@ -0,0 +1,40 @@ +/* +activation_example:!motivate +regex:!motivate +flags:gmi +order:100 +stop_processing:false +*/ + +try { + var request = new sn_ws.RESTMessageV2(); + request.setEndpoint('https://api.quotable.io/random'); // Replace with your preferred API URL + request.setHttpMethod('GET'); + + var response = request.execute(); + var responseBody = response.getBody(); + var responseData = JSON.parse(responseBody); + + var quote = `"${responseData.content}" – ${responseData.author}`; +} catch (error) { + // Fallback to a static quote in case of an error + var quotes = [ + "The only way to do great work is to love what you do. – Steve Jobs", + "Success is not the key to happiness. Happiness is the key to success. – Albert Schweitzer", + "Believe you can and you're halfway there. – Theodore Roosevelt", + "The future belongs to those who believe in the beauty of their dreams. – Eleanor Roosevelt", + "Don’t watch the clock; do what it does. Keep going. – Sam Levenson", + "Act as if what you do makes a difference. It does. – William James", + "Success is not how high you have climbed, but how you make a positive difference to the world. – Roy T. Bennett", + "Success is not final, failure is not fatal: it is the courage to continue that counts. – Winston Churchill", + "It always seems impossible until it's done. – Nelson Mandela", + "Keep your face always toward the sunshine—and shadows will fall behind you. – Walt Whitman", + "You do not find the happy life. You make it. – Camilla Eyring Kimball", + "Success is getting what you want, happiness is wanting what you get. – W.P. Kinsella", + "You are never too old to set another goal or to dream a new dream. – C.S. Lewis", + "If you want to walk fast, walk alone. But if you want to walk far, walk together. – Ratan Tata" + ]; + quote = quotes[Math.floor(Math.random() * quotes.length)]; +} + +new x_snc_slackerbot.Slacker().send_chat(current.channel, `Motivation: "${quote}"`, false, '', current.thread_ts); diff --git a/Parsers/Find Paper.js b/Parsers/Find Paper.js new file mode 100644 index 00000000..da28e400 --- /dev/null +++ b/Parsers/Find Paper.js @@ -0,0 +1,49 @@ +/* +activation_example:!findpaper paper details +regex:!findpaper +flags: +*/ + +// Extracting the search query +var searchQuery = current.text.replace(/!findpaper/gmi, "").trim().substring(0, 1000); + +// Searching for a research paper +var researchPaperRequest = new sn_ws.RESTMessageV2(); +researchPaperRequest.setEndpoint('https://api.semanticscholar.org/graph/v1/paper/search?query=' + encodeURIComponent(searchQuery) + '&limit=1'); +researchPaperRequest.setHttpMethod("GET"); +researchPaperRequest.setRequestHeader("Accept", "application/json"); + +// Executing the API call +try { + var response = researchPaperRequest.execute(); + var responseBody = response.getBody(); + var responseJson = JSON.parse(responseBody); + + // Extracting details of the first paper found + if (responseJson && responseJson.data && responseJson.data.length > 0) { + var paper = responseJson.data[0]; + var title = paper.title || "Title not available"; + var authors = "Authors not available"; + if (paper.authors && paper.authors.length > 0) { + var authorNames = []; + for (var i = 0; i < paper.authors.length; i++) { + authorNames.push(paper.authors[i].name); + } + authors = authorNames.join(", "); + } + var year = paper.year || "Year not available"; + var url = paper.url || "URL not available"; + + // Creating the message to send + var message = `Here's a research paper I found:\n\nTitle: ${title}\nAuthors: ${authors}\nYear: ${year}\nURL: ${url}`; + + // Sending the response message + new x_snc_slackerbot.Slacker().send_chat(current, message, false); + } else { + // No paper found for the given search query + new x_snc_slackerbot.Slacker().send_chat(current, "Sorry, I couldn't find any research papers for your query.", false); + } +} catch (e) { + // Handling errors + new x_snc_slackerbot.Slacker().send_chat(current, "An error occurred while trying to find a research paper.", false); +} diff --git a/Parsers/FruitRiddle.js b/Parsers/FruitRiddle.js new file mode 100644 index 00000000..ae759452 --- /dev/null +++ b/Parsers/FruitRiddle.js @@ -0,0 +1,195 @@ +/* +activation_example:!fruit riddle start +regex:^!fruit riddle.*$ +flags:gmi +*/ + + +/** + * Main variable: current, which is a record from the x_snc_slackerbot_chat table + * Important attributes: + * - Text (text) + * - User (user) + * - Channel (channel) + */ + +var FruitRiddle = Class.create(); +FruitRiddle.prototype = { + + initialize: function(currentChatContext) { + + // Available commands + this.CMD_START = "start"; + this.CMD_EXIT = "exit"; + this.CMD_SOLUTION = "solution"; + + this.user = currentChatContext.getDisplayValue("user"); + this.channel = currentChatContext.getValue("channel"); + this.text = currentChatContext.getValue("text"); + this.threadTs = currentChatContext.getValue("thread_ts"); + this.isThread = !gs.nil(this.threadTs); // The current message is in thread or not + + this.fruits = ["apple", "banana", "orange", "mango", "strawberry", "blueberry", "raspberry", "pineapple", "watermelon", "papaya", "kiwi", "peach", "pear", "grape", "cherry", "pomegranate", "lemon", "lime", "coconut", "avocado", "fig", "plum", "nectarine", "apricot", "dragonfruit", "grapefruit", "persimmon", "guava", "jackfruit", "lychee"]; + }, + + /** + * The main logic is handled by this function + */ + parse: function() { + + // Need to check the text at first + // Only the following commands are allowed + var textRegex = /^(!fruit riddle|!fruit riddle start|!fruit riddle exit|!fruit riddle solution [a-zA-Z]*|!fruit riddle [a-zA-Z]{4})$/g; + + if (this.text.match(textRegex)) { + + if (this.text == "!fruit riddle" && !this.isThread) { + return { + thread: this.isThread, + message: `This is a simple game, where I think of a fruit, and you have to guess it. You can start the game with the command: + + !fruit riddle start + +To submit a guess, use the following command: + + !fruit riddle abcd + +If you know the answer, type: + + !fruit riddle solution fruit + +If you want to give up, use the message: + + !fruit riddle exit + +Have fun :smile:` + }; + } + else if (this.text == "!fruit riddle" && this.isThread) { + return { + thread: this.isThread, + message: "Please use one of the input commands, such as solve, exit or guess letter" + }; + } + + // Need to get the 3rd word from the command + var command = this.text.split(" ")[2]; + + if (this.isThread) { + // The message is in a thread + if (command === this.CMD_START) { + return { + thread: true, + message: "The fruit riddle start command cannot be used in thread." + }; + } + else if (command === this.CMD_EXIT) { + return { + thread: true, + message: `😮 +${this.user}!! So you give up...I wouldn't have thought that about you.` + }; + } + else if (command === this.CMD_SOLUTION) { + + var solution = this.text.split(" ")[3]; + var selectedFruit = this._getRandomFruit(); + + if (solution === selectedFruit) { + return { + thread: true, + message: "Exactly! Congratulations! 🥳" + }; + } + + return { + thread: true, + message: "Well, you missed it. Try again." + }; + } + else { + // The letters + + var letters = command.split(''); + var result = this._revealLetters(this._getRandomFruit(), letters); + + return { + thread: true, + message: `Here we go: ${result}` + }; + } + } + else { + // The messasge is not part of a thread + if (command === this.CMD_START) { + return { + thread: true, + message: `Hey *${this.user}!* Do you want to play? + +I think of a fruit, and you have to guess what it is. You can provide four letters at a time in the following format: +!fruit riddle abcd + +If you know the answer, use this command: +!fruit riddle solution orange + +If you give up, type: +!fruit riddle exit + +Let's start! 💪` + }; + } + else if (command === this.CMD_EXIT || command === this.CMD_SOLUTION) { + return { + thread: false, + message: `The ${command} command cannot be used outside of the thread.` + }; + } + else { + // The letters + return { + thread: false, + message: "This game cannot be plaed outside of the thread." + }; + } + } + } + + return { + thread: this.isThread, + message: "I can't interpret this command." + }; + }, + + /** + * This function is responsible for get a random fruit from the array. The random number is based on the thread time. + * @return - one element from the fruit array + */ + _getRandomFruit: function() { + var _threadTs = (this.threadTs + "").split(".")[0]; + var randomNumber = _threadTs % 30; + return this.fruits[randomNumber]; + }, + + /** + * This function is responsible to check if the given letters are part of the fruit or not + * @param {string} fruit - The current fruit + * @param {string} letters - The four letters which were added by the uyer + * @return - If there are letters witch match it will be given back in correct position. Other ones will be masked with the '_' character. + */ + _revealLetters: function(fruit, letters) { + return fruit.split('').map(char => { + // Check if the character is in the letters array + return letters.includes(char) ? char : '_'; + }).join(''); + }, + + type: 'FruitRiddle' +}; + +var gameResultObj = new x_snc_slackerbot.FruitRiddle(current).parse(); +try { + new x_snc_slackerbot.Slacker().send_chat(current, gameResultObj.message, gameResultObj.thread); +} +catch( e ) { + gs.error( "An error occured when SlackerBot tried to send a response back to Slack.\nError: " + e.name + ": " + e.message ); +} diff --git a/Parsers/Generate an AI chat react.js b/Parsers/Generate an AI chat react.js index 9853516a..e5b71e78 100644 --- a/Parsers/Generate an AI chat react.js +++ b/Parsers/Generate an AI chat react.js @@ -11,26 +11,48 @@ count.addQuery('payload', 'CONTAINS', '"channel": "' + payload.event.item.channe count.query(); if (count.getRowCount() < 2) { - var prompt = current.text; - var chatReq = new sn_ws.RESTMessageV2(); - chatReq.setEndpoint('https://api.openai.com/v1/chat/completions'); - chatReq.setHttpMethod("POST"); - chatReq.setRequestHeader("Authorization", "Bearer " + gs.getProperty("openai.key")); - chatReq.setRequestHeader('Content-Type', "application/json"); - chatReq.setRequestHeader('User-Agent', "ServiceNow"); - chatReq.setRequestHeader("Accept", "*/*"); - var body = { - "model": "gpt-4o-mini", - "messages": [{"role": "user", "content": prompt +". You cannot ask for follow-up responses, your response will be the end of this conversation."}], - // "max_tokens": 250 - }; - chatReq.setRequestBody(JSON.stringify(body)); - var chatResponse = chatReq.execute(); - gs.info(chatResponse.getBody()); - var chatResponseBody = JSON.parse(chatResponse.getBody()); - var chatResponseContent = chatResponseBody.choices[0].message.content; - - var show_tokens = false; - var token_cost = show_tokens ? "> tokens: " + chatResponseBody.usage.total_tokens + " ($" + (parseInt(chatResponseBody.usage.total_tokens) * 0.000002).toFixed(6) + ")\n" : ""; - new x_snc_slackerbot.Slacker().send_chat(current, "> " + prompt.replace(/\n/gmi, ". ") + "\n" + token_cost + "\n" + chatResponseContent, chatResponseContent.length > 500 ? true : false); + var prompt = current.text; + var chatReq = new sn_ws.RESTMessageV2(); + chatReq.setEndpoint('https://api.openai.com/v1/chat/completions'); + chatReq.setHttpMethod("POST"); + chatReq.setRequestHeader("Authorization", "Bearer " + gs.getProperty("openai.key")); + chatReq.setRequestHeader('Content-Type', "application/json"); + chatReq.setRequestHeader('User-Agent', "ServiceNow"); + chatReq.setRequestHeader("Accept", "*/*"); + var body = { + "model": "gpt-4o-mini", + "messages": [ + { + "role": "system", + "content": ` + You are a Slack bot that enhances the formatting of messages to make them more engaging and visually appealing. + Utilize Slack's markdown language mrkdwn for various formatting elements. + Follow these instructions: + 1. Enclose important words or phrases with *asterisks* for bold emphasis. + 2. Enclose code and numbers and percentages using backticks, like \`this\`. + 3. Use emojis when necessary to add expressiveness. + 4. Organize text with numbered or bullet lists, using "-" for bullet points. + 5. Combine bold and lists as needed: *Bold text*: normal text. + 6. Italicize words like _this_. + 7. Use blockquotes with ">" for quotes. + 8. For URLs, use . + 9. Keep user (@user) and channel (#channel) tags unchanged. + Keep close to the original message tone and formatting.` + }, + { + "role": "user", + "content": prompt + ". You cannot ask for follow-up responses, your response will be the end of this conversation." + } + ], + // "max_tokens": 250 + }; + chatReq.setRequestBody(JSON.stringify(body)); + var chatResponse = chatReq.execute(); + gs.info(chatResponse.getBody()); + var chatResponseBody = JSON.parse(chatResponse.getBody()); + var chatResponseContent = chatResponseBody.choices[0].message.content; + + var show_tokens = false; + var token_cost = show_tokens ? "> tokens: " + chatResponseBody.usage.total_tokens + " ($" + (parseInt(chatResponseBody.usage.total_tokens) * 0.000002).toFixed(6) + ")\n" : ""; + new x_snc_slackerbot.Slacker().send_chat(current, "> " + prompt.replace(/\n/gmi, ". ") + "\n" + token_cost + "\n" + chatResponseContent, chatResponseContent.length > 500 ? true : false); } diff --git a/Parsers/Generate an AI chat.js b/Parsers/Generate an AI chat.js index ebd644ba..fa8fa516 100644 --- a/Parsers/Generate an AI chat.js +++ b/Parsers/Generate an AI chat.js @@ -11,11 +11,36 @@ chatReq.setRequestHeader("Authorization", "Bearer " + gs.getProperty("openai.key chatReq.setRequestHeader('Content-Type', "application/json"); chatReq.setRequestHeader('User-Agent', "ServiceNow"); chatReq.setRequestHeader("Accept", "*/*"); + var body = { "model": "gpt-4o-mini", - "messages": [{"role": "user", "content": prompt +". You cannot ask for follow-up responses, your response will be the end of this conversation."}], -// "max_tokens": 250 + "messages": [ + { + "role": "system", + "content": ` + You are a Slack bot that enhances the formatting of messages to make them more engaging and visually appealing. + Utilize Slack's markdown language mrkdwn for various formatting elements. + Follow these instructions: + 1. Enclose important words or phrases with *asterisks* for bold emphasis. + 2. Enclose code and numbers and percentages using backticks, like \`this\`. + 3. Use emojis when necessary to add expressiveness. + 4. Organize text with numbered or bullet lists, using "-" for bullet points. + 5. Combine bold and lists as needed: *Bold text*: normal text. + 6. Italicize words like _this_. + 7. Use blockquotes with ">" for quotes. + 8. For URLs, use . + 9. Keep user (@user) and channel (#channel) tags unchanged. + Keep close to the original message tone and formatting. + ` + }, + { + "role": "user", + "content": prompt + ". You cannot ask for follow-up responses, your response will be the end of this conversation." + } + ] + // "max_tokens": 250 }; + chatReq.setRequestBody(JSON.stringify(body)); var chatResponse = chatReq.execute(); gs.info(chatResponse.getBody()); diff --git a/Parsers/Generate an AI image.js b/Parsers/Generate an AI image.js index 2bde30c9..8954d255 100644 --- a/Parsers/Generate an AI image.js +++ b/Parsers/Generate an AI image.js @@ -12,35 +12,64 @@ dalleReq.setRequestHeader('Content-Type', "application/json"); dalleReq.setRequestHeader('User-Agent', "ServiceNow"); dalleReq.setRequestHeader("Accept", "*/*"); var body = { - "prompt": prompt, - "n": 1, - "size": "1024x1024", - quality: "standard", - model: "dall-e-3", - "response_format": "url", - "user": current.user.name.toString() + "prompt": prompt, + "n": 1, + "size": "1024x1024", + quality: "standard", + model: "dall-e-3", + "response_format": "url", + "user": current.user.name.toString() }; dalleReq.setRequestBody(JSON.stringify(body)); var dalleResponse = dalleReq.execute(); +var dalleResponseStatusCode = dalleResponse.getStatusCode(); var dalleResponseBody = JSON.parse(dalleResponse.getBody()); +var failureMessage = "Image Generation failed. If this is your first error please try again, otherwise contact an admin"; -//Cloudinary -var cloudinary = new sn_ws.RESTMessageV2(); -cloudinary.setEndpoint('https://api.cloudinary.com/v1_1/dxllc568e/image/upload'); -cloudinary.setHttpMethod('POST'); -cloudinary.setRequestHeader('Content-Type', "application/json"); -cloudinary.setRequestHeader('User-Agent', "ServiceNow"); -cloudinary.setRequestHeader("Accept", "*/*"); -var cloudinaryBody = { - "file": dalleResponseBody.data[0].url, - "upload_preset": gs.getProperty("cloudinary_upload_preset"), - "api_key": gs.getProperty("cloudinary_api_key"), - "timestamp": Math.floor(new Date().getTime() / 1000), -}; +if (dalleResponseStatusCode != 200) { + new x_snc_slackerbot.Slacker().send_chat(current, failureMessage); + if (gs.getProperty("generateaiimage.debug.enable", "true") == "true") { + gs.error("Error: " + dalleResponseStatusCode + " \n" + dalleResponseBody.error.message + "\n" + "Prompt: " + prompt); + } + +} else { + //Cloudinary + var cloudinary = new sn_ws.RESTMessageV2(); + cloudinary.setEndpoint('https://api.cloudinary.com/v1_1/dxllc568e/image/upload'); + cloudinary.setHttpMethod('POST'); + cloudinary.setRequestHeader('Content-Type', "application/json"); + cloudinary.setRequestHeader('User-Agent', "ServiceNow"); + cloudinary.setRequestHeader("Accept", "*/*"); + var cloudinaryBody = { + "file": dalleResponseBody.data[0].url, + "upload_preset": gs.getProperty("cloudinary_upload_preset"), + "api_key": gs.getProperty("cloudinary_api_key"), + "timestamp": Math.floor(new Date().getTime() / 1000), + }; + + cloudinary.setRequestBody(JSON.stringify(cloudinaryBody)); + + var cloudinaryResponse = cloudinary.execute(); + var cloudinaryResponseBody = JSON.parse(cloudinaryResponse.getBody()); -cloudinary.setRequestBody(JSON.stringify(cloudinaryBody)); + var message = { + "blocks": [ + { + "type": "image", + "title": { + "type": "plain_text", + "text": prompt + }, + "block_id": "image4", + "image_url": cloudinaryResponseBody.url, + "alt_text": prompt + } + ] + } -var cloudinaryResponse = cloudinary.execute(); -var cloudinaryResponseBody = JSON.parse(cloudinaryResponse.getBody()); -new x_snc_slackerbot.Slacker().send_chat(current, "<" + cloudinaryResponseBody.url + "|" + prompt + ">\n"); -//new x_snc_slackerbot.Slacker().send_chat(current, "<" + cloudinaryResponseBody.url + "|" + prompt + "> (<" + "https://collection.cloudinary.com/dxllc568e/86896fd03420bdbdb47adcc037086bdf" + "|" + "gallery>)"); + if (cloudinaryResponseBody.url) { + new x_snc_slackerbot.Slacker().send_chat(current, message); + } else { + new x_snc_slackerbot.Slacker().send_chat(current, failureMessage); + } +} diff --git a/Parsers/Get a music link.js b/Parsers/Get a music link.js new file mode 100644 index 00000000..c0681299 --- /dev/null +++ b/Parsers/Get a music link.js @@ -0,0 +1,45 @@ +/* +activation_example:!music https://music.apple.com/us/song/inferna/1814573909 +regex:^!music +flags:gmi +*/ + +function getTapeLink(url) { + + try { + const endpointURL = 'https://www.tapelink.io/api/generate-link'; + const request = new sn_ws.RESTMessageV2(); + request.setHttpMethod('post'); + request.setEndpoint(endpointURL); + request.setRequestHeader('Content-Type', 'application/json'); + request.setRequestHeader('Accept', 'application/json'); + const payload = { + 'url': url + }; + request.setRequestBody(JSON.stringify(payload)); + const response = request.execute(); + + const httpStatus = response.getStatusCode(); + const responseBody = JSON.parse(response.getBody()); + + return { + body: responseBody, + status: httpStatus + }; + } catch (ex) { + gs.error(`An error occurred in the RESTMessageV2 script: ${ex.getMessage()}`); + } +} + +const slacker = new x_snc_slackerbot.Slacker(); +let input = current.text.trim(); +let link = input.replace('!music ', ''); +const tapeLinkObj = getTapeLink(link); +let msg = ''; +if (tapeLinkObj.status == '200' && tapeLinkObj.body.success) { + msg = ``; +} else { + msg = `Something went wrong. Please check that you entered a full link. Error details: ${tapeLinkObj.body.error}`; +} + +slacker.send_chat(current, msg, false); diff --git a/Parsers/Get the Product Documentation.js b/Parsers/Get the Product Documentation.js index 3f3b9c0f..e6d1ee52 100644 --- a/Parsers/Get the Product Documentation.js +++ b/Parsers/Get the Product Documentation.js @@ -9,5 +9,5 @@ var term = current.text.substr(where).trim(); if (term == '') { var send_confusion = new x_snc_slackerbot.Slacker().send_chat(current, ':upside_down_face: !docs *something*', false); } else { - var send_chat = new x_snc_slackerbot.Slacker().send_chat(current, 'https://docs.servicenow.com/search?q=' + escape(term) + '&labels=2', false); + var send_chat = new x_snc_slackerbot.Slacker().send_chat(current, 'https://www.servicenow.com/docs/search?personalize=true&q=' + escape(term), false); } diff --git a/Parsers/Hacktoberfest Leaderboard.js b/Parsers/Hacktoberfest Leaderboard.js new file mode 100644 index 00000000..1b563402 --- /dev/null +++ b/Parsers/Hacktoberfest Leaderboard.js @@ -0,0 +1,78 @@ +/* +activation_example:!hackleaders +regex:!hackleaders?|!hacktoberfestleaderboard|!leaderboardhacktoberfest +flags:gmi +stop_processing:false +*/ + +var count = new GlideAggregate("x_snc_hacktrack_event"); +count.addEncodedQuery( + "userISNOTEMPTY^pointISNOTEMPTY^point!=0^sys_created_onDATEPARTSeptember@javascript:gs.datePart('month','sep','EE')^ORsys_created_onDATEPARTOctober@javascript:gs.datePart('month','oct','EE')^ORsys_created_onDATEPARTNovember@javascript:gs.datePart('month','nov','EE')^sys_created_onONThis year@javascript:gs.beginningOfThisYear()@javascript:gs.endOfThisYear()^payload!=ignore" +); +count.groupBy("user"); +count.addAggregate("SUM", "point"); +count.orderByAggregate("SUM", "point"); +count.query(); +var items = []; +while (count.next()) { + var item = {}; + item.username = count.user.toString(); + item.points = parseInt(count.getAggregate("SUM", "point").split(".")[0]); + items.push(item); +} +var leaderboard = []; +var leaderboard_index = 0; +var count = 0; +if (Math.floor(items.length * 0.05) > 0) { + leaderboard.push("*Top 5% contributors:*"); + count = Math.floor(items.length * 0.05); + var this_section = []; + for (var i05 = 0; i05 < count; i05++) { + this_section.push(items[leaderboard_index].username); + leaderboard_index++; + } + leaderboard.push(this_section.join(", ")); + leaderboard.push(""); +} +if (Math.floor(items.length * 0.1) > 0) { + leaderboard.push("*Top 10% contributors:*"); + count = Math.floor(items.length * 0.1) - Math.floor(items.length * 0.05); + this_section = []; + for (var i10 = 0; i10 < count; i10++) { + this_section.push(items[leaderboard_index].username); + leaderboard_index++; + } + leaderboard.push(this_section.join(", ")); + leaderboard.push(""); +} +if (Math.floor(items.length * 0.25) > 0) { + leaderboard.push("*Top 25% contributors:*"); + count = + Math.floor(items.length * 0.25) - + Math.floor(items.length * 0.1) - + Math.floor(items.length * 0.05); + this_section = []; + for (var i25 = 0; i25 < count; i25++) { + this_section.push(items[leaderboard_index].username); + leaderboard_index++; + } + leaderboard.push(this_section.join(", ")); + leaderboard.push(""); +} +if (Math.floor(items.length * 0.5) > 0) { + leaderboard.push("*Top 50% contributors:*"); + count = + Math.floor(items.length * 0.5) - + Math.floor(items.length * 0.25) - + Math.floor(items.length * 0.1) - + Math.floor(items.length * 0.05); + this_section = []; + for (var i50 = 0; i50 < count; i50++) { + this_section.push(items[leaderboard_index].username); + leaderboard_index++; + } + leaderboard.push(this_section.join(", ")); + leaderboard.push(""); +} + +new x_snc_slackerbot.Slacker().send_chat(current, leaderboard.join("\n"), true); diff --git a/Parsers/Hello from.js b/Parsers/Hello from.js index 6ff0ea0b..273a4473 100644 --- a/Parsers/Hello from.js +++ b/Parsers/Hello from.js @@ -1,22 +1,60 @@ /* -activation_example:hellofrom -regex:(!?hellofrom) +activation_example:!hellofrom Miami, FL +regex:(!hellofrom) flags:gmi */ -var responses = [ - ["IST","Delhi, India"], - ["PST","San Diego, USA"], - ["GMT","London, UK"], - ["JST","Tokyo, Japan"], - ["CET","Budapest, Hungary"] -]; -var randNumber=parseInt(Math.random() * responses.length); -var currentDateTime=new GlideDateTime(); -var strConvertedDateTime= new GlideScheduleDateTime(currentDateTime).convertTimeZone("UTC", responses[randNumber][0]); -var gdtConvertedDateTime = new GlideDateTime(strConvertedDateTime); - -var response="Hello from " + responses[randNumber][1] + ". Our current time is " + gdtConvertedDateTime; -//gs.info(response); - -var send_chat = new x_snc_slackerbot.Slacker().send_chat(current, response, true); +var rawInput = current.text; +// Check if the user is requesting help +if (rawInput.trim().toLowerCase() === "!hellofrom -help") { + provideHelpText(); +} else { + processHelloFromCommand(rawInput); +} + +function provideHelpText() { + var helpText = "SNDevs Slacker Hello From\n" + + "A command to get the current date and time for a specified location.\n\n" + + "Usage: `!hellofrom [location]`\n" + + "Examples:\n" + + " `!hellofrom New York, NY`\n" + + " `!hellofrom Paris, France`\n" + + " `!hellofrom Tokyo`\n\n" + + "Notes:\n" + + "• You can specify a city by itself, or include a state or country for more precision.\n" + + "• The command will return the current date and time for the specified location."; + + new x_snc_slackerbot.Slacker().send_chat(current, helpText, true); +} + +function processHelloFromCommand(input) { + var location = processLocationString(input); + // You will need to create a global system property called 'timezone.token' and obtain an API key from https://ipgeolocation.io/ to use this parser. + var apiKey = gs.getProperty("timezone.token"); + + // Build the full endpoint + var baseUrl = 'https://api.ipgeolocation.io/timezone'; + var searchUrl = baseUrl + '?location=' + location + '&apiKey=' + apiKey; + + // Make the API request + var chatReq = new sn_ws.RESTMessageV2(); + chatReq.setEndpoint(searchUrl); + chatReq.setHttpMethod("GET"); + var chatResponse = chatReq.execute(); + var chatResponseBody = JSON.parse(chatResponse.getBody()); + var dateTimeTxt = chatResponseBody.date_time_txt; + + var response = "Hello from " + location + ". Our current date and time is " + dateTimeTxt; + + new x_snc_slackerbot.Slacker().send_chat(current, response, true); +} + +function processLocationString(input) { + // Remove "!hellofrom" and trim + var cleaned = input.replace(/^!hellofrom\s*/, '').trim(); + + // Limit to first location (up to first comma if present) + var limited = cleaned.split(',')[0].trim(); + + return limited; +} diff --git a/Parsers/Highlight Maintainers.js b/Parsers/Highlight Maintainers.js new file mode 100644 index 00000000..78cd8963 --- /dev/null +++ b/Parsers/Highlight Maintainers.js @@ -0,0 +1,12 @@ +/* +activation_example:!maintainers +regex:!hackmaintainers|!hacktobermaintainers|!maintainershacktoberfest|!maintainers +flags:gmi +*/ + +(function(){ + + var strMessage = 'Navigate to `https://github.com/ServiceNowDevProgram/Hacktoberfest?tab=readme-ov-file#reviewers` to checkout the Hacktoberfest crew who are maintaining the hacktoberfest projects ' + var send_chat = new x_snc_slackerbot.Slacker().send_chat(current, strMessage, false); + +})(); diff --git a/Parsers/Let a dungeon master determine your fate.js b/Parsers/Let a dungeon master determine your fate.js new file mode 100644 index 00000000..96b40b61 --- /dev/null +++ b/Parsers/Let a dungeon master determine your fate.js @@ -0,0 +1,49 @@ +/* +activation_example:!d20 I try to jump over a 10 foot gap +regex:!d20 +flags:gmi +*/ + +(function(current) { + + var prompt = current.text.replace(/!d20/gmi, "").trim().substring(0, 1000); + var chatReq = new sn_ws.RESTMessageV2(); + chatReq.setEndpoint('https://api.openai.com/v1/chat/completions'); + chatReq.setHttpMethod("POST"); + chatReq.setRequestHeader("Authorization", "Bearer " + gs.getProperty("openai.key")); + chatReq.setRequestHeader('Content-Type', "application/json"); + chatReq.setRequestHeader('User-Agent', "ServiceNow"); + chatReq.setRequestHeader("Accept", "*/*"); + var body = { + "model": "gpt-4o", + "messages": [{ + "role": "system", + "content": ` + You are a Slack bot that enhances the formatting of messages to make them more engaging and visually appealing. + Utilize Slack's markdown language mrkdwn for various formatting elements. + Follow these instructions: + 1. Enclose important words or phrases with *asterisks* for bold emphasis. + 2. Enclose code and numbers and percentages using backticks, like \`this\`. + 3. Use emojis when necessary to add expressiveness. + 4. Organize text with numbered or bullet lists, using "-" for bullet points. + 5. Combine bold and lists as needed: *Bold text*: normal text. + 6. Italicize words like _this_. + 7. Use blockquotes with ">" for quotes. + 8. For URLs, use . + 9. Keep user (@user) and channel (#channel) tags unchanged. + Keep close to the original message tone and formatting.` + }, + { + "role": "user", + "content": "You are a dungeon master and a player is about to roll a D20 to determine the outcome of an action they are pursuing. When they tell you the action they are trying to perform, first determine what the Difficulty Class (DC) score should be and then roll a d20 (a twenty sided dice) against that DC check and respond with what they rolled, the Difficulty Class (DC) you determined and why, and then add a detailed flavor text to go alongside their resulting roll to describe what happens as a result of their roll. A resulting 1 will always be a critical failure and a resulting 20 will always be a critical success, regardless of the DC score you have determined. If they roll a critical failure (a 1) or a critical success (a 20), the flavor text should be incredibly exaggerated to emphasize the extreme luck. Try to make non-critical rolls still interesting but reflective of the number rolled still. Here is what your player is trying to do, in their words: " + prompt + } + ], + }; + chatReq.setRequestBody(JSON.stringify(body)); + var chatResponse = chatReq.execute(); + gs.info(chatResponse.getBody()); + var chatResponseBody = JSON.parse(chatResponse.getBody()); + + var intro = "> " + prompt + "\n"; + new x_snc_slackerbot.Slacker().send_chat(current, intro + "\n" + chatResponseBody.choices[0].message.content, false); +})(current); diff --git a/Parsers/NDA.js b/Parsers/NDA.js new file mode 100644 index 00000000..f46276bc --- /dev/null +++ b/Parsers/NDA.js @@ -0,0 +1,23 @@ +/* +activation_example:!nda +regex:(!nda|!confidential) +flags:gmi +*/ + +//Provides a response to the strings that will cause the `Summarize the thread so far` parser to not produce a summary. + +var message = []; //Array of potential messages to respond with +message.push(":zipper_mouth_face:"); +message.push(":shushing_face:"); +message.push("I got you :fingerguns:"); +message.push("Lips are sealed"); +message.push(":spill-the-tea:"); +message.push(":popcorn2:"); +message.push(":speak_no_evil:"); +message.push("Request for confidentiality acknowledged. Use of !summary, !catchmeup, and !catchup have been disabled."); + + +//Select one at random. +var randomIndex = Math.floor(Math.random() * message.length); + +new x_snc_slackerbot.Slacker().send_chat(current, message[randomIndex], true); diff --git a/Parsers/News Article Parser.js b/Parsers/News Article Parser.js new file mode 100644 index 00000000..39fccf20 --- /dev/null +++ b/Parsers/News Article Parser.js @@ -0,0 +1,35 @@ +/* +activation_example:!news topic or story to search for +regex:!news +flags:gmi +*/ + +var term = current.text.replace("!news ", "").trim(); + +// Build the search URL using a public news API to search for a specific topic or story +var searchUrl = 'https://newsapi.org/v2/everything?q=' + encodeURIComponent(term) + '&apiKey=' + gs.getProperty("newsapi.key"); + +var chatReq = new sn_ws.RESTMessageV2(); +chatReq.setEndpoint(searchUrl); +chatReq.setHttpMethod("GET"); +var chatResponse = chatReq.execute(); +var chatResponseBody = chatResponse.getBody(); + +// Parse the API response as JSON +var responseData = JSON.parse(chatResponseBody); + +// Check if the API response contains any articles +if (responseData.articles.length > 0) { + // Extract information from the first article + var article = responseData.articles[0]; + var title = article.title; + var author = article.author || "Unknown author"; + var description = article.description.length > 255 ? article.description.substring(0, 252) + "... Read More" : article.description; + var url = article.url; + + // Send the extracted information as a Slack message + var message = "*Title:* " + title + "\n*Author:* " + author + "\n*Description:* " + description + "\n*URL:* " + url; + new x_snc_slackerbot.Slacker().send_chat(current, message, true); +} else { + new x_snc_slackerbot.Slacker().send_chat(current, "Sorry, I couldn't find any news articles related to \"" + term + "\".", true); +} diff --git a/Parsers/Random Number generator.js b/Parsers/Random Number generator.js new file mode 100644 index 00000000..0b58d264 --- /dev/null +++ b/Parsers/Random Number generator.js @@ -0,0 +1,18 @@ +/* +activation_example:!random 1 10 +regex:!random (\d+) (\d+) +flags:gmi +order:100 +stop_processing:false +active:true +*/ + +var matches = current.text.match(/!random (\d+) (\d+)/); +if (matches) { + var min = parseInt(matches[1], 10); + var max = parseInt(matches[2], 10); + var randomNumber = Math.floor(Math.random() * (max - min + 1)) + min; + new x_snc_slackerbot.Slacker().send_chat(current, `Random number between ${min} and ${max}: ${randomNumber}`, false); +} else { + new x_snc_slackerbot.Slacker().send_chat(current, 'Please use the format: !random [min] [max]', true); +} diff --git a/Parsers/Random fact generator.js b/Parsers/Random fact generator.js index b9af5431..426f0b60 100644 --- a/Parsers/Random fact generator.js +++ b/Parsers/Random fact generator.js @@ -3,7 +3,6 @@ activation_example:!fact regex:!fact flags:gmi */ -var prompt = current.text.replace(/!chatgpt/gmi, "").trim().substring(0, 1000); var chatReq = new sn_ws.RESTMessageV2(); chatReq.setEndpoint('http://numbersapi.com/random'); chatReq.setHttpMethod("GET"); diff --git a/Parsers/Reverse.js b/Parsers/Reverse.js new file mode 100644 index 00000000..2bce7c75 --- /dev/null +++ b/Parsers/Reverse.js @@ -0,0 +1,33 @@ +/* +activation_example:!reverse your string or not +regex:!reverse +flags:gmi +order:250 +stop_processing:false +*/ + +var uno = `⠀⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀ +⡈⠀⢀⣴⣦⢶⣴⣴⣦⢶⣴⢦⣶⡴⣦⡶⣴⣦⢶⣴⣤⡀⠀⠠⡀ +⠀⠀⣿⣟⣾⠗⢀⢸⣾⢿⣽⣻⣾⠻⠝⠛⢉⡈⠛⠻⢾⣿⠀⠀⠀ +⠀⠀⣿⣾⠷⠞⣹⡿⣯⢿⡞⠋⣡⣴⣾⢿⣟⡿⣟⣷⣄⠹⠀⠀⠀ +⠀⠀⣿⣯⣤⣼⡿⣽⡛⢁⣴⣿⣻⣽⡾⣯⡿⣽⣟⣷⣻⡆⠀⠀⠀ +⠀⠀⣿⣽⣻⢯⡟⠃⣴⣿⣻⢾⣽⣳⡿⣽⣻⣽⡾⣯⣷⢿⠀⠀⠀ +⠀⠀⣿⣳⡿⠏⣠⣿⣟⣷⣻⣯⣟⣷⣿⣻⣽⣷⣻⢷⣯⣿⠀⠀⠀ +⠀⠀⣿⣽⠃⣴⣿⣳⢿⣞⡿⣿⠆⠀⠀⢠⣿⣞⣯⣿⣳⡏⠀⠀⠀ +⠀⠀⣿⠃⣼⣻⢾⣽⣯⣿⠟⠁⠀⣠⣄⣸⣿⢾⣽⣞⣯⠅⠀⠀⠀ +⠀⠀⡇⣸⣷⣻⣯⣷⣻⣿⡀⣠⠞⠹⣿⣿⢯⣿⣞⣯⠏⢸⠀⠀⠀ +⠀⠀⢁⣿⣽⣯⢿⣽⡟⢿⠗⠉⠀⢠⣿⣽⢾⣯⣟⡿⢀⣾⠀⠀⠀ +⠀⠀⢸⣟⣾⡽⣯⣿⠀⠀⠀⣠⣶⣿⣻⢾⣟⣾⡽⢀⣾⣻⠀⠀⠀ +⠀⠀⣿⣟⡷⣿⣻⣿⣤⣤⣤⣬⣿⣷⣻⣯⢿⠞⢠⣾⢯⣿⠀⠀⠀ +⠀⠀⣿⣽⣻⢷⣟⣿⣻⣟⣿⣻⣟⡾⣷⡻⠋⣰⣿⢯⣿⣽⠀⠀⠀ +⠀⠀⢻⣷⣻⢿⣾⣳⡿⣾⡽⣷⣻⡽⠛⣠⣾⢿⡽⠿⢾⣽⠀⠀⠀ +⠀⠐⡌⢳⣿⣻⡾⣽⣻⢷⣟⠏⢃⣤⣾⢿⡽⡟⣁⣤⣾⣿⠀⠀⠀ +⠀⠀⣿⢦⣈⣑⠛⠛⣋⣁⣤⡾⣿⡽⣾⣻⡏⠛⣡⣾⣟⣾⠀⠀⠀ +⠃⠀⠙⢿⣯⣟⣿⣻⣯⣿⣽⣻⣷⣟⣯⣷⣷⣶⣿⣳⡯⠋⠀⠀⠀`; + +var string = current.text.replace(/!reverse/gmi, "").split('').reverse().join(''); +if (string == '') { + new x_snc_slackerbot.Slacker().send_chat(current, uno, false); +} else { + new x_snc_slackerbot.Slacker().send_chat(current, string, false); +} diff --git a/Parsers/Stranger things quote.js b/Parsers/Stranger things quote.js new file mode 100644 index 00000000..ff332a95 --- /dev/null +++ b/Parsers/Stranger things quote.js @@ -0,0 +1,22 @@ +/* +activation_example:!strangerthings +regex:!strangerthings +flags:gmi +*/ + +var input = current.text.trim(); +if (/!strangerthings/i.test(input)) { + var quote = new sn_ws.RESTMessageV2(); + quote.setEndpoint('https://strangerthings-quotes.vercel.app/api/quotes'); + quote.setHttpMethod('GET'); + + var chatResponse = quote.execute(); + var chatResponseBody = JSON.parse(chatResponse.getBody()); + + if (chatResponseBody && chatResponseBody.length > 0) { + var quoteText = chatResponseBody[0].quote; + var author = chatResponseBody[0].author; + + new x_snc_slackerbot.Slacker().send_chat(current, 'Here is a Stranger Things quote for you:\n "' + quoteText + '" - ' + author, false); + } +} diff --git a/Parsers/Summarize the thread so far.js b/Parsers/Summarize the thread so far.js index 10ad9aa0..21d73fdb 100644 --- a/Parsers/Summarize the thread so far.js +++ b/Parsers/Summarize the thread so far.js @@ -4,71 +4,116 @@ regex:!(catchmeup|summary|catchup) flags:gmi */ -( function( current ){ - var si = new Slacker(); - var thread_ts = current.getValue( 'thread_ts' ); +(function(current) { + function findWordAfterDashDash(str) { + let match = str.match(/--\s*(.+)/); + return match ? ` in the style of ${match[1]}` : ''; + } - // if( !thread_ts ){ - // message = 'This command is only usable in a thread.'; - // si.send_chat( current, message, false ); - // return null; - // } + const si = new x_snc_slackerbot.Slacker(); + const thread_ts = current.getValue('thread_ts'); + const style = findWordAfterDashDash(current.text); + const chats = []; + const userPronouns = []; + const users = []; + let nda = false; + let encodedQuery = ''; + if (thread_ts) { + encodedQuery = `thread_ts=${thread_ts}^ORts=${thread_ts}^textNOT LIKE!catchmeup^textNOT LIKE!summary^textNOT LIKE!catchup`; + } else { + encodedQuery = `channel=${current.getValue('channel')}^sys_created_onRELATIVEGT@minute@ago@60^thread_ts=NULL^textNOT LIKE!catchmeup^textNOT LIKE!summary^textNOT LIKE!catchup`; + } - function findWordAfterDashDash(str) { - var match = str.match(/--\s*(.+)/); - return match ? " in the style of " + match[1] : ""; - } - var style = findWordAfterDashDash(current.text); - - var chats = []; - var nda = false; - var chatGr = new GlideRecord('x_snc_pointsthing_chat'); - if (thread_ts){ - chatGr.addEncodedQuery('thread_ts=' + thread_ts + '^ORts=' + thread_ts + '^textNOT LIKE!catchmeup^textNOT LIKE!summary^textNOT LIKE!catchup'); - } else { - chatGr.addEncodedQuery('channel=' + current.getValue('channel') + '^sys_created_onONLast 30 minutes@javascript:gs.beginningOfLast30Minutes()@javascript:gs.endOfLast30Minutes()^thread_ts=NULL^textNOT LIKE!catchmeup^textNOT LIKE!summary^textNOT LIKE!catchup') - } - chatGr.orderBy('sys_created_on'); - chatGr.query(); - while (chatGr.next()){ - var chat = chatGr.getDisplayValue('user') + ': ' + chatGr.getValue('text'); - if (chat.indexOf('!nda') > -1 || chat.indexOf('!confidential') > -1) { - nda = true; - break; - } - chats.push(chat); - } - if(nda){ - if (thread_ts){ - new x_snc_slackerbot.Slacker().send_chat(current, "This thread has been marked confidential and cannot be summarized.", false); - } else { - new x_snc_slackerbot.Slacker().send_chat(current, "This recent conversation has been marked confidential and cannot be summarized.", false); - } - return; - } + const chatGr = new GlideRecord('x_snc_slackerbot_chat'); + chatGr.addEncodedQuery(encodedQuery); + chatGr.orderBy('sys_created_on'); + chatGr.query(); - var prompt = current.text.replace(/!chatgpt/gmi, "").trim().substring(0, 1000); - var chatReq = new sn_ws.RESTMessageV2(); - chatReq.setEndpoint('https://api.openai.com/v1/chat/completions'); - chatReq.setHttpMethod("POST"); - chatReq.setRequestHeader("Authorization", "Bearer " + gs.getProperty("openai.key")); - chatReq.setRequestHeader('Content-Type', "application/json"); - chatReq.setRequestHeader('User-Agent', "ServiceNow"); - chatReq.setRequestHeader("Accept", "*/*"); - var body = { - "model": "gpt-4o", - "messages": [{"role": "user", "content": "summarize the following conversation" + style + ". You cannot ask for follow-up responses. Ignore the user named Slackbot.\n\n" + chats.join("\n")}], - // "max_tokens": 250 - }; - chatReq.setRequestBody(JSON.stringify(body)); - var chatResponse = chatReq.execute(); - gs.info(chatResponse.getBody()); - var chatResponseBody = JSON.parse(chatResponse.getBody()); + while (chatGr.next()) { + const chat = `${chatGr.getDisplayValue('user')}: ${chatGr.getValue('text')}`; + if (chat.indexOf('!nda') > -1 || chat.indexOf('!confidential') > -1) { + nda = true; + break; + } + chats.push(chat); + } + + if (nda) { + if (thread_ts) { + si.send_chat(current, 'This thread has been marked confidential and cannot be summarized.', false); + } else { + si.send_chat(current, 'This recent conversation has been marked confidential and cannot be summarized.', false); + } + return; + } - var show_tokens = false; - var token_cost = show_tokens ? "> tokens: " + chatResponseBody.usage.total_tokens + " ($" + (parseInt(chatResponseBody.usage.total_tokens) * 0.000002).toFixed(6) + ")\n" : ""; + const userGa = new GlideAggregate('x_snc_slackerbot_chat'); + userGa.addEncodedQuery(encodedQuery); + userGa.addAggregate('GROUP_CONCAT_DISTINCT','user.user_id'); + userGa.query(); - var intro = thread_ts ? "> This thread so far:\n" : "> Last 30 minutes summarized:\n"; - new x_snc_slackerbot.Slacker().send_chat(current, intro + token_cost + "\n" + chatResponseBody.choices[0].message.content, false); -} )( current ); + while(userGa.next()){ + const userId = userGa.getAggregate('GROUP_CONCAT_DISTINCT','user.user_id'); + + const rm = new sn_ws.RESTMessageV2(); + rm.setHttpMethod('GET'); + rm.setLogLevel('all'); + rm.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + rm.setRequestHeader('authorization', `Bearer ${gs.urlEncode(gs.getProperty('x_snc_slackerbot.SlackerBot.token'))}`); + rm.setEndpoint(`https://slack.com/api/users.info?user=${userId}`); + const response = rm.execute(); + const responseBody = JSON.parse(response.getBody()); + const pronouns = responseBody.user.profile.pronouns ?? 'they/them'; + const name = responseBody.user.real_name ?? 'Unknown'; + userPronouns.push(`${name}: ${pronouns}`); + users.push(name); + } + + const chatReq = new sn_ws.RESTMessageV2(); + chatReq.setEndpoint('https://api.openai.com/v1/chat/completions'); + chatReq.setHttpMethod('POST'); + chatReq.setRequestHeader('Authorization', `Bearer ${gs.getProperty('openai.key')}`); + chatReq.setRequestHeader('Content-Type', 'application/json'); + chatReq.setRequestHeader('User-Agent', 'ServiceNow'); + chatReq.setRequestHeader('Accept', '*/*'); + const body = { + 'model': 'gpt-4o', + 'messages': [{ + 'role': 'system', + 'content': ` + You are a Slack bot that enhances the formatting of messages to make them more engaging and visually appealing. + Utilize Slack's markdown language mrkdwn for various formatting elements. + Follow these instructions: + 1. Enclose important words or phrases with *asterisks* for bold emphasis. + 2. Enclose code and numbers and percentages using backticks, like \`this\`. + 3. Use emojis when necessary to add expressiveness. + 4. Organize text with numbered or bullet lists, using '-' for bullet points. + 5. Combine bold and lists as needed: *Bold text*: normal text. + 6. Italicize words like _this_. + 7. Use blockquotes with '>' for quotes. + 8. For URLs, use . + 9. Keep user (@user) and channel (#channel) tags unchanged. + Keep close to the original message tone and formatting. + + Use the below mapping between user name and pronouns to ensure you use the correct pronoun: + ${userPronouns.join('\n')}` + }, + { + 'role': 'user', + 'content': `summarize the following conversation${style}. You cannot ask for follow-up responses. Ignore the user named Slackbot.\n\n${chats.join('\n')}` + } + ], + // 'max_tokens': 250 + }; + chatReq.setRequestBody(JSON.stringify(body)); + const chatResponse = chatReq.execute(); + gs.info(chatResponse.getBody()); + const chatResponseBody = JSON.parse(chatResponse.getBody()); + + const show_tokens = false; + const token_cost = show_tokens ? `> tokens: ${chatResponseBody.usage.total_tokens} ($${(parseInt(chatResponseBody.usage.total_tokens) * 0.000002).toFixed(6)})\n` : ''; + + const intro = thread_ts ? '> This thread so far:\n' : '> Last 60 minutes summarized:\n'; + si.send_chat(current, `${intro}\nInvolved users:\n${users.join('\n')}\n${token_cost}\n${chatResponseBody.choices[0].message.content}`, false); +})(current); diff --git a/Parsers/Translator.js b/Parsers/Translator.js new file mode 100644 index 00000000..a41e8d7b --- /dev/null +++ b/Parsers/Translator.js @@ -0,0 +1,67 @@ +/* +activation_example:!translate Hello world to Spanish +regex:!translate\s+(.+?)\s+(?:to|into|in)\s+(\w+)$ +flags:gi +*/ + +var regex = /!translate\s+(.+?)\s+(?:to|into|in)\s+(\w+)$/gi; +var match = regex.exec(current.text); + +if (match && match[1] && match[2]) { + var phrase = match[1].trim(); + var language = match[2].trim().toLowerCase(); + + var languageMap = { + 'afrikaans': 'af', + 'arabic': 'ar', + 'chinese': 'zh', + 'dutch': 'nl', + 'french': 'fr', + 'german': 'de', + 'hindi': 'hi', + 'italian': 'it', + 'japanese': 'jpn', + 'korean': 'ko', + 'malay': 'my', + 'pakistani': 'ur', + 'portuguese': 'pt', + 'spanish': 'es', + 'turkish': 'tr' + }; + + var targetLanguageCode = languageMap[language]; + + if (!targetLanguageCode) { + new x_snc_slackerbot.Slacker().send_chat(current, 'Sorry, I do not support the language "' + language + '".', false); + return; + } + + var apiUrl = 'https://api.mymemory.translated.net/get'; + var requestBody = { + 'q': phrase, + 'langpair': 'en|' + targetLanguageCode + }; + + var restMessage = new sn_ws.RESTMessageV2(); + restMessage.setHttpMethod('GET'); + restMessage.setEndpoint(apiUrl + '?q=' + encodeURIComponent(phrase) + '&langpair=en|' + targetLanguageCode); + + try { + var httpResponse = restMessage.execute(); + var httpResponseStatus = httpResponse.getStatusCode(); + + if (httpResponseStatus === 200) { + var translationData = JSON.parse(httpResponse.getBody()); + var translatedText = translationData.responseData.translatedText; + var responseMessage = phrase + 'translated into ' + language + ' is:\n\n ' + translatedText; + new x_snc_slackerbot.Slacker().send_chat(current, responseMessage, false); + } else { + new x_snc_slackerbot.Slacker().send_chat(current, 'Sorry, I could not translate the phrase "' + phrase + '" to ' + language + '.', false); + } + } catch (error) { + new x_snc_slackerbot.Slacker().send_chat(current, 'There was an error with the translation service. Please try again later.', false); + } +} else { + new x_snc_slackerbot.Slacker().send_chat(current, 'Please use the format `!translate [phrase] to/into/in [language]` to translate text.', false); +} + diff --git a/Parsers/Weather Report Generator.js b/Parsers/Weather Report Generator.js index d5861623..25647cf5 100644 --- a/Parsers/Weather Report Generator.js +++ b/Parsers/Weather Report Generator.js @@ -3,6 +3,7 @@ activation_example:!weather london regex:!weather flags:gmi */ + var wordCount = current.text.replace('!weather', '').trim().split(' '); if (wordCount[0] != '') { @@ -12,7 +13,19 @@ if (wordCount[0] != '') { rm.setLogLevel('all'); rm.setEndpoint('https://wttr.in/' + wordCount[0] + '?format=4'); var response = rm.execute(); - new x_snc_slackerbot.Slacker().send_chat(current, response.getBody()); + var weatherBody = response.getBody(); + + var locrm = new sn_ws.RESTMessageV2(); + locrm.setHttpMethod('GET'); + locrm.setLogLevel('all'); + locrm.setEndpoint('https://wttr.in/' + wordCount[0]); + var locResponse = locrm.execute(); + var locBody = locResponse.getBody(); + var matches = locBody.match(/Location: ([^[]+)\s/); + + if (matches[1]) weatherBody = weatherBody.replace(/[^:]+/, matches[1]); + + new x_snc_slackerbot.Slacker().send_chat(current, weatherBody); } else { new x_snc_slackerbot.Slacker().send_chat(current, 'Please Provide Location Ex: "!weather london"'); } diff --git a/Parsers/What time is it.js b/Parsers/What time is it.js index 0d3f4beb..2dcefbf7 100644 --- a/Parsers/What time is it.js +++ b/Parsers/What time is it.js @@ -5,13 +5,13 @@ flags:gmi */ var term = /^(?:!time )([^\s]+)/gmi.exec(current.text); -var astrid = /!astrid/.test(current.text) +var astrid = /!astrid/.test(current.text); var tz = ""; var text = ""; var tza = getTimeZoneArray(); var endText = ""; -if (astrid) { +if (!tz || !tza || tza.indexOf(tz.toLowerCase()) == -1) { tz = "Australia/Sydney"; endText = "Astrid time!"; } @@ -25,26 +25,26 @@ if (!tz || !tza || tza.indexOf(tz.toLowerCase()) == -1) { new x_snc_slackerbot.Slacker().send_chat(current, text, true); } else { - var uri = "https://worldtimeapi.org/api/timezone/" + var uri = "https://timeapi.io/api/time/current/zone?timeZone="; var rm = new sn_ws.RESTMessageV2(); rm.setEndpoint(uri + tz); rm.setHttpMethod('get'); response = rm.execute(); rb = JSON.parse(response.getBody()); - var timeString = rb.datetime.split('T')[1].split('.')[0]; + var timeString = rb.dateTime.split('T')[1].split('.')[0]; var gd = new GlideDate(); - gd.setValue(rb.datetime.replace('T',' ')); + gd.setValue(rb.dateTime.replace('T',' ')); dateString = gd.getByFormat('EEEE, MMMM dd, yyyy'); - endText = !endText ? rb.timezone : endText; - var text = 'It is currently ' + timeString + ' on ' + dateString + ' in ' + endText; + endText = !endText ? rb.timeZone : endText; + text = 'It is currently ' + timeString + ' on ' + dateString + ' in ' + endText; new x_snc_slackerbot.Slacker().send_chat(current, text, false); } function getTimeZoneArray(){ - var uri = "https://worldtimeapi.org/api/timezone/" + var uri = "https://timeapi.io/api/timezone/availabletimezones"; var rm = new sn_ws.RESTMessageV2(); rm.setEndpoint(uri); rm.setHttpMethod('get'); response = rm.execute(); - return JSON.parse(response.getBody()).map(function(e){return e.toLowerCase()}); + return JSON.parse(response.getBody()).map(function(e){return e.toLowerCase();}); } diff --git a/Parsers/Word Dictionary.js b/Parsers/Word Dictionary.js new file mode 100644 index 00000000..4ca4e30e --- /dev/null +++ b/Parsers/Word Dictionary.js @@ -0,0 +1,37 @@ +/* +activation_example:!dictionary type a word +regex:!dictionary +flags:gmi +order:200 +stop_processing:false +*/ + +var word = current.text.replace(/!dictionary/gmi, "").trim(); +if (word == '') { + new x_snc_slackerbot.Slacker().send_chat(current, 'Please type a word', false); +} else { + var regex = /[0-9!@#$%^&*(),.?":{}|<>]/; + if (regex.test(word)) { + new x_snc_slackerbot.Slacker().send_chat(current, 'Please type a word with only characters', false); + } else { + var wordsArray = word.split(" "); + var firstWord = wordsArray[0]; + + var dictionary = new sn_ws.RESTMessageV2(); + dictionary.setEndpoint('https://api.dictionaryapi.dev/api/v2/entries/en/' + firstWord); + dictionary.setHttpMethod("GET"); + dictionary.setRequestHeader("Accept", "application/json"); + var response = dictionary.execute(); + var responseBody = response.getBody(); + var statuscode = response.getStatusCode(); + var responseJson = JSON.parse(responseBody); + var sendsentence = ""; + if (statuscode == 200) { + var meaning = responseJson[0].meanings[0].definitions[0].definition; + sendsentence = firstWord + ': ' + meaning; + } else { + sendsentence = "Sorry pal, we couldn't find definitions for the word you were looking for."; + } + new x_snc_slackerbot.Slacker().send_chat(current, sendsentence, false); + } +} \ No newline at end of file diff --git a/Parsers/fetch IP Address Information.js b/Parsers/fetch IP Address Information.js new file mode 100644 index 00000000..20cbdac5 --- /dev/null +++ b/Parsers/fetch IP Address Information.js @@ -0,0 +1,52 @@ +/* +activation_example:!ipinfo +regex:^!ipinfo +flags:i +*/ + +var input = current.text.trim(); // getting the user input + +// regular expression to check the correct IP Address +var regexp = /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/; + +// regular expression to fetch the IP Address from information added by user +var extractedip = input.match(/\b(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b/g); + + +var ipaddress = extractedip ? extractedip : " "; // set the value of ipaddress with the ipaddress given by user or remain empty + + + +// fetch the IP Address Information like(city,region,country,loc,org,postal,timezone) from API and return result +function fetchinfo(ipaddress) { + var url = "https://ipinfo.io/" + ipaddress + "/json"; + var r = new sn_ws.RESTMessageV2(); + r.setEndpoint(url); + r.setHttpMethod('GET'); + var ipinfo = r.execute(); + var result = JSON.parse(ipinfo.getBody()); + return result; +} + +try { + if (regexp.test(ipaddress)) { + var ipdata = fetchinfo(ipaddress); // calling the fetchinfo function + var ipInformationSlackMessage = "*IP Information:*\n" + + "• *IP*: " + ipdata.ip + "\n" + + "• *City*: " + ipdata.city + "\n" + + "• *Region*: " + ipdata.region + "\n" + + "• *Country*: " + ipdata.country + "\n" + + "• *Location*: " + ipdata.loc + "\n" + + "• *Organization*: " + ipdata.org + "\n" + + "• *Postal Code*: " + ipdata.postal + "\n" + + "• *Timezone*: " + ipdata.timezone; //formatting for slack mark down + new x_snc_slackerbot.Slacker().send_chat(current, ipInformationSlackMessage , false); // display the output to user + + } else { + new x_snc_slackerbot.Slacker().send_chat(current, "Oops! I couldn't understand that. Please provide a valid IP Address.", false); // Message to show when IP is invalid + } + + +} catch (error) { + new x_snc_slackerbot.Slacker().send_chat(current, "Oops! Facing Issue while fetching information about IP Address.", false); //handling exception in try block +} diff --git a/Parsers/weekMoji.js b/Parsers/weekMoji.js new file mode 100644 index 00000000..760036ca --- /dev/null +++ b/Parsers/weekMoji.js @@ -0,0 +1,21 @@ +/* +emoji:calendar +*/ + +new x_snc_slackerbot.Slacker().send_reaction(current, weeklyMoji()); + +function weeklyMoji() { + var weekEmojis = { + 0: 'person_in_lotus_position', // Sunday - calm and relaxation + 1: 'coffee', // Monday - getting over weekend with coffee + 2: 'chart_with_upwards_trend', // Tuesday - Moving forward through week + 3: 'climbing', // Wednesday - Work and only work + 4: 'rocket', // Thursday - Powering through tasks and finishing them + 5: 'beers', // Friday - Chilling and enjoyment + 6: 'beach_with_umbrella' // Saturday - Rest; friends and faily time + }; + + var day = new GlideDateTime().getDayOfWeekUTC(); // Get the current day of the week (0 = Sunday, 6 = Saturday) + return weekEmojis[day]; +} +//Added script for weekly emoji reactions diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/author_elective_update/sys_choice_x_snc_slackerbot_payload_payload_type.xml b/b02cf9e61b861d10d806dc23b24bcb3f/author_elective_update/sys_choice_x_snc_slackerbot_payload_payload_type.xml new file mode 100644 index 00000000..89714ad4 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/author_elective_update/sys_choice_x_snc_slackerbot_payload_payload_type.xml @@ -0,0 +1,57 @@ + + + + payload_type + x_snc_slackerbot_payload + sys_choice_set + asteroid + 2024-10-20 08:14:42 + 0 + payload_type + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_choice_x_snc_slackerbot_payload_payload_type + asteroid + 2024-10-20 08:14:42 + + + + payload_type + + false + + en + x_snc_slackerbot_payload + 1 + + asteroid + 2024-10-20 08:14:42 + global + / + 0 + asteroid + 2024-10-20 08:14:42 + event + + + + payload_type + + false + + en + x_snc_slackerbot_payload + 2 + + asteroid + 2024-10-20 08:15:43 + global + / + 0 + asteroid + 2024-10-20 08:15:43 + interaction + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/checksum.txt b/b02cf9e61b861d10d806dc23b24bcb3f/checksum.txt index 6c283f63..39f1d464 100644 --- a/b02cf9e61b861d10d806dc23b24bcb3f/checksum.txt +++ b/b02cf9e61b861d10d806dc23b24bcb3f/checksum.txt @@ -1 +1 @@ -TxUn7YBBzC4y4yBIwhOZOYoM2cD_taHAd9fX1TpmfahkXhCExmWH7RSJrClFWpNmGsR0euG2IfjhD9Acnry5qzhv5ZQ3PC1YhbRBHHB6vZQoUPhMPrl1wEaYf0Vsw1yfaZxJOXFKflo4fi7esV0qZCjmPKTQhfQVoaUjDit1ojAKbQRPBwTIEIYTrR_XqmKf3mHetIbRioJsxPYjemSb5RVyQiD2sBkLl3Gx7qzpArG1fxreb2794G23RvMfpOhK3jz9xipYa7IaYeA29E1gTi5-_6xPF_uMzCw3NGMssDvqV2UBj4cAfG4cPQhQcHy0_D6Qo6AFvfR2d9ADjm0lq4ujS6u1vRX2lcVTdkjZAWU1F05AhdymVeNbKj0fTceGbjk4z_92tCKi8k6pPvQTHzsD8n4qdZlbb_AEItWqOrSpGlSlnOA6g7UpuWSq-e2-R2K_fMsILwm8AJqLdnnO6YWodvito7u1JGaHeH6bxHqw3hm9ykU9yRiw8rPpdononPNW80emjGAGw9IkuRsgFdyewxrbk_B5yA3R6dwccQgGMLd9eydajIHkLNAflIqyTdsBqKnbkHJWYwvflnD_pdSM2r4Jcp5rWlmn9c_He4O0B5FW-KpSZhPKyU2AaV4-osOezFMv9ca8O0s5eZIv5L0MPkW-CK58oeICX6wycgc \ No newline at end of file +FMnDKTnj29fo7iD7UYS-j9oTOC9uOUZMzH7pdgqrpZlwybDRakyvFFtYDU2x-diGkBhb-u0ra_PQAKbCmw4z5v1QQKQRd3JnIJUmsD2xGIvHNSaPXusgnhRsHiwukXErp6JS442uoQlacx0vvr6qFN1arcEVAkaf5785evXIBe0cy--L8pearIgGD3funyWQDUNyyXs1sENqQFZP3Q3Mv1T1KCPNQiNf3_sQ3Zi9LrF_F2f8mEKImMd0UbSzmSHu5LBqDcXZMe_fYqoxv24VSGlUjTKiE4lHuNH2Wdr_T3ULFHsErPvH-xb3IbyYE9F2CrAh3oNGIMRZIn7xMV3ZnD1PzYE3L7hKElI3fabgQSU9Y8p2MB_EopbzNZCeS_pKafjtXUefvCo1FfFCQc9DLzqjKhAdDPoxf9hkYZDgp3DEFwmIa7rxjyQCEApwHGK_vikKhJVOMUsXkc4Pe593TJ-nVWMDdCSUXRrwLOOJLRSsJy2zF1PCd8tAv8JW2v-jtAYqQZ26huFKKJIoF0AlFjeTCC5ajVOOAT3ndoQa__routNl2yk2x8hB-tUvHFKtbPE6f5dlnmcwwlZzzBwmeJ0JbKNOipTRwfCaclYo6LJK3NHMF2wR5pYt4TPm5qIoyj0mtJHKk9H88b6Qza0yx12mXp3W_S7HjtVCG7SVg2Q \ No newline at end of file diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_interaction.xml b/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_interaction.xml new file mode 100644 index 00000000..652a2c2d --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_interaction.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_parser.xml b/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_parser.xml index 3eda189c..f0ea1886 100644 --- a/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_parser.xml +++ b/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_parser.xml @@ -1,14 +1,14 @@ - + + - - - - + + + + - diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_payload.xml b/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_payload.xml index 430e94ad..d2897ac8 100644 --- a/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_payload.xml +++ b/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_payload.xml @@ -1,7 +1,13 @@ - + + + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_user.xml b/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_user.xml index a58005ba..2b5f0315 100644 --- a/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_user.xml +++ b/b02cf9e61b861d10d806dc23b24bcb3f/dictionary/x_snc_slackerbot_user.xml @@ -1,10 +1,11 @@ - - - + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_app_module_82da0f3bc3d15a5055bdd523e40131ae.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_app_module_82da0f3bc3d15a5055bdd523e40131ae.xml new file mode 100644 index 00000000..d0ba2635 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_app_module_82da0f3bc3d15a5055bdd523e40131ae.xml @@ -0,0 +1,43 @@ + + + true + b56dfd2a1b861d10d806dc23b24bcb52 + + + + + + + LIST + + Interactions + Mobile + x_snc_slackerbot_interaction + + false + + + true + + sys_app_module + asteroid + 2024-10-24 10:21:28 + global + / + 82da0f3bc3d15a5055bdd523e40131ae + 0 + Interactions + + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_app_module_82da0f3bc3d15a5055bdd523e40131ae + asteroid + 2024-10-24 10:21:28 + + Interactions + false + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_db_object_ee0be417c3dd165055bdd523e401314f.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_db_object_ee0be417c3dd165055bdd523e401314f.xml new file mode 100644 index 00000000..6f7af0e1 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_db_object_ee0be417c3dd165055bdd523e401314f.xml @@ -0,0 +1,37 @@ + + + + true + true + + true + false + true + false + false + false + + false + x_snc_slackerbot_interaction + + + true + false + + sys_db_object + asteroid + 2024-10-22 09:25:37 + ee0be417c3dd165055bdd523e401314f + 0 + Slackerbot Interaction + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_db_object_ee0be417c3dd165055bdd523e401314f + asteroid + 2024-10-22 09:25:37 + true + + true + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_action_id_regex.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_action_id_regex.xml new file mode 100644 index 00000000..0c2daeb2 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_action_id_regex.xml @@ -0,0 +1,74 @@ + + + true + false + + false + + + + + Action ID regex + + + + + + + + false + false + + + + action_id_regex + false + + + + false + string + false + 128 + x_snc_slackerbot_interaction + + false + false + + + + false + + + + + false + false + sys_dictionary + asteroid + 2024-10-22 09:33:05 + Action ID regex + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_dictionary_x_snc_slackerbot_interaction_action_id_regex + asteroid + 2024-10-22 09:36:34 + false + false + false + false + false + simple + false + script + + + false + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_active.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_active.xml new file mode 100644 index 00000000..0d334acb --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_active.xml @@ -0,0 +1,74 @@ + + + true + false + + false + + + + + Active + + + true + + + + + false + false + + + + active + false + + + true + false + boolean + false + 40 + x_snc_slackerbot_interaction + + false + false + + + + false + + + + + false + false + sys_dictionary + asteroid + 2024-10-22 09:29:59 + Active + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_dictionary_x_snc_slackerbot_interaction_active + asteroid + 2024-10-22 09:29:59 + false + false + false + false + false + simple + false + script + + + false + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_description.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_description.xml new file mode 100644 index 00000000..0e895147 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_description.xml @@ -0,0 +1,74 @@ + + + true + false + + false + + + + + Description + + + + + + + + true + false + + + + description + false + + + + false + string + false + 128 + x_snc_slackerbot_interaction + + false + false + + + + false + + + + + false + false + sys_dictionary + asteroid + 2024-10-22 09:30:26 + Description + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_dictionary_x_snc_slackerbot_interaction_description + asteroid + 2024-10-22 09:36:57 + false + false + false + false + false + simple + false + script + + + false + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_null.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_null.xml new file mode 100644 index 00000000..d6560510 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_null.xml @@ -0,0 +1,69 @@ + + + true + false + + false + + 0 + + + + + + + + + + + false + false + + + + + false + + + + false + collection + false + 40 + x_snc_slackerbot_interaction + + false + false + + + + false + + + + + false + false + sys_dictionary + asteroid + 2024-10-22 09:25:37 + x_snc_slackerbot_interaction + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_dictionary_x_snc_slackerbot_interaction_null + asteroid + 2024-10-22 09:25:37 + false + false + false + false + false + simple + false + script + + + false + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_related_parsers.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_related_parsers.xml new file mode 100644 index 00000000..9645bd20 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_related_parsers.xml @@ -0,0 +1,74 @@ + + + true + false + + false + + + + + Related parsers + + + + + + + + false + false + + + + related_parsers + false + + + + false + glide_list + false + 4000 + x_snc_slackerbot_interaction + + false + false + + x_snc_slackerbot_parser + + false + + + + + false + false + sys_dictionary + asteroid + 2024-10-22 09:35:15 + Related parsers + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_dictionary_x_snc_slackerbot_interaction_related_parsers + asteroid + 2024-10-22 09:35:15 + false + false + false + false + false + simple + false + script + + + false + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_script.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_script.xml new file mode 100644 index 00000000..20a3076c --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_interaction_script.xml @@ -0,0 +1,74 @@ + + + true + false + + false + + + + + Script + + + + + + + + false + false + + + + script + false + + + + false + script + false + 8000 + x_snc_slackerbot_interaction + + false + false + + + + false + + + + + false + false + sys_dictionary + asteroid + 2024-10-22 09:31:41 + Script + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_dictionary_x_snc_slackerbot_interaction_script + asteroid + 2024-10-22 09:31:41 + false + false + false + false + false + simple + false + script + + + false + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_emoji.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_emoji.xml index db461205..34911b78 100644 --- a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_emoji.xml +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_emoji.xml @@ -2,11 +2,10 @@ true false - false - edge_encryption_enabled=true + edge_encryption_enabled=true,trim_value=true false - 0 + Emoji @@ -25,6 +24,7 @@ emoji false + false string @@ -42,21 +42,18 @@ - false false sys_dictionary earl.duque 2022-10-01 06:29:44 - c2379b45db565510791d8f8d1396195b - 0 Emoji b02cf9e61b861d10d806dc23b24bcb3f b02cf9e61b861d10d806dc23b24bcb3f sys_dictionary_x_snc_slackerbot_parser_emoji - earl.duque - 2022-10-01 06:29:44 + admin + 2024-10-10 19:02:14 false false false @@ -64,6 +61,7 @@ false simple false + script false diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_flags.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_flags.xml index 5bb4c180..f9bc68d7 100644 --- a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_flags.xml +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_flags.xml @@ -2,7 +2,7 @@ true false - + trim_value=true false b02cf9e61b861d10d806dc23b24bcb3f sys_dictionary_x_snc_slackerbot_parser_flags + admin + 2024-10-10 19:02:27 false false false diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_regex.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_regex.xml index e7e54c2e..0efce052 100644 --- a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_regex.xml +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_parser_regex.xml @@ -2,7 +2,7 @@ true false - + trim_value=true false b02cf9e61b861d10d806dc23b24bcb3f sys_dictionary_x_snc_slackerbot_parser_regex + admin + 2024-10-10 19:02:23 false false false diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_payload_payload_type.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_payload_payload_type.xml new file mode 100644 index 00000000..e7e4da98 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_payload_payload_type.xml @@ -0,0 +1,74 @@ + + + true + false + + false + + 1 + + + Payload type + + + event + + + + + false + false + + + + payload_type + false + + + + false + choice + false + 40 + x_snc_slackerbot_payload + + false + false + + + + false + + + + + false + false + sys_dictionary + asteroid + 2024-10-20 08:14:03 + Payload type + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_dictionary_x_snc_slackerbot_payload_payload_type + asteroid + 2024-10-20 08:16:18 + false + false + false + false + false + simple + false + script + + + false + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_user_pronouns.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_user_pronouns.xml new file mode 100644 index 00000000..e87f1e2f --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_dictionary_x_snc_slackerbot_user_pronouns.xml @@ -0,0 +1,74 @@ + + + true + false + + false + + + + + Pronouns + + + + + + + + false + false + + + + pronouns + false + + + + false + string + false + 128 + x_snc_slackerbot_user + + false + false + + + + false + + + + + false + false + sys_dictionary + admin + 2024-11-01 02:13:23 + Pronouns + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_dictionary_x_snc_slackerbot_user_pronouns + admin + 2024-11-01 02:13:54 + false + false + false + false + false + simple + false + script + + + false + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_action_id_regex_en.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_action_id_regex_en.xml new file mode 100644 index 00000000..75699500 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_action_id_regex_en.xml @@ -0,0 +1,26 @@ + + + + action_id_regex + + + + en + x_snc_slackerbot_interaction + + sys_documentation + asteroid + 2024-10-22 09:33:05 + 5 + Action ID regex + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_documentation_x_snc_slackerbot_interaction_action_id_regex_en + asteroid + 2024-10-22 09:36:34 + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_active_en.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_active_en.xml new file mode 100644 index 00000000..b4153100 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_active_en.xml @@ -0,0 +1,26 @@ + + + + active + + + + en + x_snc_slackerbot_interaction + + sys_documentation + asteroid + 2024-10-22 09:29:59 + 3 + Active + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_documentation_x_snc_slackerbot_interaction_active_en + asteroid + 2024-10-22 09:29:59 + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_description_en.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_description_en.xml new file mode 100644 index 00000000..51e89627 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_description_en.xml @@ -0,0 +1,26 @@ + + + + description + + + + en + x_snc_slackerbot_interaction + + sys_documentation + asteroid + 2024-10-22 09:30:26 + 7 + Description + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_documentation_x_snc_slackerbot_interaction_description_en + asteroid + 2024-10-22 09:36:57 + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_related_parsers_en.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_related_parsers_en.xml new file mode 100644 index 00000000..1ba8fbfb --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_related_parsers_en.xml @@ -0,0 +1,26 @@ + + + + related_parsers + + + + en + x_snc_slackerbot_interaction + + sys_documentation + asteroid + 2024-10-22 09:35:16 + 3 + Related parsers + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_documentation_x_snc_slackerbot_interaction_related_parsers_en + asteroid + 2024-10-22 09:35:16 + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_script_en.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_script_en.xml new file mode 100644 index 00000000..2bc5f721 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_interaction_script_en.xml @@ -0,0 +1,26 @@ + + + + script + + + + en + x_snc_slackerbot_interaction + + sys_documentation + asteroid + 2024-10-22 09:31:41 + 3 + Script + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_documentation_x_snc_slackerbot_interaction_script_en + asteroid + 2024-10-22 09:31:41 + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_payload_payload_type_en.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_payload_payload_type_en.xml new file mode 100644 index 00000000..61267378 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_payload_payload_type_en.xml @@ -0,0 +1,26 @@ + + + + payload_type + + + + en + x_snc_slackerbot_payload + Payload types + sys_documentation + asteroid + 2024-10-20 08:14:04 + 0 + Payload type + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_documentation_x_snc_slackerbot_payload_payload_type_en + asteroid + 2024-10-20 08:14:04 + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_user_pronouns_en.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_user_pronouns_en.xml new file mode 100644 index 00000000..646e3f53 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_documentation_x_snc_slackerbot_user_pronouns_en.xml @@ -0,0 +1,26 @@ + + + + pronouns + + + + en + x_snc_slackerbot_user + Pronouns + sys_documentation + admin + 2024-11-01 02:13:23 + 0 + Pronouns + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_documentation_x_snc_slackerbot_user_pronouns_en + admin + 2024-11-01 02:13:23 + + + + + diff --git a/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_script_36471c93c35d165055bdd523e40131ab.xml b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_script_36471c93c35d165055bdd523e40131ab.xml new file mode 100644 index 00000000..22ec5cd6 --- /dev/null +++ b/b02cf9e61b861d10d806dc23b24bcb3f/update/sys_script_36471c93c35d165055bdd523e40131ab.xml @@ -0,0 +1,86 @@ + + + false + package_private + false + true + false + false + true + false + true + false + false + x_snc_slackerbot_payload + + + false + payload_type=interaction^EQ + + + false + + Parse interactivity + 100 + 100 + + + + + + + + sys_script + asteroid + 2024-10-22 09:22:47 + global + / + 36471c93c35d165055bdd523e40131ab + 2 + Parse interactivity + + b02cf9e61b861d10d806dc23b24bcb3f + + b02cf9e61b861d10d806dc23b24bcb3f + sys_script_36471c93c35d165055bdd523e40131ab + asteroid + 2024-10-23 09:50:01 +