Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import groovy.json.JsonSlurper
- /**
- * Plex Communicator
- *
- * Copyright 2018 Jake Tebbett
- * Credit To: 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
- * v2.0 - Too many changes to list them all (General improvements and fixes + added track description)
- *
- */
- definition(
- name: "Plex Communicator",
- namespace: "jebbett",
- author: "Jake Tebbett",
- description: "Allow Your Hub and Plex 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: "PlexServer", displayLink: ""])
- def installed() {
- initialize()
- }
- def updated() {
- unsubscribe()
- initialize()
- }
- def initialize() {
- // sub to plex now playing response
- subscribe(location, null, response, [filterEvents:false])
- // Add New Devices
- def storedDevices = state.plexClients
- settings.devices.each {deviceId ->
- try {
- def existingDevice = getChildDevice(deviceId)
- if(!existingDevice) {
- def theHub = location.hubs[0]
- if(logging){ log.debug "ADDING DEVICE ID:${deviceId} on HUB [${theHub.id}] : ${theHub}"}
- def childDevice = addChildDevice("jebbett", "Plex 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 plexPoller has gasped it's last breath (in case it's used)
- if(settings?.stPoller){runEvery3Hours(plexPoller)}
- }
- preferences {
- page name: "mainMenu"
- page name: "noAuthPage"
- page name: "authPage"
- page name: "authPage2"
- page name: "clientPage"
- page name: "clearClients"
- page name: "mainPage"
- page name: "ApiSettings"
- }
- mappings {
- path("/statechanged/:command") { action: [GET: "plexExeHandler"] }
- path("/p2stset") { action: [GET: "p2stset"] }
- path("/pwhset") { action: [GET: "pwhset"] }
- path("/pwh") { action: [POST: "plexWebHookHandler"] }
- }
- /***********************************************************
- ** Main Pages
- ************************************************************/
- def mainMenu() {
- // Get ST 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()
- }
- if (state?.authenticationToken) { return mainPage() }
- else { return authPage() }
- }
- 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(""){
- paragraph "<h1><img src='https://github.com/jebbett/Plex-Communicator/raw/master/icon.png' width='64' height='64' /> <strong><span style='color: #E8A60B;'>Plex Communicator</span></strong></h1>"
- }
- section("Main Menu") {
- href "clientPage", title:"Select Your Devices", description: "Select the devices you want to monitor"
- href "authPage", title:"Plex Account Details", description: "Update Plex Account Details"
- href(name: "ApiSettings", title: "Connection Methods", required: false, page: "ApiSettings", description: "Select your method for connecting to Plex")
- input "logging", "bool", title: "Turn on to enable debug logs", defaultValue:false, submitOnChange: true
- }
- section("If you want to control lighting scenes then the 'MediaScene' SmartApp is ideal for this purpose"){}
- }
- }
- /***********************************************************
- ** Interface Settings
- ************************************************************/
- def ApiSettings() {
- dynamicPage(name: "ApiSettings", title: "Select Your Connection Method", install: false, uninstall: false) {
- section("1. Plex Webhooks - Plex Pass Only (Best)") {
- paragraph("Plex Webhooks is the best method for connecting Plex to your hub, however you will need an active Plex Pass Subscription to use it")
- href url: "${getLocalApiServerUrl()}/${app.id}/pwhset?access_token=${state.accessToken}", style:"embedded", required:false, title:"Plex Webhooks Settings", description: ""
- }
- section("2. Plex2Hub Program <B>*NOT WORKING PRESENTLY*</B>") {
- paragraph("This involves running a program on an always on computer")
- href url: "${getLocalApiServerUrl()}/${app.id}/p2stset?access_token=${state.accessToken}", style:"embedded", required:false, title:"Plex2Hub Program Settings", description: ""
- }
- section("3. Hub Polling *Not Recommended*") {
- paragraph("Your hub will poll every 10 seconds and request the status from Plex, however this method is unreliable and puts increased load on your hub and your network (Don't complain to me that it stops working occasionally)")
- input "stPoller", "bool", title: "Enable - At your own risk", defaultValue:false, submitOnChange: true
- }
- if(settings?.stPoller){plexPoller()}
- }
- }
- def pwhset() {
- def html = """<html><head><title>Plex Webhook Settings</title></head><body><h1>
- ${getFullLocalApiServerUrl()}/pwh?access_token=${state.accessToken}<br />
- </h1></body></html>"""
- render contentType: "text/html", data: html, status: 200
- }
- def p2stset() {
- def html = """
- <!DOCTYPE html>
- <html><head><title>Plex2Hub Program Settings</title></head><body><p>
- <!ENTITY accessToken '${state.accessToken}'><br />
- <!ENTITY appId '${app.id}'><br />
- <!ENTITY ide '${getFullLocalApiServerUrl()}'><br />
- <!ENTITY plexStatusUrl 'http://${settings.plexServerIP}:32400/status/sessions?X-Plex-Token=${state.authenticationToken}'>
- </p></body></html>"""
- render contentType: "text/html", data: html, status: 200
- }
- /***********************************************************
- ** Plex Authentication
- ************************************************************/
- def authPage() {
- return dynamicPage(name: "authPage", nextPage: authPage2, install: false) {
- def hub = location.hubs[0]
- section("Plex Login Details") {
- input "plexUserName", "text", "title": "Plex Username", multiple: false, required: true
- input "plexPassword", "password", "title": "Plex Password", multiple: false, required: true
- input "plexServerIP", "text", "title": "Server IP", multiple: false, required: true
- }
- }
- }
- def authPage2() {
- getAuthenticationToken()
- clientPage()
- }
- def getAuthenticationToken() {
- if(logging){ log.debug "Getting authentication token for Plex Server " + settings.plexServerIP }
- def paramsp = [
- uri: "https://plex.tv/users/sign_in.json?user%5Blogin%5D=" + settings.plexUserName + "&user%5Bpassword%5D=" + URLEncoder.encode(settings.plexPassword),
- requestContentType: "application/json",
- headers: [
- 'X-Plex-Client-Identifier': 'PlexCommunicator',
- 'X-Plex-Product': 'Plex Communicator',
- 'X-Plex-Version': '1.0'
- ]
- ]
- try {
- httpPost(paramsp) { resp ->
- state.authenticationToken = resp.data.user.authentication_token;
- if(logging){ log.debug "Congratulations Token recieved: " + state.authenticationToken + " & your Plex Pass status is " + resp.data.user.subscription.status } }
- }
- catch (Exception e) { log.warn "Hit Exception $e on $paramsp" }
- }
- /***********************************************************
- ** CLIENTS
- ************************************************************/
- def clientPage() {
- getClients()
- pause(2000)
- def devs = getClientList()
- return dynamicPage(name: "clientPage", title: "NOTE:", nextPage: mainPage, uninstall: false, install: true) {
- section("If your device does not appear in the list"){}
- section("Devices currently playing video 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 Done and come back to this screen"){
- input "devices", "enum", title: "Select Your Devices", options: devs, multiple: true, required: false, submitOnChange: true
- }
- section("Use the below to clear and re-load the device list"){
- href(name: "clearClients", title:"RESET Devices List", description: "", page: "clearClients", required: false)
- }
- }
- }
- def clearClients() {
- state.plexClients = [:]
- getClientsXML()
- getNowPlayingXML()
- pause(2000)
- mainPage()
- }
- def getClientList() {
- def devList = [:]
- state.plexClients.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.plexClients instanceof Map
- if(!isMap){state.plexClients = [:]}
- def isMap2 = state.playingClients instanceof Map
- if(!isMap2){state.playingClients = [:]}
- // Get devices.xml clients
- getClientsXML()
- // Request server:32400/status/sessions clients - Chromecast for example is not in devices.
- getNowPlayingXML()
- }
- def getNowPlayingXML() {
- def params = [
- uri: "http://${settings.plexServerIP}:32400/status/sessions",
- contentType: 'application/xml',
- headers: [ 'X-Plex-Token': state.authenticationToken ]
- ]
- try {
- httpGet(params) { resp ->
- def videos = resp.data.Video
- def whatToCallMe = ""
- def playingDevices = [:]
- videos.each { thing ->
- if(thing.Player.@title.text() != "") {whatToCallMe = "${thing.Player.@title.text()}-${thing.Player.@product.text()}"}
- else if(thing.Player.@device.text()!="") {whatToCallMe = "${thing.Player.@device.text()}-${thing.Player.@product.text()}"}
- playingDevices << [ (thing.Player.@machineIdentifier.text()): [name: "${whatToCallMe}", id: "${thing.Player.@machineIdentifier.text()}"]]
- if(settings?.stPoller){
- def plexEvent = [:] << [ id: "${thing.Player.@machineIdentifier.text()}", type: "${thing.@type.text()}", status: "${thing.Player.@state.text()}", user: "${thing.User.@title.text()}", title: "${thing.@title.text()}" ]
- stillPlaying << "${thing.Player.@machineIdentifier.text()}"
- eventHandler(plexEvent)
- }
- }
- 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 plexEvent2 = [:] << [ id: "${id}", type: "--", status: "stopped", user: "--", title: "--" ]
- eventHandler(plexEvent2)
- }
- }
- }
- state.plexClients << playingDevices
- state.playingClients = playingDevices
- }
- } catch (e) {
- log.error "something went wrong: $e"
- }
- }
- def getClientsXML() {
- //getAuthenticationToken()
- if(logging){ log.debug "Auth Token: $state.authenticationToken" }
- def xmlDevices = [:]
- // Get from Devices List
- def paramsg = [
- uri: "https://plex.tv/devices.xml",
- contentType: 'application/xml',
- headers: [ 'X-Plex-Token': state.authenticationToken ]
- ]
- httpGet(paramsg) { resp ->
- if(logging){ log.debug "Parsing plex.tv/devices.xml" }
- def devices = resp.data.Device
- devices.each { thing ->
- // If not these things
- if(thing.@name.text()!="Plex Communicator" && !thing.@provides.text().contains("server")){
- //Define name based on name unless blank then use device name
- def whatToCallMe = "Unknown"
- if(thing.@name.text() != "") {whatToCallMe = "${thing.@name.text()}-${thing.@product.text()}"}
- else if(thing.@device.text()!="") {whatToCallMe = "${thing.@device.text()}-${thing.@product.text()}"}
- xmlDevices << [ (thing.@clientIdentifier.text()): [name: "${whatToCallMe}", id: "${thing.@clientIdentifier.text()}"]]
- }
- }
- }
- //Get from status
- state.plexClients << xmlDevices
- }
- /***********************************************************
- ** INPUT HANDLERS
- ************************************************************/
- def plexExeHandler() {
- def status = params.command
- def userName = params.user
- def playerName = params.player
- def playerIP = params.ipadd
- def mediaType = params.type
- def playerID = params.id
- def title = "none"
- if(logging){
- log.debug "PLAYER_ID:$playerID / STATUS:$status / USERNAME:$userName / PLAYERNAME:$playerName / PLAYERIP:$playerIP / MEDIA_TYPE:$mediaType"
- }
- def plexEvent = [:] << [ id: "$playerID", type: "$mediaType", status: "$status", user: "$userName", title: "$title" ]
- eventHandler(plexEvent)
- return
- }
- def plexPoller(){
- if(settings?.stPoller){
- getNowPlayingXML()
- if(logging){log.debug "Plex Poller Update Request"}
- runOnce( new Date(now() + 10000L), plexPoller)
- }
- }
- def plexWebHookHandler(){
- def payloadStart = request.body.indexOf('application/json') + 17
- def newBody = request.body.substring(payloadStart)
- def jsonSlurper = new JsonSlurper()
- def plexJSON = jsonSlurper.parseText(newBody)
- if(logging){
- //log.debug "Metadata JSON: ${plexJSON.Metadata as String}" //Only unhide if you want to see media data, cast etc..
- log.debug "Media Type: ${plexJSON.Metadata.type as String}"
- log.debug "Player JSON: ${plexJSON.Player as String}"
- log.debug "Account JSON: ${plexJSON.Account}"
- log.debug "Event JSON: ${plexJSON.event}"
- log.info "## EVENT FROM WEBHOOKS BELOW ##"
- }
- //edition of original code from Jake Tebbett (05/18/21)
- def plexEvent = [:] << [id: plexJSON.Player.uuid,
- type: plexJSON.Metadata.librarySectionType,
- status: plexJSON.event,
- user: plexJSON.Account.title,
- title: plexJSON.Metadata.title,
- rating: plexJSON.Metadata.contentRating,
- seasonNumber: plexJSON.Metadata.parentIndex,
- episode: plexJSON.Metadata.index,
- show: plexJSON.Metadata.grandparentTitle,
- live: plexJSON.Metadata.live
- ]
- //end of edition
- eventHandler(plexEvent)
- }
- /***********************************************************
- ** 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"]: status = "playing"; break;
- case ["media.pause","onpause","pause"]: status = "paused"; break;
- case ["media.stop","onstop","stop"]: status = "stopped"; title = " "; break;
- }
- //**************edition of original code from Jake Tebbett (05/18/21)***************** in order to recive the extra atributes
- pcd = getChildDevice(event.id)
- if (!pcd) {
- log.info "child not beeing monitor"
- } else {
- pcd.sendEvent(name: "trackDescription", value: event.title)
- pcd.sendEvent(name: "status", value: status)
- if (event.type){
- pcd.sendEvent(name: "playbackType", value: event.type)
- } else {
- pcd.sendEvent(name: "playbackType", value: "No Type Found" )
- }
- if (event.rating){
- pcd.sendEvent(name: "Rating", value: event.rating)
- } else {
- pcd.sendEvent(name: "Rating", value: "No Rating Found")
- }
- if (event.seasonNumber){
- pcd.sendEvent(name: "Season", value: event.seasonNumber)
- } else {
- pcd.sendEvent(name: "Season", value: "Not a Show")
- }
- if (event.episode){
- pcd.sendEvent(name: "Episode", value: event.episode)
- } else {
- pcd.sendEvent(name: "Episode", value: "Not a Show")
- }
- if (event.show){
- pcd.sendEvent(name: "Show", value: event.show)
- } else {
- pcd.sendEvent(name: "Show", value: "Not a Show")
- }
- if (event.live){
- pcd.sendEvent(name: "Live", value: true)
- } else {
- pcd.sendEvent(name: "Live", value: false)
- }
- }
- ////**************end of edition**************
- }
- //**************edition of original code from Jake Tebbett (05/20/21)***************** in order to control players from the hub
- def sendCommand (XPlexTargetClientIdentifier, command) {
- def Params = [
- uri: "http://${settings.plexServerIP}:32400${command}",
- contentType: 'application/xml',
- headers: [
- 'X-Plex-Token': state.authenticationToken,
- 'X-Plex-Target-Client-Identifier':XPlexTargetClientIdentifier
- ]
- ]
- asynchttpGet("processCallBack",Params)
- }
- def processCallBack(response, data) {
- if (logEnable) log.debug "processCallBack"
- if (response.hasError()){
- log.error "got error # ${response.getStatus()} ${response.getErrorMessage()}"
- }
- if (!response.hasError()){
- if(logging){
- log.debug "no error responce data = ${response.getData()} satus = ${response.status}"
- }
- }
- }
- ////**************end of edition**************
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement