def appVersion() { return "4.0.0" } /** * GCal Search * https://raw.githubusercontent.com/HubitatCommunity/Google_Calendar_Search/main/Apps/GCal_Search.groovy * * Credits: * Originally posted on the SmartThings Community in 2017:https://community.smartthings.com/t/updated-3-27-18-gcal-search/80042 * Special thanks to Mike Nestor & Anthony Pastor for creating the original SmartApp and DTH * UI/UX contributions made by Michael Struck and OAuth improvements by Gary Spender * Code was ported for use on Hubitat Elevation by cometfish in 2019: https://github.com/cometfish/hubitat_app_gcalsearch * Further improvements made by ritchierich and posted to the HubitatCommunity GitHub Repository so other community members can continue to improve this application * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * for the specific language governing permissions and limitations under the License. * */ import groovy.json.JsonSlurper import groovy.json.JsonOutput private getClientId() { return settings.gaClientID } private getClientSecret() { return settings.gaClientSecret } private getRedirectURL() { "https://cloud.hubitat.com/oauth/stateredirect" } private oauthInitState() { "${getHubUID()}/apps/${app.id}/callback?access_token=${state.accessToken}" } definition( name: "GCal Search", namespace: "HubitatCommunity", author: "Mike Nestor & Anthony Pastor, cometfish, ritchierich", description: "Integrates Hubitat with Google Calendar events to toggle virtual switch.", category: "Convenience", documentationLink: "https://community.hubitat.com/t/release-google-calendar-search/71397", importUrl: "https://raw.githubusercontent.com/HubitatCommunity/Google_Calendar_Search/main/Apps/GCal_Search.groovy", iconUrl: "", iconX2Url: "", iconX3Url: "", ) preferences { page(name: "mainPage") page(name: "addNotificationDevice") page(name: "authenticationPage") page(name: "utilitiesPage") page name: "authenticationReset" page(name: "removePage") } mappings { path("/callback") {action: [GET: "callback"]} } def mainPage() { dynamicPage(name: "mainPage", title: "${getFormat("title", "GCal Search Version " + appVersion())}", uninstall: false, install: true) { def isAuthorized = authTokenValid("mainPage") logDebug("mainPage - isAuthorized: ${isAuthorized}") if (isAuthorized) { section("${getFormat("box", "Search Triggers")}") { app(name: "childApps", appName: "GCal Search Trigger", namespace: "HubitatCommunity", title: "New Search...", multiple: true) paragraph "${getFormat("line")}" } section("${getFormat("box", "Gmail Notification Devices")}") { if (state.scopesAuthorized.indexOf("mail.google.com") > -1) { clearNotificationDeviceSettings() paragraph notificationDeviceInstructions() input name: "security", type: "bool", title: "Do you plan to send local files from File Manager and have hub security enabled? Credentials are required to get the local file.", defaultValue: false, submitOnChange: true if (settings.security == true) { input name: "username", type: "string", title: "Hub Security Username", required: true input name: "password", type: "password", title: "Hub Security Password", required: true } paragraph getNotificationDevices() paragraph "${getFormat("line")}" } else { paragraph "${getFormat("text", "This app is capable of creating Gmail Notification devices to send email notifications from rules. In order to leverage this feature:\n1. Enable the Gmail API in the Google Console\n2. Click Google API Authorization below and then Reset Google Authentication. Leave your existing credentials alone; you just need to reauthorize the APIs including Gmail\n3. Follow steps to complete the Google authentication process again and be sure to allow Hubitat access to Gmail when prompted.")}" } } } section("${getFormat("box", "Authentication")}") { if (!isAuthorized) { paragraph "${getFormat("warning", "Authentication Problem! Please click the button below to setup Google API Authorization.")}" } href ("authenticationPage", title: "Google API Authorization", description: "Click for Google Authentication") paragraph "${getFormat("line")}" } section("${getFormat("box", "Options")}") { input name: "appName", type: "text", title: "Name this parent app", required: true, defaultValue: "GCal Search", submitOnChange: true input name: "isDebugEnabled", type: "bool", title: "Enable debug logging?", defaultValue: false, required: false href "utilitiesPage", title: "Utilities", description: "Tap to access utilities" paragraph "${getFormat("line")}" } section("${getFormat("box", "Removal")}") { href ("removePage", description: "Click to remove ${app.label?:app.name}", title: "Remove GCal Search") } } } def authenticationPage() { def isOAuthEnabled = oauthEnabled() def readyToInstall = false def isAuthorized = false if (isOAuthEnabled) { isAuthorized = authTokenValid("authenticationPage") if (isAuthorized && !atomicState.version) { readyToInstall = true } } dynamicPage(name: "authenticationPage", install: readyToInstall, uninstall: false, nextPage: "mainPage") { section("${getFormat("box", "Google Authentication")}") { if (isOAuthEnabled) { // Make sure no leading or trailing spaces on gaClientID and gaClientSecret if (settings.gaClientID && settings.gaClientID != settings.gaClientID.trim()) { app.updateSetting("gaClientID",[type: "text", value: settings.gaClientID.trim()]) } if (settings.gaClientSecret && settings.gaClientSecret != settings.gaClientSecret.trim()) { app.updateSetting("gaClientSecret",[type: "text", value: settings.gaClientSecret.trim()]) } if (!atomicState.authToken && !isAuthorized) { paragraph "${getFormat("text", "Enter your Google API credentials below. Instructions to setup these credentials can be found in HubitatCommunity GitHub.")}" input "gaClientID", "text", title: "Google API Client ID", required: true, submitOnChange: true input "gaClientSecret", "text", title: "Google API Client Secret", required: true, submitOnChange: true } else if (!isAuthorized) { paragraph "${getFormat("warning", "Authentication Problem! Please click Reset Google Authentication and try the setup again.")}" } else if (readyToInstall) { paragraph "${getFormat("text", "Authentication process complete!")}" paragraph "${getFormat("warning", "Click Done to complete the installation of this app. Open the GCal Search app again to setup Google search triggers.")}" } else { paragraph "${getFormat("text", "Authentication process complete! Click Next to continue setup.")}" } if (gaClientID && gaClientSecret) { if (!atomicState.authToken) { paragraph "${authenticationInstructions()}" href url: getOAuthInitUrl(), style: "external", required: true, title: "Authenticate GCal Search", description: "Tap to start the authentication process" } paragraph "${getFormat("text", "At any time click the button below to restart the authentication process.")}" href "authenticationReset", title: "Reset Google Authentication", description: "Tap to reset Google API Authentication and start over" paragraph "${getFormat("text", "Use the browser back button or click Next to exit.")}" } } else { paragraph "${getFormat("warning", "OAuth must be enabled on the GCal Search app.")}" paragraph "${oAuthInstructions()}" } } } } def oAuthInstructions() { def text = "
Steps to enable OAuth:
" text += "Steps required to complete the Google authentication process:
" text += "Email message settings can dynamically get set via notification message. Optionally include the following keys separated by commas at the beginning of the message, followed by the email body. Keys are case sensitive.
" text += "Device Name | " + "Email Address | " + "Email Subject |
---|---|---|
$devLink | " + "${devPrefs.toEmail} | " + "${devPrefs.toSubject} |
$newNotificationDevice | " + "$newNotificationDevice | " + "Create New Gmail Notification Device | " + "
---|
Your Google Account has been successfully authorized.
Close this page to continue with the setup.
""" } else { logMsg.push("OAuth flow failed") message = """The connection could not be established!
Close this page and click Reset Google Authentication to try again.
""" } logDebug("${logMsg}") connectionStatus(message) } else { log.error "callback() failed oauthState != state.oauthInitState" } } private refreshAuthToken() { def answer def logMsg = ["refreshAuthToken - state.refreshToken: ${state.refreshToken}"] if(!atomicState.refreshToken && !state.refreshToken) { answer = false logMsg.push("Can not refresh OAuth token since there is no refreshToken stored, ${state}") } else { def refTok if (state.refreshToken) { refTok = state.refreshToken logMsg.push("Existing state.refreshToken = ${refTok}") } else if (atomicState.refreshToken) { refTok = atomicState.refreshToken logMsg.push("Existing state.refreshToken = ${refTok}") } def refreshParams = [ uri : "https://www.googleapis.com", path : "/oauth2/v4/token", body : [ refresh_token: "${refTok}", client_secret: getClientSecret(), grant_type: 'refresh_token', client_id: getClientId() ], ] logMsg.push("refreshParams: ${refreshParams}") try { httpPost(refreshParams) { resp -> if(resp.data) { logMsg.push("resp callback ${resp.data}") atomicState.authToken = resp.data.access_token atomicState.tokenExpires = now() + (resp.data.expires_in * 1000) answer = true } } } catch(Exception e) { log.error "refreshAuthToken - caught exception refreshing auth token: " + e answer = false } } logMsg.push("returning ${answer}") logDebug("${logMsg}") return answer } def authTokenValid(fromFunction) { //Upgrade check if (state.scopesAuthorized == null && ["mainPage", "authenticationPage"].indexOf(fromFunction) > -1) { return false } if (atomicState.tokenExpires >= now()) { logDebug "authTokenValid - fromFunction: ${fromFunction}, authToken good expires ${new Date(atomicState.tokenExpires)}" return true } else { def refreshAuthToken = refreshAuthToken() logDebug "authTokenValid - fromFunction: ${fromFunction}, authToken ${(atomicState.tokenExpires == null) ? "null" : "expired (" + new Date(atomicState.tokenExpires) + ")"} - calling refreshAuthToken: ${refreshAuthToken}" return refreshAuthToken } } def revokeAccess() { logDebug "GCalSearch: revokeAccess()" revokeAccessToken() refreshAuthToken() if (!atomicState.authToken) { return } try { def uri = "https://accounts.google.com/o/oauth2/revoke?token=${atomicState.authToken}" logDebug "Revoke: ${uri}" httpGet(uri) { resp -> logDebug "Resp Status: ${resp.status}, Data: ${resp.data}" } } catch (e) { log.error "revokeAccess - something went wrong: ${e}" } } def apiGet(fromFunction, uri, path, queryParams) { def logMsg = [] def apiResponse = [] def isAuthorized = authTokenValid(fromFunction) logMsg.push("apiGet - fromFunction: ${fromFunction}, isAuthorized: ${isAuthorized}") if (isAuthorized == true) { def output = new JsonOutput() def apiParams = [ uri: uri, path: path, headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], query: queryParams ] logMsg.push("apiParams: ${apiParams}") try { httpGet(apiParams) { resp -> apiResponse = resp.data logDebug "Resp Status: ${resp.status}" } } catch (e) { if (e.toString().indexOf("HttpResponseException") > -1) { if (e.response.status == 401 && refreshAuthToken()) { return apiGet(fromFunction, uri, path, queryParams) } else if (e.response.status == 403) { log.error "apiGet - path: ${path}, ${e}, ${e.getResponse().getData()}" apiResponse = "error" } } else { log.error "apiGet - fromFunction: ${fromFunction}, path: ${path}, error: ${e}" } } } else { logMsg.push("Authentication Problem") } logMsg.push("apiResponse: ${apiResponse}") logDebug("${logMsg}") return apiResponse } def apiPut(fromFunction, uri, path, bodyParams) { def logMsg = [] def apiResponse = [] def isAuthorized = authTokenValid(fromFunction) logMsg.push("apiPut - fromFunction: ${fromFunction}, isAuthorized: ${isAuthorized}") if (isAuthorized == true) { def output = new JsonOutput() def apiParams = [ uri: uri, path: path, contentType: "application/json", headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"], body: output.toJson(bodyParams) ] logMsg.push("apiParams: ${apiParams}") try { httpPut(apiParams) { resp -> apiResponse = resp.data logDebug "Resp Status: ${resp.status}, apiResponse: ${apiResponse}" } } catch (e) { if (e.toString().indexOf("HttpResponseException") > -1 && e.response.status == 401 && refreshAuthToken()) { return apiPut(fromFunction, uri, path, bodyParams) } else { log.error "apiPut - fromFunction: ${fromFunction}, path: ${path}, ${e}" } } } else { logMsg.push("Authentication Problem") } logDebug("${logMsg}") return apiResponse } def apiPatch(fromFunction, uri, path, bodyParams) { def logMsg = [] def apiResponse = [] def isAuthorized = authTokenValid(fromFunction) logMsg.push("apiPatch - fromFunction: ${fromFunction}, isAuthorized: ${isAuthorized}") if (isAuthorized == true) { def output = new JsonOutput() def apiParams = [ uri: uri, path: path, contentType: "application/json", headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"], body: output.toJson(bodyParams) ] logMsg.push("apiParams: ${apiParams}") try { httpPatch(apiParams) { resp -> apiResponse = resp.data logDebug "Resp Status: ${resp.status}, apiResponse: ${apiResponse}" } } catch (e) { if (e.toString().indexOf("HttpResponseException") > -1 && e.response.status == 401 && refreshAuthToken()) { return apiPatch(fromFunction, uri, path, bodyParams) } else { log.error "apiPatch - fromFunction: ${fromFunction}, path: ${path}, ${e}" } } } else { logMsg.push("Authentication Problem") } logDebug("${logMsg}") return apiResponse } def apiPost(fromFunction, apiPrefs, bodyParams) { def logMsg = [] def apiResponse = [:] def isAuthorized = authTokenValid(fromFunction) logMsg.push("apiPost - fromFunction: ${fromFunction}, isAuthorized: ${isAuthorized}, apiPrefs: ${apiPrefs}") if (isAuthorized == true) { def apiParams = [ uri: apiPrefs.uri, path: (apiPrefs.containsKey("path")) ? apiPrefs.path : null, contentType: "application/json", headers: ["Content-Type": apiPrefs.contentType, "Authorization": "Bearer ${atomicState.authToken}"] ] if (bodyParams) { def output = new JsonOutput() apiParams.body = (apiPrefs.jsonBody == true) ? output.toJson(bodyParams) : bodyParams } def trimFilefromLog = false //Remove file contents from logging, if trying to troubleshoot the API, comment the following line so it gets logged. Performance issues will arise if this is left on. trimFilefromLog = true logMsg.push("apiParams: ${(trimFilefromLog && apiParams.toString().indexOf("filename=") > -1) ? apiParams.toString().substring(0, apiParams.toString().indexOf("filename=")) : apiParams}") try { httpPost(apiParams) { resp -> apiResponse.status = resp.status apiResponse.data = resp.data logDebug "apiResponse: ${apiResponse}" } } catch (e) { if (e.toString().indexOf("HttpResponseException") > -1 && e.response.status == 401 && refreshAuthToken()) { return apiPost(fromFunction, apiPrefs, bodyParams) } else { log.error "apiPost - fromFunction: ${fromFunction}, path: ${path}, ${e}" } } } else { logMsg.push("Authentication Problem") } logDebug("${logMsg}") return apiResponse } /* ============================= End Google APIs ============================= */ /* ============================= Start Google Calendar ============================= */ def getCalendarList() { def logMsg = [] def calendarList = [:] def uri = "https://www.googleapis.com" def path = "/calendar/v3/users/me/calendarList" def queryParams = [ format: 'json' ] def calendars = apiGet("getCalendarList", uri, path, queryParams) logMsg.push("getCalendarList - path: ${path}, queryParams: ${queryParams}, calendars: ${calendars}") if (calendars instanceof Map && calendars.size() > 0) { calendars.items.each { calendarItem -> calendarList[calendarItem.id] = (calendarItem.summaryOverride) ? calendarItem.summaryOverride : calendarItem.summary } logMsg.push("calendarList: ${calendarList}") } else { calendarList = calendars } logDebug("${logMsg}") return calendarList } def getNextEvents(watchCalendar, GoogleMatching, search, endTimePreference, offsetEnd, dateFormat) { endTimePreference = translateEndTimePref(endTimePreference) def logMsg = ["getNextEvents - watchCalendar: ${watchCalendar}, search: ${search}, endTimePreference: ${endTimePreference}"] def eventList = [] def uri = "https://www.googleapis.com" def path = "/calendar/v3/calendars/${watchCalendar}/events" def queryParams = [ //maxResults: 1, orderBy: "startTime", singleEvents: true, //timeMin: getCurrentTime(), timeMin: getStartTime(offsetEnd), timeMax: getEndDate(endTimePreference) ] if (GoogleMatching == true && search != "") { queryParams['q'] = "${search}" } def events = apiGet("getNextEvents", uri, path, queryParams) logMsg.push("queryParams: ${queryParams}, events: ${events}") if (events.items && events.items.size() > 0) { def defaultReminder = (events.containsKey("defaultReminders") && events.defaultReminders.size() > 0) ? events.defaultReminders[0] : [method:"popup", minutes:15] for (int i = 0; i < events.items.size(); i++) { def event = events.items[i] def reminderMinutes if (event.containsKey("reminders") && event.reminders.containsKey("overrides")) { def reminders = event.reminders.overrides reminderMinutes = reminders.find{it.method == defaultReminder.method} reminderMinutes = reminderMinutes.minutes } else { reminderMinutes = defaultReminder.minutes } def eventDetails = [:] eventDetails.kind = event.kind //eventDetails.timeZone = events.timeZone eventDetails.eventID = event.id eventDetails.eventTitle = event.summary ? event.summary.trim() : "none" eventDetails.eventLocation = event.location ? event.location : "none" eventDetails.eventReminderMin = reminderMinutes if (event.description && event.description != null && event.description.trim() != "") { eventDetails.eventDescription = event.description //Description is an HTML field, remove html tags, special characters, and spaces eventDetails.eventDescription = eventDetails.eventDescription.replaceAll("\n"," ") eventDetails.eventDescription = eventDetails.eventDescription.replaceAll("\\<.*?\\>", " ") eventDetails.eventDescription = eventDetails.eventDescription.replaceAll("\\&.*?\\;", " ") eventDetails.eventDescription = eventDetails.eventDescription.trim().replaceAll(" +", " ") } else { eventDetails.eventDescription = "none" } def eventAllDay def eventStartTime def eventEndTime if (event.start.containsKey('date')) { eventAllDay = true def sdf = new java.text.SimpleDateFormat("yyyy-MM-dd") sdf.setTimeZone(location.timeZone) eventStartTime = sdf.parse(event.start.date) eventEndTime = new Date(sdf.parse(event.end.date).time - 60) } else { eventAllDay = false def sdf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss") sdf.setTimeZone(TimeZone.getTimeZone(events.timeZone)) eventStartTime = sdf.parse(event.start.dateTime) eventEndTime = sdf.parse(event.end.dateTime) } eventDetails.eventAllDay = eventAllDay eventDetails.eventStartTime = eventStartTime eventDetails.eventEndTime = eventEndTime eventList.push(eventDetails) } } logMsg.push("eventList:\n${eventList.join("\n")}") logDebug("${logMsg}") return eventList } /* ============================= End Google Calendar ============================= */ /* ============================= Start Google Task ============================= */ def getTaskList() { def logMsg = [] def taskList = [:] def uri = "https://www.googleapis.com" def path = "/tasks/v1/users/@me/lists" def queryParams = [ format: 'json' ] def taskLists = apiGet("getTaskList", uri, path, queryParams) logMsg.push("getTaskList - path: ${path}, queryParams: ${queryParams}, taskLists: ${taskLists}") if (taskLists instanceof Map && taskLists.size() > 0) { taskLists.items.each { taskListItem -> taskList[taskListItem.id] = taskListItem.title } logMsg.push("taskLists: ${taskLists}") } else { taskList = taskLists } logDebug("${logMsg}") return taskList } def getNextTasks(taskList, search, endTimePreference) { endTimePreference = translateEndTimePref(endTimePreference) def logMsg = ["getNextTasks - taskList: ${taskList}, search: ${search}, endTimePreference: ${endTimePreference}"] def tasksList = [] def uri = "https://www.googleapis.com" def path = "/tasks/v1/lists/${taskList}/tasks" def queryParams = [ //maxResults: 1, showCompleted: false, dueMax: getEndDate(endTimePreference) ] def tasks = apiGet("getNextTasks", uri, path, queryParams) logMsg.push("queryParams: ${queryParams}, tasks: ${tasks}") if (tasks.items && tasks.items.size() > 0) { for (int i = 0; i < tasks.items.size(); i++) { def task = tasks.items[i] def taskDetails = [:] taskDetails.kind = task.kind taskDetails.taskTitle = task.title ? task.title.trim() : "none" taskDetails.taskID = task.id def sdf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss") taskDetails.taskDueDate = sdf.parse(task.due) tasksList.push(taskDetails) } } logMsg.push("tasksList:\n${tasksList.join("\n")}") logDebug("${logMsg}") return tasksList } def completeTask(watchTaskList, taskID) { def logMsg = ["completeTask - watchTaskList: ${watchTaskList}, taskID: ${taskID} - "] def uri = "https://tasks.googleapis.com" def path = "/tasks/v1/lists/${watchTaskList}/tasks/${taskID}" def bodyParams = [ id: taskID, status: "completed" ] def task = apiPatch("completeTask", uri, path, bodyParams) logMsg.push("bodyParams: ${bodyParams}, task: ${task}") logDebug("${logMsg}") return task } /* ============================= End Google Task ============================= */ /* ============================= Start Google Reminder - WARNING: Uses unnofficial API ============================= */ def getNextReminders(search, endTimePreference) { endTimePreference = translateEndTimePref(endTimePreference) def logMsg = ["getNextReminders - search: ${search}, endTimePreference: ${endTimePreference}"] def reminderList = [] def dueMax = getEndDate(endTimePreference, false) logMsg.push("dueMax: ${dueMax}") def bodyParams = [ //"max_results": 10, //"utc_due_before_ms": dueMax.getTime() "due_before_ms": dueMax.getTime() ] def apiPrefs = [ uri: "https://reminders-pa.clients6.google.com", path: "/v1internalOP/reminders/list", contentType: "application/json", jsonBody: true ] def reminders = apiPost("getNextReminders", apiPrefs, bodyParams) logMsg.push("bodyParams: ${bodyParams}, apiPrefs: ${apiPrefs}, reminders: ${reminders}") if (reminders.status == 200 && reminders.data && reminders.data.task && reminders.data.task.size() > 0) { for (int i = 0; i < reminders.data.task.size(); i++) { def reminder = reminders.data.task[i] def dueDate = getReminderDate(reminder.dueDate) if (dueDate <= dueMax) { def reminderDetails = [:] reminderDetails.kind = "reminder" reminderDetails.taskTitle = reminder.title ? reminder.title.trim() : "none" reminderDetails.taskID = reminder.taskId.serverAssignedId reminderDetails.taskDueDate = dueDate if (reminder.recurrenceInfo && reminder.recurrenceInfo.recurrence.frequency) { reminderDetails.repeat = reminder.recurrenceInfo.recurrence.frequency reminderDetails.recurrenceId = reminder.recurrenceInfo.recurrenceId.id } else { reminderDetails.repeat = "none" } reminderList.push(reminderDetails) } } reminderList.sort{it.taskDueDate} } logMsg.push("reminderList:\n${reminderList.join("\n")}") logDebug("${logMsg}") return reminderList } def getSpecificReminder(taskID) { def logMsg = ["getSpecificReminder - taskID: ${taskID}"] def reminderList = [] def bodyParams = [ "taskId": [['serverAssignedId': taskID]] ] def apiPrefs = [ uri: "https://reminders-pa.clients6.google.com", path: "/v1internalOP/reminders/get", contentType: "application/json", jsonBody: true ] def reminders = apiPost("getSpecificReminder", apiPrefs, bodyParams) logMsg.push("bodyParams: ${bodyParams}, apiPrefs: ${apiPrefs}, reminders: ${reminders}") if (reminders.status == 200 && reminders.data && reminders.data.task && reminders.data.task.size() > 0) { for (int i = 0; i < reminders.data.task.size(); i++) { def reminder = reminders.data.task[i] def reminderDetails = [:] reminderDetails.kind = "reminder" reminderDetails.taskTitle = reminder.title ? reminder.title.trim() : "none" reminderDetails.taskID = reminder.taskId.serverAssignedId reminderDetails.taskDueDate = getReminderDate(reminder.dueDate) reminderList.push(reminderDetails) } } logMsg.push("reminderList:\n${reminderList.join("\n")}") logDebug("${logMsg}") return reminderList } def getReminderDate(dueDate) { //[year:2022, month:1, day:24, time:[hour:20, minute:0, second:0]] //[year:2022, month:2, day:3, allDay:true] def dateString = new Date().copyWith( year: dueDate.year, month: dueDate.month-1, dayOfMonth: dueDate.day, hourOfDay: (dueDate.time) ? dueDate.time.hour : 0, minute: (dueDate.time) ? dueDate.time.minute : 0, second: (dueDate.time) ? dueDate.time.second : 0 ) return dateString } def deleteReminder(taskID) { def logMsg = ["deleteReminder - taskID: ${taskID}"] def reminderDeleted = false def taskIDList = taskID.split(",") for (int i = 0; i < taskIDList.size(); i++) { taskID = taskIDList[i] def bodyParams = [ "taskId": [["serverAssignedId": taskID]] ] def apiPrefs = [ uri: "https://reminders-pa.clients6.google.com", path: "/v1internalOP/reminders/delete", contentType: "application/json", jsonBody: true ] def reminders = apiPost("deleteReminder", apiPrefs, bodyParams) logMsg.push("bodyParams: ${bodyParams}, apiPrefs: ${apiPrefs}, reminders: ${reminders}") if (reminders.status == 200 ) { reminderDeleted = true } } logMsg.push("reminderDeleted: ${reminderDeleted}") logDebug("${logMsg}") return reminderDeleted } def completeReminder(taskID) { def logMsg = ["completeReminder - taskID: ${taskID}"] def reminderCompleted = false def taskIDList = taskID.split(",") for (int i = 0; i < taskIDList.size(); i++) { taskID = taskIDList[i] def bodyParams = [ "1": ["4": "WRP / /WebCalendar/calendar_190319.03_p1"], "2": ["1": taskID], "4": ["1": ["1": taskID], "8": 1], "7": ["1": [1, 10, 3]], ] def apiPrefs = [ uri: "https://reminders-pa.clients6.google.com", path: "/v1internalOP/reminders/update", contentType: "application/json+protobuf", jsonBody: true ] def reminders = apiPost("completeReminder", apiPrefs, bodyParams) logMsg.push("bodyParams: ${bodyParams}, apiPrefs: ${apiPrefs}, reminders: ${reminders}") if (reminders.status == 200 ) { reminderCompleted = true } } logMsg.push("reminderCompleted: ${reminderCompleted}") logDebug("${logMsg}") return reminderCompleted } /* ============================= End Google Reminder ============================= */ /* ============================= Start Gmail ============================= */ def getUserLabels() { def logMsg = [] def userLabelList = ["none":"NONE"] def uri = "https://gmail.googleapis.com" def path = "/gmail/v1/users/me/labels" def queryParams = [:] def userLabels = apiGet("getUserLabels", uri, path, queryParams) logMsg.push("getUserLabels - path: ${path}, queryParams: ${queryParams}, userLabels: ${userLabels}") if (userLabels instanceof Map && userLabels.labels.size() > 0) { def includeSystemLabels = ["INBOX", "IMPORTANT", "STARRED", "TRASH", "UNREAD"] for (int i = 0; i < userLabels.labels.size(); i++) { def userLabelItem = userLabels.labels[i] //if (userLabelItem.containsKey("labelListVisibility") || ignoreLabels.indexOf(userLabelItem.id) > -1) continue if (userLabelItem.type == "system" && includeSystemLabels.indexOf(userLabelItem.id) == -1) continue userLabelList[userLabelItem.id] = userLabelItem.name } logMsg.push("userLabelList: ${userLabelList}") } else { userLabelList = userLabels } logDebug("${logMsg}") return userLabelList } def getNextMessages(search, setlabelList=null) { def logMsg = ["getNextMessages - search: ${search}, setlabelList: ${setlabelList}"] def messageList = [] def uri = "https://gmail.googleapis.com" def path = "/gmail/v1/users/me/messages" def queryParams = [ //maxResults: 1, q: "${search}" ] if (labelList != null) { //queryParams['labelIds'] = "${labelList}" } def messages = apiGet("getNextMessages", uri, path, queryParams) logMsg.push("queryParams: ${queryParams}, messages: ${messages}") def messageIDs = [] if (messages.resultSizeEstimate > 0) { for (int i = 0; i < messages.messages.size(); i++) { def message = messages.messages[i] def messageID = message.id messageIDs.push(messageID) def messageDetails = getMessage(messageID) messageDetails.kind = "message" messageList.push(messageDetails) } if (setlabelList != null) { batchModifyMessages(messageIDs, setlabelList.add, setlabelList.remove) } } messageList.sort{it.messageReceived} logMsg.push("messageList:\n${messageList.join("\n")}") logDebug("${logMsg}") return messageList } def getMessage(messageID) { def logMsg = ["getMessage - messageID: ${messageID}"] def uri = "https://gmail.googleapis.com" def path = "/gmail/v1/users/me/messages/${messageID}" def queryParams = [:] def message = apiGet("getMessage", uri, path, queryParams) logMsg.push("queryParams: ${queryParams}, message: ${message}") def messageDetails = [:] if (message && message.id) { messageDetails.messageID = message.id messageDetails.threadID = message.threadId messageDetails.labelIDs = message.labelIds def messageBody = message.snippet messageDetails.messageBody = messageBody ? messageBody : "none" messageDetails.messageReceived = new Date(message.internalDate.toLong()) def payloadHeaders = message.payload.headers def messageTitle = payloadHeaders.find{it.name == "Subject"}.value messageDetails.messageTitle = messageTitle ? messageTitle : "none" messageDetails.messageFrom = payloadHeaders.find{it.name == "From"}.value.replace("\u003c", "").replace("\u003e", "") messageDetails.messageTo = payloadHeaders.find{it.name == "To"}.value.replace("\u003c", "").replace("\u003e", "") } logMsg.push("messageDetails: ${messageDetails}") logDebug("${logMsg}") return messageDetails } def batchModifyMessages(messageIDs, addLabels, removeLabels) { def logMsg = ["batchModifyMessages - messageIDs: ${messageIDs}, addLabels: ${addLabels}, removeLabels: ${removeLabels} - "] def bodyParams = [ ids: messageIDs, addLabelIds: addLabels, removeLabelIds: removeLabels ] def apiPrefs = [ uri: "https://gmail.googleapis.com", path: "/gmail/v1/users/me/messages/batchModify", contentType: "application/json", jsonBody: true ] def messages = apiPost("batchModifyMessages", apiPrefs, bodyParams) logMsg.push("bodyParams: ${bodyParams}, apiPrefs: ${apiPrefs}, reminders: ${reminders}") logDebug("${logMsg}") return messages } def sendMessage(toEmail, subject, message) { def logMsg = ["sendMessage - toEmail: ${toEmail}, subject: ${subject}, message: ${message} - "] def keyWords = ["To", "Subject", "File"] def foundKeywords = [:] for (int k = 0; k < keyWords.size(); k++) { def keyWord = keyWords[k] def keyWordIndex = message.indexOf(keyWord + ":") def commaIndex = message.indexOf(",", keyWordIndex) if (keyWordIndex > -1 && commaIndex > -1 && keyWordIndex < commaIndex) { def word = message.substring(keyWordIndex + keyWord.length() +1, commaIndex) foundKeywords[keyWord] = word message = message.replace(keyWord + ":" + word + ",", "").trim() } } logMsg.push("foundKeywords: ${foundKeywords}") toEmail = (foundKeywords.containsKey("To")) ? foundKeywords.To : toEmail subject = (foundKeywords.containsKey("Subject"))? foundKeywords.Subject : subject def bodyParams = [ to: "${toEmail}", subject: "${subject}", body: "${message}" ] if (foundKeywords.containsKey("File") && foundKeywords.File.indexOf(".") > -1) { def file = getFile(foundKeywords.File) if (file.startsWith("File Error")) { bodyParams.body += "