Cleanup and tweaks

This commit is contained in:
Phin 2022-05-11 03:27:06 +05:30
parent 05b6a5e672
commit 86d59fa16a
5 changed files with 95 additions and 91 deletions

View file

@ -15,7 +15,7 @@ if config["logging"]["debug"]:
PlexAlertListener.useRemainingTime = config["display"]["useRemainingTime"]
if len(config["users"]) == 0:
logger.info("No users in config. Initiating authorisation flow. ! TBD !") # TODO
logger.info("No users found in the config file. Initiating authorisation flow. ! TBD !") # TODO
exit()
plexAlertListeners: list[PlexAlertListener] = []

View file

@ -1,8 +1,8 @@
from datetime import datetime
from models.config import Config
from utils.logs import logger
import json
import os
import time
class ConfigService:
@ -15,7 +15,7 @@ class ConfigService:
with open(self.configFilePath, "r", encoding = "UTF-8") as configFile:
self.config = json.load(configFile)
except:
os.rename(configFilePath, configFilePath.replace(".json", f"-{datetime.now().timestamp():.0f}.json"))
os.rename(configFilePath, configFilePath.replace(".json", f"-{time.time():.0f}.json"))
logger.exception("Failed to parse the application's config file. A new one will be created.")
self.resetConfig()
else:
@ -24,12 +24,12 @@ class ConfigService:
def resetConfig(self) -> None:
self.config = {
"logging": {
"debug": True
"debug": True,
},
"display": {
"useRemainingTime": False
"useRemainingTime": False,
},
"users": []
"users": [],
}
self.saveConfig()

View file

@ -20,6 +20,9 @@ class DiscordRpcService:
self.connected = False
def connect(self):
if self.connected:
logger.debug("Attempt to connect Discord IPC Pipe while already connected")
return
logger.info("Connecting Discord IPC Pipe")
self.loop = asyncio.new_event_loop() if isUnix else asyncio.ProactorEventLoop()
self.loop.run_until_complete(self.handshake())
@ -45,7 +48,7 @@ class DiscordRpcService:
return data
except:
logger.exception("An unexpected error occured during a RPC read operation")
self.disconnect()
self.connected = False
def write(self, op, payload):
try:
@ -54,22 +57,21 @@ class DiscordRpcService:
self.pipeWriter.write(struct.pack("<ii", op, len(payload)) + payload.encode("utf-8"))
except:
logger.exception("An unexpected error occured during a RPC write operation")
self.disconnect()
self.connected = False
def disconnect(self):
if not self.connected:
logger.debug("Attempt to disconnect Discord IPC Pipe while not connected")
return
logger.info("Disconnecting Discord IPC Pipe")
if (self.pipeWriter):
try:
self.pipeWriter.close()
except:
logger.exception("An unexpected error occured while closing an IPC pipe writer")
self.pipeWriter = None
if (self.pipeReader):
try:
self.loop.run_until_complete(self.pipeReader.read())
except:
logger.exception("An unexpected error occured while closing an IPC pipe reader")
self.pipeReader = None
try:
self.pipeWriter.close()
except:
logger.exception("An unexpected error occured while closing an IPC pipe writer")
try:
self.loop.run_until_complete(self.pipeReader.read())
except:
logger.exception("An unexpected error occured while closing an IPC pipe reader")
try:
self.loop.close()
except:
@ -84,7 +86,7 @@ class DiscordRpcService:
"pid": processID,
"activity": activity,
},
"nonce": "{0:.20f}".format(time.time())
"nonce": "{0:.2f}".format(time.time()),
}
self.write(1, payload)
self.loop.run_until_complete(self.read())

View file

