Python #81

Merged
Ghost merged 7 commits from Python into master 2021-08-10 14:47:20 +02:00
2 changed files with 490 additions and 0 deletions
Showing only changes of commit 00c4e87f69 - Show all commits

View File

@ -0,0 +1,77 @@
# Plex Discord Activity
**Plex Discord Activity est un logiciel en Python. Il a été récuperé déjà depuis le dépot d'un utilisateur GitHub mais a été simplifié de sorte a ce que la pré-configuration soit la plus simple possible**
Il y a eu peu de modifications, mais elle vous simplifie grandement la vie
## Attention
Sachez que pour le script fonctionne, il faut qu'il tourne sur le même PC que votre application Discord. Malheureusement faire tourner ce script sur un serveur en tâche de fond ne donnera aucun résultat...
## Installation
Pour cela, commencez déjà par installer Python3 sur votre ordinateur si cela n'est pas déjà fait...
Ensuite installez ces 2 bibliothèques Python via pip3:
```bash
pip3 install plexapi
pip3 install websocket-client==0.48.0
```
## Configuration
Éditez le document pma.py et éditez ce paragraphe seulement:
```python
### SEULEMENT CE QUE VOUS DEVEZ EDITER
MyDomain = "https://your.plex.dns:32400" #Soit via votre nom de domaine, soit via le domaine de Plex, c'est à dire, https://app.plex.tv
Serveur = "MyPlex" #Le nom de votre Serveur Plex, visible dans Paramètres > Général > Nom d'usage
NomUser = "username" #Le nom d'utilisateur Plex pour récupérer les métadonnées et les informations de lecture en temps réel
MotDePasse = "password" #Votre mot de passe de votre compte Plex
MonToken = "your_token" #Votre Token, accessible a la fin de l'url lorsque vous consultez le fichier XML de l'un de vos médias
BlackList = ["Musique", "BackingTracks"] #séparée par des virgules avec espace, entourées par des "", entrent 2 balises [], ex: ["bib1", "bib2"]
UserOnly = "username" #Si vous avez crée des utilisateurs gérés, pour éviter de partager l'état de lecture d'un autre utilisateur que vous-même, précisez le psuedo du bon utilisateur a priori le même que le 3ème paramètre que vous...
InfosSupp = "true" #Ceci partage les métadonnées de votre épisode si c'est sur True, sinon mettez False pour n'afficher que l'état de lecture
TempsRestant = "true" #True = Afficher le temps restant de lecture / False = Afficher le nombre de minutes que vous avez déjà consulté depuis le démarrage de l'épisode
### END DE CE QUE VOUS DEVEZ EDITER
```
Voici un résumé des paramètres:
**Attention a ne pas supprimer des guillemets ou des crochets... Si vous ne voulez pas préciser de valeur, laissez les guillemets vides ou indiquez-y ``none`` !**
**Serveur**: C'est le nom de votre serveur Plex. Vous le trouverez dans ``Paramètres > Général > Nom d'usage``
**NomUser**: Votre nom d'utilisateur Plex associé à votre compte.
**MotDePasse**: Votre mot de passe de l'utilisateur Plex associé à votre compte.
**MonToken**: Token que vous pouvez obtenir en consultant le fichier XML d'un de vos média. Allez sur un épisode ou film précis, allez dans ``Voir informations`` puis ``Voir le XML``. Ensuite regardez à la fin de votre URL, vous devriez avoir: **Plex-Token=0x0x0xx0x00xx0x0**
**BlackList**: Vous pouvez décider de ne pas partager sur Discord les médias d'une bibliothèque définie, indiquez-y entre guillemets et eux-même entre crochets, le nom des Bibliothèques.
Exemple:
```python
BlackList = ["Musique", "BackingTracks"]
```
**UserOnly**: Si vous avez crée des utilisateurs gérés, pour éviter de partager l'état de lecture d'un autre utilisateur que vous-même, précisez le psuedo du bon utilisateur qui à priori est le même que celui du paramètre ``username``...
**InfosSupp**: Vous permet de partager davantage d'infos sur le média, à savoir les métadonnées, etc. (Réponse: True / False)
**TempsRestant**: Vous permet de préciser si vous voulez indiquer le temps restant du média ou la position actuelle (Réponse: True (Il reste 05:00 minutes) / False (Vous êtes à 15:00 minutes sur 20:00 minutes))
## Utilisation
```
python3 pma.py
```
*Patientez +/- 20 secondes pour que le script démarre... Le script lancera tout seul la page web Plex*
## Source
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
## License
[MIT](https://choosealicense.com/licenses/mit/)

View File

@ -0,0 +1,413 @@
import asyncio
import datetime
import hashlib
import json
import os
import plexapi.myplex
import struct
import subprocess
import sys
import tempfile
import threading
import time
import webbrowser
### SEULEMENT CE QUE VOUS DEVEZ EDITER
MyDomain = "https://your.plex.dns:32400" #Soit via votre nom de domaine, soit via le domaine de Plex, c'est à dire, https://app.plex.tv
Serveur = "MyPlex" #Le nom de votre Serveur Plex, visible dans Paramètres > Général > Nom d'usage
NomUser = "username" #Le nom d'utilisateur Plex pour récupérer les métadonnées et les informations de lecture en temps réel
MotDePasse = "password" #Votre mot de passe de votre compte Plex
MonToken = "your_token" #Votre Token, accessible a la fin de l'url lorsque vous consultez le fichier XML de l'un de vos médias
BlackList = ["Musique", "BackingTracks"] #séparée par des virgules avec espace, entourées par des "", entrent 2 balises [], ex: ["bib1", "bib2"]
UserOnly = "username" #Si vous avez crée des utilisateurs gérés, pour éviter de partager l'état de lecture d'un autre utilisateur que vous-même, précisez le psuedo du bon utilisateur a priori le même que le 3ème paramètre que vous...
InfosSupp = "true" #Ceci partage les métadonnées de votre épisode si c'est sur True, sinon mettez False pour n'afficher que l'état de lecture
TempsRestant = "true" #True = Afficher le temps restant de lecture / False = Afficher le nombre de minutes que vous avez déjà consulté depuis le démarrage de l'épisode
### END DE CE QUE VOUS DEVEZ EDITER
webbrowser.open(MyDomain)
class plexConfig:
extraLogging = (InfosSupp)
timeRemaining = (TempsRestant)
def __init__(self, serverName = "", username = "", password = "", token = "", listenForUser = "", blacklistedLibraries = None, whitelistedLibraries = None, clientID = "413407336082833418"):
self.serverName = serverName
self.username = username
self.password = password
self.token = token
self.listenForUser = (username if listenForUser == "" else listenForUser).lower()
self.blacklistedLibraries = blacklistedLibraries
self.whitelistedLibraries = whitelistedLibraries
self.clientID = clientID
plexConfigs = [
plexConfig(serverName = (Serveur), username = (NomUser), password = (MotDePasse), token = (MonToken), blacklistedLibraries = (BlackList), listenForUser = (UserOnly))
]
class discordRichPresence:
def __init__(self, clientID, child):
self.IPCPipe = ((os.environ.get("XDG_RUNTIME_DIR", None) or os.environ.get("TMPDIR", None) or os.environ.get("TMP", None) or os.environ.get("TEMP", None) or "/tmp") + "/discord-ipc-0") if isLinux else "\\\\?\\pipe\\discord-ipc-0"
self.clientID = clientID
self.pipeReader = None
self.pipeWriter = None
self.process = None
self.running = False
self.child = child
async def read(self):
try:
data = await self.pipeReader.read(1024)
self.child.log("[READ] " + str(json.loads(data[8:].decode("utf-8"))))
except Exception as e:
self.child.log("[READ] " + str(e))
self.stop()
def write(self, op, payload):
payload = json.dumps(payload)
self.child.log("[WRITE] " + str(payload))
data = self.pipeWriter.write(struct.pack("<ii", op, len(payload)) + payload.encode("utf-8"))
async def handshake(self):
try:
if (isLinux):
self.pipeReader, self.pipeWriter = await asyncio.open_unix_connection(self.IPCPipe, loop = self.loop)
else:
self.pipeReader = asyncio.StreamReader(loop = self.loop)
self.pipeWriter, _ = await self.loop.create_pipe_connection(lambda: asyncio.StreamReaderProtocol(self.pipeReader, loop = self.loop), self.IPCPipe)
self.write(0, {"v": 1, "client_id": self.clientID})
await self.read()
self.running = True
except Exception as e:
self.child.log("[HANDSHAKE] " + str(e))
def start(self):
self.child.log("Opening Discord IPC Pipe")
emptyProcessFilePath = tempfile.gettempdir() + ("/" if isLinux else "\\") + "discordRichPresencePlex-emptyProcess.py"
if (not os.path.exists(emptyProcessFilePath)):
with open(emptyProcessFilePath, "w") as emptyProcessFile:
emptyProcessFile.write("import time\n\ntry:\n\twhile (True):\n\t\ttime.sleep(3600)\nexcept:\n\tpass")
self.process = subprocess.Popen(["python3" if isLinux else "pythonw", emptyProcessFilePath])
self.loop = asyncio.new_event_loop() if isLinux else asyncio.ProactorEventLoop()
self.loop.run_until_complete(self.handshake())
def stop(self):
self.child.log("Closing Discord IPC Pipe")
self.child.lastState, self.child.lastSessionKey, self.child.lastRatingKey = None, None, None
self.process.kill()
if (self.child.stopTimer):
self.child.stopTimer.cancel()
self.child.stopTimer = None
if (self.child.stopTimer2):
self.child.stopTimer2.cancel()
self.child.stopTimer2 = None
if (self.pipeWriter):
try:
self.pipeWriter.close()
except:
pass
self.pipeWriter = None
if (self.pipeReader):
try:
self.loop.run_until_complete(self.pipeReader.read(1024))
except:
pass
self.pipeReader = None
try:
self.loop.close()
except:
pass
self.running = False
def send(self, activity):
payload = {
"cmd": "SET_ACTIVITY",
"args": {
"activity": activity,
"pid": self.process.pid
},
"nonce": "{0:.20f}".format(time.time())
}
self.write(1, payload)
self.loop.run_until_complete(self.read())
class discordRichPresencePlex(discordRichPresence):
productName = "Plex Media Server"
stopTimerInterval = 5
stopTimer2Interval = 35
checkConnectionTimerInterval = 60
maximumIgnores = 3
def __init__(self, plexConfig):
self.plexConfig = plexConfig
self.instanceID = hashlib.md5(str(id(self)).encode("UTF-8")).hexdigest()[:5]
super().__init__(plexConfig.clientID, self)
self.plexAccount = None
self.plexServer = None
self.isServerOwner = False
self.plexAlertListener = None
self.lastState = None
self.lastSessionKey = None
self.lastRatingKey = None
self.stopTimer = None
self.stopTimer2 = None
self.checkConnectionTimer = None
self.ignoreCount = 0
def run(self):
self.reset()
connected = False
while (not connected):
try:
if (self.plexConfig.token):
self.plexAccount = plexapi.myplex.MyPlexAccount(self.plexConfig.username, token = self.plexConfig.token)
else:
self.plexAccount = plexapi.myplex.MyPlexAccount(self.plexConfig.username, self.plexConfig.password)
self.log("Logged in as Plex User \"" + self.plexAccount.username + "\"")
self.plexServer = None
for resource in self.plexAccount.resources():
if (resource.product == self.productName and resource.name == self.plexConfig.serverName):
self.plexServer = resource.connect()
try:
self.plexServer.account()
self.isServerOwner = True
except:
pass
self.log("Connected to " + self.productName + " \"" + self.plexConfig.serverName + "\"")
self.plexAlertListener = self.plexServer.startAlertListener(self.onPlexServerAlert)
self.log("Listening for PlaySessionStateNotification alerts from user \"" + self.plexConfig.listenForUser + "\"")
if (self.checkConnectionTimer):
self.checkConnectionTimer.cancel()
self.checkConnectionTimer = None
self.checkConnectionTimer = threading.Timer(self.checkConnectionTimerInterval, self.checkConnection)
self.checkConnectionTimer.start()
connected = True
break
if (not self.plexServer):
self.log(self.productName + " \"" + self.plexConfig.serverName + "\" not found")
break
except Exception as e:
self.log("Failed to connect to Plex: " + str(e))
self.log("Reconnecting in 10 seconds")
time.sleep(10)
def reset(self):
if (self.running):
self.stop()
self.plexAccount, self.plexServer = None, None
if (self.plexAlertListener):
try:
self.plexAlertListener.stop()
except:
pass
self.plexAlertListener = None
if (self.stopTimer):
self.stopTimer.cancel()
self.stopTimer = None
if (self.stopTimer2):
self.stopTimer2.cancel()
self.stopTimer2 = None
if (self.checkConnectionTimer):
self.checkConnectionTimer.cancel()
self.checkConnectionTimer = None
def checkConnection(self):
try:
self.log("Request for clients list to check connection: " + str(self.plexServer.clients()), extra = True)
self.checkConnectionTimer = threading.Timer(self.checkConnectionTimerInterval, self.checkConnection)
self.checkConnectionTimer.start()
except Exception as e:
self.log("Connection to Plex lost: " + str(e))
self.log("Reconnecting")
self.run()
def log(self, text, colour = "", extra = False):
timestamp = datetime.datetime.now().strftime("%I:%M:%S %p")
prefix = "[" + timestamp + "] [" + self.plexConfig.serverName + "/" + self.instanceID + "] "
lock.acquire()
if (extra):
if (self.plexConfig.extraLogging):
print(prefix + colourText(str(text), colour))
else:
print(prefix + colourText(str(text), colour))
lock.release()
def onPlexServerAlert(self, data):
if (not self.plexServer):
return
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.log("Received Update: " + colourText(sessionData, "yellow").replace("'", "\""), extra = True)
metadata = self.plexServer.fetchItem(ratingKey)
libraryName = metadata.section().title
if (isinstance(self.plexConfig.blacklistedLibraries, list)):
if (libraryName in self.plexConfig.blacklistedLibraries):
self.log("Library \"" + libraryName + "\" is blacklisted, ignoring", "yellow", True)
return
if (isinstance(self.plexConfig.whitelistedLibraries, list)):
if (libraryName not in self.plexConfig.whitelistedLibraries):
self.log("Library \"" + libraryName + "\" is not whitelisted, ignoring", "yellow", True)
return
if (self.lastSessionKey == sessionKey and self.lastRatingKey == ratingKey):
if (self.stopTimer2):
self.stopTimer2.cancel()
self.stopTimer2 = None
if (self.lastState == state):
if (self.ignoreCount == self.maximumIgnores):
self.ignoreCount = 0
else:
self.log("Nothing changed, ignoring", "yellow", True)
self.ignoreCount += 1
self.stopTimer2 = threading.Timer(self.stopTimer2Interval, self.stopOnNoUpdate)
self.stopTimer2.start()
return
elif (state == "stopped"):
self.lastState, self.lastSessionKey, self.lastRatingKey = None, None, None
self.stopTimer = threading.Timer(self.stopTimerInterval, self.stop)
self.stopTimer.start()
self.log("Started stopTimer", "yellow", True)
return
elif (state == "stopped"):
self.log("\"stopped\" state update from unknown session key, ignoring", "yellow", True)
return
if (self.isServerOwner):
self.log("Checking Sessions for Session Key " + colourText(sessionKey, "yellow"), extra = True)
plexServerSessions = self.plexServer.sessions()
if (len(plexServerSessions) < 1):
self.log("Empty session list, ignoring", "red", True)
return
for session in plexServerSessions:
self.log(str(session) + ", Session Key: " + colourText(session.sessionKey, "yellow") + ", Users: " + colourText(session.usernames, "yellow").replace("'", "\""), extra = True)
sessionFound = False
if (session.sessionKey == sessionKey):
sessionFound = True
self.log("Session found", "green", True)
if (session.usernames[0].lower() == self.plexConfig.listenForUser):
self.log("Username \"" + session.usernames[0].lower() + "\" matches \"" + self.plexConfig.listenForUser + "\", continuing", "green", True)
break
else:
self.log("Username \"" + session.usernames[0].lower() + "\" doesn't match \"" + self.plexConfig.listenForUser + "\", ignoring", "red", True)
return
if (not sessionFound):
self.log("No matching session found", "red", True)
return
if (self.stopTimer):
self.stopTimer.cancel()
self.stopTimer = None
if (self.stopTimer2):
self.stopTimer2.cancel()
self.stopTimer2 = threading.Timer(self.stopTimer2Interval, self.stopOnNoUpdate)
self.stopTimer2.start()
self.lastState, self.lastSessionKey, self.lastRatingKey = state, sessionKey, ratingKey
mediaType = metadata.type
if (state != "playing"):
extra = secondsToText(viewOffset / 1000, ":") + "/" + secondsToText(metadata.duration / 1000, ":")
else:
extra = secondsToText(metadata.duration / 1000)
if (mediaType == "movie"):
title = metadata.title + " (" + str(metadata.year) + ")"
extra = extra + " · " + ", ".join([genre.tag for genre in metadata.genres[:3]])
largeText = "Watching a Movie"
elif (mediaType == "episode"):
title = metadata.grandparentTitle
extra = extra + " · S" + str(metadata.parentIndex) + " · E" + str(metadata.index) + " - " + metadata.title
largeText = "Watching a TV Show"
elif (mediaType == "track"):
title = metadata.title
artist = metadata.originalTitle
if (not artist):
artist = metadata.grandparentTitle
extra = artist + " · " + metadata.parentTitle
largeText = "Listening to Music"
else:
self.log("Unsupported media type \"" + mediaType + "\", ignoring", "red", True)
return
activity = {
"details": title,
"state": extra,
"assets": {
"large_text": largeText,
"large_image": "logo",
"small_text": state.capitalize(),
"small_image": state
},
}
if (state == "playing"):
currentTimestamp = int(time.time())
if (self.plexConfig.timeRemaining):
activity["timestamps"] = {"end": round(currentTimestamp + ((metadata.duration - viewOffset) / 1000))}
else:
activity["timestamps"] = {"start": round(currentTimestamp - (viewOffset / 1000))}
if (not self.running):
self.start()
if (self.running):
self.send(activity)
else:
self.stop()
except Exception as e:
self.log("onPlexServerAlert Error: " + str(e))
def stopOnNoUpdate(self):
self.log("No updates from session key " + str(self.lastSessionKey) + ", stopping", "red", True)
self.stop()
isLinux = sys.platform in ["linux", "darwin"]
lock = threading.Semaphore(value = 1)
os.system("clear" if isLinux else "cls")
if (len(plexConfigs) == 0):
print("Error: plexConfigs list is empty")
sys.exit()
colours = {
"red": "91",
"green": "92",
"yellow": "93",
"blue": "94",
"magenta": "96",
"cyan": "97"
}
def colourText(text, colour = ""):
prefix = ""
suffix = ""
colour = colour.lower()
if (colour in colours):
prefix = "\033[" + colours[colour] + "m"
suffix = "\033[0m"
return prefix + str(text) + suffix
def secondsToText(seconds, joiner = ""):
seconds = round(seconds)
text = {"h": seconds // 3600, "m": seconds // 60 % 60, "s": seconds % 60}
if (joiner == ""):
text = [str(v) + k for k, v in text.items() if v > 0]
else:
if (text["h"] == 0):
del text["h"]
text = [str(v).rjust(2, "0") for k, v in text.items()]
return joiner.join(text)
discordRichPresencePlexInstances = []
for config in plexConfigs:
discordRichPresencePlexInstances.append(discordRichPresencePlex(config))
try:
for discordRichPresencePlexInstance in discordRichPresencePlexInstances:
discordRichPresencePlexInstance.run()
while (True):
time.sleep(3600)
except KeyboardInterrupt:
for discordRichPresencePlexInstance in discordRichPresencePlexInstances:
discordRichPresencePlexInstance.reset()
except Exception as e:
print("Error: " + str(e))