def appVersion() { return "4.7.4" }
/**
* 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, submitOnChange: true, width: 4
if (settings.isDebugEnabled == true) {
input name: "debugAuth", type: "bool", title: "Debug authentication?", defaultValue: false, required: false, width: 4
}
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 += ""
text += "
Please click this link to open another browser tab to enable this setting in Apps Code.
"
text += "
Instructions to enable OAuth can be found in the 'Enabling OAuth' Section of the How to Install Custom Apps article.
"
text += "
After completing Step 1 is refresh this page (browser refresh) to continue setup.
"
text += ""
return text
}
def authenticationInstructions() {
def text = "
Steps required to complete the Google authentication process:
"
text += "
"
text += "
Tap the 'Authenticate GCal Search' button below to start the authentication process.
"
text += "
A popup will appear walking you through process as outlined in the instructions on GitHub.
"
text += "
Be sure to select the appropriate access to Google Calendar, Google Tasks, and/or Gmail.
"
text += "
Troubleshooting Note: If the popup presents an 'Authorization Error, Error 400: redirect_uri_mismatch' please check your OAuth credential in the Google Console to ensure it is of type Web application and that the redirect URI is set correctly.
"
text += "
"
return text
}
def authenticationReset() {
revokeAccess()
atomicState.authToken = null
atomicState.oauthInitState = null
atomicState.refreshToken = null
atomicState.tokenExpires = null
atomicState.scopesAuthorized = null
authenticationPage()
}
def utilitiesPage() {
if (settings.resyncNow == true) {
runIn(10, resyncChildApps)
app.updateSetting("resyncNow",[type: "bool", value: false])
}
dynamicPage(name: "utilitiesPage", title: "${getFormat("box", "App Utilities")}", uninstall: false, install: false, nextPage: "mainPage") {
section() {
paragraph "${getFormat("text", "All commands take effect immediately!")}"
input "resyncNow", "bool", title: "Sync all calendar searches now. FYI You can sync individual calendar searches by clicking the Poll button within the child switch.", required: false, defaultValue: false, submitOnChange: true
}
}
}
def addNotificationDevice() {
dynamicPage(name: "addNotificationDevice", title: "${getFormat("box", "Add Gmail Notification Device")}", uninstall: false, install: false, nextPage: "mainPage") {
section() {
if (state.missingDriver == null) {
paragraph "${getFormat("text", "Fill in the following details and click anywhere on the screen to expose the 'Create Notification Device' button. Click this to add a new Gmail notification device and repeat steps to add additional Gmail notification devices. Click Next to return to the main menu.")}"
input "notifLabel", "text", title: "Notification device name", required: false, submitOnChange: true
input "notifTo", "text", title: "Default email address to send notification (if one is not passed in the notification)", required: false, submitOnChange: true
input "notifSubject", "text", title: "Default email subject (if one is not passed in the notification)", defaultValue: "${location.name} Notification", required: false, submitOnChange: true
input "notifDisplayName", "text", title: "Default display name for email sender (if one is not passed in the notification)", required: false, submitOnChange: true
if (settings.notifLabel && settings.notifTo) {
input name: "createChild", type: "button", title: "Create Notification Device", backgroundColor: "Green", textColor: "white", width: 4, submitOnChange: true
}
paragraph "${getFormat("line")}"
paragraph "${getFormat("text", "Existing Gmail Notification Devices:\n${getNotificationDevices(false)}")}"
} else {
paragraph "${getFormat("text", "Gmail Notification Device driver is missing and a notification device cannot be created.\n1. Please download the Gmail Notification Device driver from GitHub\n2. Navigate to Drivers code and install this driver\n3. Once installed click the 'Driver Installed' button to continue adding Gmail notification devices")}"
input name: "driverInstalled", type: "button", title: "Driver Installed", backgroundColor: "Green", textColor: "white", width: 4, submitOnChange: true
}
}
}
}
def notificationDeviceInstructions() {
def 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 += "
"
text += "
To: recipient@example.com Note: Multiple email addresses can be separated by ';' (semicolon)
"
text += "
Subject: Dynamic Email Subject
"
text += "
From: Dynamic From Display Name (Display Name <from@gmail.com>)
"
text += "
File: Exact name of file within your hub's File Manager
"
text += "
If including any dynamic settings from above, the final comma value should be the body of the email. The body is base64 encoded so HTML tags can be included in the email body if desired.
"
text += "
For example 'Subject:Urgent from Hubitat, To:newRecipient@example.com, File:CameraImage.jpg, <b>Dishwasher</b> water sensor is wet!' will send the email to 'newRecipient@example.com', make the email subject 'Urgent from Hubitat', attach the 'CameraImage.jpg' file, and the body of the email 'Dishwasher water sensor is wet!' with the word Dishwasher in bold.