@ -1,5 +1,6 @@
# type: ignore
from plexapi.alert import AlertListener
from plexapi.myplex import MyPlexAccount
from services import DiscordRpcService
from utils.logs import LoggerWithPrefix
@ -11,9 +12,9 @@ import time
class PlexAlertListener:
productName = "Plex Media Server"
updateTimeoutTimerInterval = 35
updateTimeoutTimerInterval = 30
connectionTimeoutTimerInterval = 60
maximumIgnores = 3
maximumIgnores = 2
useRemainingTime = False
def __init__(self, username, token, serverConfig):
@ -39,13 +40,13 @@ class PlexAlertListener:
def connect(self):
connected = False
while (not connected):
while not connected:
try:
self.plexAccount = MyPlexAccount(self.username, token = self.token)
self.logger.info("Logged in as Plex User \"%s\"", self.plexAccount.username)
self.plexServer = None
for resource in self.plexAccount.resources():
if (resource.product == self.productName and resource.name == self.serverConfig["name"]):
if resource.product == self.productName and resource.name == self.serverConfig["name"]:
self.logger.info("Connecting to %s \"%s\"", self.productName, self.serverConfig["name"])
self.plexServer = resource.connect()
try:
@ -54,13 +55,14 @@ class PlexAlertListener:
except:
pass
self.logger.info("Connected to %s \"%s\"", self.productName, self.serverConfig["name"])
self.plexAlertListener = self.plexServer.startAlertListener(self.handlePlexAlert)
self.plexAlertListener = AlertListener(self.plexServer, self.handlePlexAlert, self.reconnect)
self.plexAlertListener.start()
self.logger.info("Listening for alerts from user \"%s\"", self.username)
self.connectionTimeoutTimer = threading.Timer(self.connectionTimeoutTimerInterval, self.connectionTimeout)
self.connectionTimeoutTimer.start()
connected = True
break
if (not self.plexServer):
if not self.plexServer:
self.logger.error("%s \"%s\" not found", self.productName, self.serverConfig["name"])
break
except Exception as e:
@ -69,67 +71,65 @@ class PlexAlertListener:
time.sleep(10)
def disconnect(self):
self.disconnectRpc()
self.discordRpcService.disconnect()
self.cancelTimers()
if (self.plexAlertListener):
try:
self.plexAlertListener.stop()
except:
pass
try:
self.plexAlertListener.stop()
except:
pass
self.reset()
self.logger.info("Stopped listening for alerts")
def disconnectRpc(self):
if (self.discordRpcService.connected):
self.discordRpcService.disconnect()
def reconnect(self, exception):
self.logger.error("Connection to Plex lost: %s", exception)
self.disconnect()
self.logger.error("Reconnecting")
self.connect()
def cancelTimers(self):
if (self.updateTimeoutTimer):
if self.updateTimeoutTimer:
self.updateTimeoutTimer.cancel()
self.updateTimeoutTimer = None
if (self.connectionTimeoutTimer):
if self.connectionTimeoutTimer:
self.connectionTimeoutTimer.cancel()
self.connectionTimeoutTimer = None
def updateTimeout(self):
self.logger.debug("No recent updates from session key %s", self.lastSessionKey)
self.disconnectRpc()
self.discordRpcService.disconnect()
self.cancelTimers()
def connectionTimeout(self):
try:
self.logger.debug("Request for list of clients to check connection: %s", self.plexServer.clients())
except Exception as e:
self.logger.error("Connection to Plex lost: %s", e)
self.disconnect()
self.logger.error("Reconnecting")
self.connect()
self.reconnect(e)
else:
self.connectionTimeoutTimer = threading.Timer(self.connectionTimeoutTimerInterval, self.connectionTimeout)
self.connectionTimeoutTimer.start()
def handlePlexAlert(self, data):
try:
if (data["type"] == "playing" and "PlaySessionStateNotification" in data):
sessionData = data["PlaySessionStateNotification"][0]
state = sessionData["state"]
sessionKey = int(sessionData["sessionKey"])
ratingKey = int(sessionData["ratingKey"])
viewOffset = int(sessionData["viewOffset"])
self.logger.debug("Received alert: %s", sessionData)
metadata = self.plexServer.fetchItem(ratingKey)
libraryName = metadata.section().title
if ("blacklistedLibraries" in self.serverConfig and libraryName in self.serverConfig["blacklistedLibraries"]):
if data["type"] == "playing" and "PlaySessionStateNotification" in data:
alert = data["PlaySessionStateNotification"][0]
state = alert["state"]
sessionKey = int(alert["sessionKey"])
ratingKey = int(alert["ratingKey"])
viewOffset = int(alert["viewOffset"])
self.logger.debug("Received alert: %s", alert)
item = self.plexServer.fetchItem(ratingKey)
libraryName = item.section().title
if "blacklistedLibraries" in self.serverConfig and libraryName in self.serverConfig["blacklistedLibraries"]:
self.logger.debug("Library \"%s\" is blacklisted, ignoring", libraryName)
return
if ("whitelistedLibraries" in self.serverConfig and libraryName not in self.serverConfig["whitelistedLibraries"]):
if "whitelistedLibraries" in self.serverConfig and libraryName not in self.serverConfig["whitelistedLibraries"]:
self.logger.debug("Library \"%s\" is not whitelisted, ignoring", libraryName)
return
if (self.lastSessionKey == sessionKey and self.lastRatingKey == ratingKey):
if (self.updateTimeoutTimer):
if self.lastSessionKey == sessionKey and self.lastRatingKey == ratingKey:
if self.updateTimeoutTimer:
self.updateTimeoutTimer.cancel()
self.updateTimeoutTimer = None
if (self.lastState == state and self.ignoreCount < self.maximumIgnores):
if self.lastState == state and self.ignoreCount < self.maximumIgnores:
self.logger.debug("Nothing changed, ignoring")
self.ignoreCount += 1
self.updateTimeoutTimer = threading.Timer(self.updateTimeoutTimerInterval, self.updateTimeout)
@ -137,26 +137,26 @@ class PlexAlertListener:
return
else:
self.ignoreCount = 0
if (state == "stopped"):
if state == "stopped":
self.lastState, self.lastSessionKey, self.lastRatingKey = None, None, None
self.disconnectRpc()
self.discordRpcService.disconnect()
self.cancelTimers()
return
elif (state == "stopped"):
elif state == "stopped":
self.logger.debug("Received \"stopped\" state alert from unknown session key, ignoring")
return
if (self.isServerOwner):
if self.isServerOwner:
self.logger.debug("Searching sessions for session key %s", sessionKey)
plexServerSessions = self.plexServer.sessions()
if (len(plexServerSessions) < 1):
if len(plexServerSessions) < 1:
self.logger.debug("Empty session list, ignoring")
return
for session in plexServerSessions:
self.logger.debug("%s, Session Key: %s, Usernames: %s", session, session.sessionKey, session.usernames)
if (session.sessionKey == sessionKey):
if session.sessionKey == sessionKey:
self.logger.debug("Session found")
sessionUsername = session.usernames[0].lower()
if (sessionUsername == self.username):
if sessionUsername == self.username:
self.logger.debug("Username \"%s\" matches \"%s\", continuing", sessionUsername, self.username)
break
else:
@ -165,33 +165,35 @@ class PlexAlertListener:
else:
self.logger.debug("No matching session found, ignoring")
return
if (self.updateTimeoutTimer):
if self.updateTimeoutTimer:
self.updateTimeoutTimer.cancel()
self.updateTimeoutTimer = threading.Timer(self.updateTimeoutTimerInterval, self.updateTimeout)
self.updateTimeoutTimer.start()
self.lastState, self.lastSessionKey, self.lastRatingKey = state, sessionKey, ratingKey
mediaType = metadata.type
if (state != "playing"):
stateText = f"{formatSeconds(viewOffset / 1000, ':')}/{formatSeconds(metadata.duration / 1000, ':')}"
if state != "playing":
stateText = f"{formatSeconds(viewOffset / 1000, ':')}/{formatSeconds(item.duration / 1000, ':')}"
else:
stateText = formatSeconds(metadata.duration / 1000)
if (mediaType == "movie"):
title = f"{metadata.title} ({metadata.year})"
if len(metadata.genres) > 0:
stateText += f" · {', '.join(genre.tag for genre in metadata.genres[:3])}"
stateText = formatSeconds(item.duration / 1000)
mediaType = item.type
if mediaType == "movie":
title = f"{item.title} ({item.year})"
if len(item.genres) > 0:
stateText += f" · {', '.join(genre.tag for genre in item.genres[:3])}"
largeText = "Watching a movie"
elif (mediaType == "episode"):
title = metadata.grandparentTitle
stateText += f" · S{metadata.parentIndex:02}E{metadata.index:02} - {metadata.title}"
# self.logger.debug("Poster: %s", item.thumbUrl)
elif mediaType == "episode":
title = item.grandparentTitle
stateText += f" · S{item.parentIndex:02}E{item.index:02} - {item.title}"
largeText = "Watching a TV show"
elif (mediaType == "track"):
print(metadata)
title = metadata.title
artist = metadata.originalTitle
if (not artist):
artist = metadata.grandparentTitle
stateText = f"{artist} - {metadata.parentTitle}"
# self.logger.debug("Poster: %s", self.plexServer.url(item.grandparentThumb, True))
elif mediaType == "track":
title = item.title
artist = item.originalTitle
if not artist:
artist = item.grandparentTitle
stateText = f"{artist} - {item.parentTitle}"
largeText = "Listening to music"
# self.logger.debug("Album Art: %s", item.thumbUrl)
else:
self.logger.debug("Unsupported media type \"%s\", ignoring", mediaType)
return
@ -202,18 +204,18 @@ class PlexAlertListener:
"large_text": largeText,
"large_image": "logo",
"small_text": state.capitalize(),
"small_image": state
"small_image": state,
},
}
if (state == "playing"):
if state == "playing":
currentTimestamp = int(time.time())
if (self.useRemainingTime):
activity["timestamps"] = {"end": round(currentTimestamp + ((metadata.duration - viewOffset) / 1000))}
if self.useRemainingTime:
activity["timestamps"] = {"end": round(currentTimestamp + ((item.duration - viewOffset) / 1000))}
else:
activity["timestamps"] = {"start": round(currentTimestamp - (viewOffset / 1000))}
if (not self.discordRpcService.connected):
if not self.discordRpcService.connected:
self.discordRpcService.connect()
if (self.discordRpcService.connected):
if self.discordRpcService.connected:
self.discordRpcService.sendActivity(activity)
except:
self.logger.exception("An unexpected error occured in the alert handler")

View file

@ -1,8 +1,8 @@
def formatSeconds(seconds: int, joiner: str = "") -> str:
seconds = round(seconds)
timeValues = {"h": seconds // 3600, "m": seconds // 60 % 60, "s": seconds % 60}
if (not joiner):
if not joiner:
return "".join(str(v) + k for k, v in timeValues.items() if v > 0)
if (timeValues["h"] == 0):
if timeValues["h"] == 0:
del timeValues["h"]
return joiner.join(str(v).rjust(2, "0") for v in timeValues.values())