-
Notifications
You must be signed in to change notification settings - Fork 0
/
EmbyCommunicatorST.groovy
349 lines (312 loc) · 13.6 KB
/
EmbyCommunicatorST.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
import groovy.json.JsonSlurper
/**
* Emby Communicator
*
* Copyright 2020 Mike Robichaud
* Credit To: Jake Tebbett for his Plex Communicator code used as a base, Keo, Christian Hjelseth, iBeech & Ph4r as snippets of code taken and amended from their apps
*
* 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.
*
* VERSION CONTROL
* ###############
*
* v1.0 - Test Release
*
*/
definition(
name: "Emby Communicator",
namespace: "MRobi",
author: "Mike Robichaud",
description: "Allow Hubitat and Emby to Communicate",
category: "My Apps",
iconUrl: "https://github.com/jebbett/Plex-Communicator/raw/master/icon.png",
iconX2Url: "https://github.com/jebbett/Plex-Communicator/raw/master/icon.png",
iconX3Url: "https://github.com/jebbett/Plex-Communicator/raw/master/icon.png",
oauth: [displayName: "EmbyServer", displayLink: ""])
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
// sub to Emby now playing response
subscribe(location, null, response, [filterEvents:false])
// Add New Devices
def storedDevices = state.embyClients
settings.devices.each {deviceId ->
try {
def existingDevice = getChildDevice(deviceId)
if(!existingDevice) {
def theHub = location.hubs[0]
log.warn "${deviceId} and ${theHub}"
//def childDevice = addChildDevice("MRobi", "Emby Communicator Device", deviceId, theHub.id, [name: "${deviceId}", isComponent: false])
def childDevice = addChildDevice("MRobi", "Emby Communicator Device", deviceId, theHub.id, [name: deviceId, label: storedDevices."$deviceId".name, completedSetup: false])
}
} catch (e) { log.error "Error creating device: ${e}" }
}
// Clean up child devices
if(settings?.devices) {
getChildDevices().each { if(settings.devices.contains(it.deviceNetworkId)){}else{deleteChildDevice("${it.deviceNetworkId}")} }
}else{
getChildDevices().each { deleteChildDevice("${it.deviceNetworkId}") }
}
// Just in case embyPoller has gasped it's last breath (in case it's used)
if(settings?.stPoller){runEvery3Hours(embyPoller)}
}
preferences {
page name: "mainMenu"
page name: "noAuthPage"
page name: "authPage"
page name: "clientPage"
page name: "clearClients"
page name: "mainPage"
page name: "WebhooksSettings"
}
mappings {
path("/statechanged/:command") { action: [GET: "embyExeHandler"] }
path("/ewhset") { action: [GET: "ewhset"] }
path("/ewh") { action: [POST: "embyWebHookHandler"] }
}
/***********************************************************
** Main Pages
************************************************************/
def mainMenu() {
// Get HE Token
try { if (!state.accessToken) {createAccessToken()} }
catch (Exception e) {
log.info "Unable to create access token, OAuth has probably not been enabled in IDE: $e"
return noAuthPage()
}
return mainPage()
}
def noAuthPage() {
return dynamicPage(name: "noAuthPage", uninstall: true, install: true) {
section("*Error* You have not enabled OAuth when installing the app code, please enable OAuth")
}
}
def mainPage() {
return dynamicPage(name: "mainPage", uninstall: true, install: true) {
section("Main Menu") {
href "authPage", title:"Emby Account Details", description: "Update Emby Account Details"
href "clientPage", title:"Select Your Devices", description: "Select the devices you want to monitor"
href(name: "WebhooksSettings", title: "Connection Methods", required: false, page: "WebhooksSettings", description: "Select your method for connecting to Emby")
}
section("If you want to control lighting scenes then the 'MediaScene' SmartApp is ideal for this purpose"){}
section("This app is developed by MRobi, additional credit goes to jebbett (PlexCommunicator), Keo (Emby to Vudu), to Christian H (Plex2SmartThings), iBeech (Plex Home Theatre) & Ph4r (Improved Plex control)."){}
}
}
def WebhooksSettings() {
dynamicPage(name: "WebhooksSettings", title: "Webhook Address", install: false, uninstall: false) {
section("Webhooks - Emby service required") {
paragraph("Note: You will need an active Emby Subscription to use this")
href url: "${getLocalApiServerUrl()}/${app.id}/ewhset?access_token=${state.accessToken}", style:"embedded", required:false, title:"Emby Webhooks Settings", description: ""
}
section("NOTE: The settings above have also been sent to Live Logging, for easy access from a computer."){}
log.debug(
"\n ## URL FOR USE IN EMBY WEBHOOKS ##\n${getFullLocalApiServerUrl()}/ewh?access_token=${state.accessToken}"+
"<!ENTITY accessToken '${state.accessToken}'>\n"+
"<!ENTITY appId '${app.id}'>\n")
}
}
def ewhset() {
def html = """<html><head><title>Emby2Hubitat Settings</title></head><body><h1>
${getFullLocalApiServerUrl()}/ewh?access_token=${state.accessToken}<br />
</h1></body></html>"""
render contentType: "text/html", data: html, status: 200
}
/***********************************************************
** Emby Authentication
************************************************************/
def authPage() {
return dynamicPage(name: "authPage", install: true) {
def hub = location.hubs[0]
section("Emby Server Details") {
input "embyServerIP", "text", "title": "Server IP", multiple: false, required: true
input "embyServerPort", "text", "title": "Server Port", multiple: false, required: true, defaultValue: "8096"
input("embyApiKey", "text", title: "API Key", description: "Your Emby API Key", required: true)
}
}
}
def authPage2() {
clientPage()
}
/***********************************************************
** CLIENTS
************************************************************/
def clientPage() {
getClients()
def devs = getClientList()
return dynamicPage(name: "clientPage", title: "SELECT CLIENTS", uninstall: false, install: true) {
section("If your device does not appear in the list"){}
section("Devices currently in use by Emby will have a [►] icon next to them, this can be helpful when multiple devices share the same name, if a device is playing but not shown then press Save above and come back to this screen"){
input "devices", "enum", title: "Select Your Devices", options: devs, multiple: true, required: false, submitOnChange: true
}
if(!devices){
section("*CAUTION*"){
href(name: "clearClients", title:"RESET Devices List", description: "", page: "clearClients", required: false)
}
}else{
section("To Reset Devices List - Untick all devices in the list above, and the option to reset will appear"){}
}
}
}
def clearClients() {
state.embyClients = [:]
mainPage()
}
def getClientList() {
def devList = [:]
state.embyClients.each { id, details -> devList << [ "$id": "${details.name}" ] }
state.playingClients.each { id, details -> devList << [ "$id": "${details.name} [►]" ] }
return devList.sort { a, b -> a.value.toLowerCase() <=> b.value.toLowerCase() }
}
def getClients(){
// set lists
def isMap = state.embyClients instanceof Map
if(!isMap){state.embyClients = [:]}
def isMap2 = state.playingClients instanceof Map
if(!isMap2){state.playingClients = [:]}
// Get devices.json clients
getClientsXML()
// Request server:${settings.embyServerPort}/Sessions?DeviceId={deviceId} clients - chrome cast for example is not in devices.
executeRequest("http://${settings.embyServerIP}:${settings.embyServerPort}/emby/Devices?&api_key=${settings.embyApiKey}", "GET")
}
def executeRequest(Path, method) {
def headers = [:]
headers.put("HOST", "$settings.embyServerIP:${settings.embyServerPort}")
headers.put("X-Emby-Token", state.authenticationToken)
try {
def actualAction = new physicalgraph.device.HubAction(
method: method,
path: Path,
headers: headers)
sendHubCommand(actualAction)
}
catch (Exception e) {log.debug "Hit Exception $e on $hubAction"}
}
def response(evt) {
// Reponse to hub from now playing request
def msg = parseLanMessage(evt.description);
def stillPlaying = []
if(msg && msg.body && msg.body.startsWith("<?json")){
log.debug "Parsing status/sessions"
def whatToCallMe = ""
def playingDevices = [:]
def mediaContainer = new XmlSlurper().parseText(msg.body)
mediaContainer.Video.each { thing ->
playingDevices << [ ([email protected]()): [name: "${whatToCallMe}", id: "${[email protected]()}"]]
if(settings?.stPoller){
def embyEvent = [:] << [ id: "${[email protected]()}", type: "${[email protected]()}", status: "${[email protected]()}", user: "${[email protected]()}" ]
stillPlaying << "${[email protected]()}"
eventHandler(embyEvent)
}
}
if(settings?.stPoller){
//stop anything that's no long visible in the playing list but was playing before
state.playingClients.each { id, data ->
if(!stillPlaying.contains("$id")){
def embyEvent2 = [:] << [ id: "${id}", type: "--", status: "stopped", user: "--" ]
eventHandler(embyEvent2)
}
}
}
state.embyClients << playingDevices
state.playingClients = playingDevices
}
}
def getClientsXML() {
//getAuthenticationToken()
def jsonDevices = [:]
// Get from Devices List
def paramsg = [
uri: "http://${settings.embyServerIP}:${settings.embyServerPort}/emby/Devices?&api_key=${settings.embyApiKey}",
contentType: 'application/json',
]
httpGet(paramsg) { resp ->
log.debug "Parsing Emby Devices"
def devices = resp.data.Items
devices.each { thing ->
// If not these things
if(thing.Name !="Emby Communicator"){
//Define name based on name unless blank then use device name
def whatToCallMe = "Unknown"
if(thing.Name != "") {whatToCallMe = "${thing.Name}-${thing.AppName}"}
else if(thing.Name!="") {whatToCallMe = "${thing.AppName}"}
jsonDevices << [ (thing.Id): [name: "${whatToCallMe}", id: "${thing.Id}"]]
}
}
}
//Get from status
state.embyClients << jsonDevices
}
/***********************************************************
** INPUT HANDLERS
************************************************************/
def embyExeHandler() {
def status = params.command
def userName = params.user
//def playerName = params.player
//def playerIP = params.ipadd
def mediaType = params.type
def playerID = params.id
//log.debug "$playerID / $status / $userName / $playerName / $playerIP / $mediaType"
def embyEvent = [:] << [ id: "$playerID", type: "$mediaType", status: "$status", user: "$userName" ]
eventHandler(embyEvent)
return
}
def embyWebHookHandler(){
def payloadStart = request.body.indexOf('application/json') + 78
def newBody = request.body.substring(payloadStart)
//log.debug "Webhook Received with payload - $newBody"
def jsonSlurper = new JsonSlurper()
def embyJSON = jsonSlurper.parseText(newBody)
//log.debug "Event JSON: ${embyJSON.Event}"
def playerID = embyJSON.Session.DeviceId
def userName = embyJSON.User.Name
def mediaType = embyJSON.Item.Type
def status = embyJSON.Event
def mediaTitle = embyJSON.Item.Name
def seriesName = embyJSON.Item.SeriesName
def embyEvent = [:] << [ id: "$playerID", type: "$mediaType", series: "$seriesName", title: "$mediaTitle", status: "$status", user: "$userName" ]
//log.debug embyEvent
eventHandler(embyEvent)
}
def embyPoller(){
if(settings?.stPoller){
executeRequest("/status/sessions", "GET")
log.warn "Emby Poller Update"
runOnce( new Date(now() + 10000L), embyPoller)
}
}
/***********************************************************
** DTH OUTPUT
************************************************************/
def eventHandler(event) {
def status = event.status as String
// change command to right format
switch(status) {
case ["media.play","media.resume","media.scrobble","onplay","play","playback.start"]: status = "playing"; break;
case ["media.pause","onpause","pause","playback.pause"]: status = "paused"; break;
case ["media.stop","onstop","stop","playback.stop"]: status = "stopped"; break;
}
//log.debug "Playback Status: $status"
getChildDevices().each { pcd ->
if (event.id == pcd.deviceNetworkId){
pcd.setPlayStatus(status)
pcd.playbackType(event.type)
pcd.playbackTitle(event.title)
pcd.playbackSeries(event.series)
}
}
}