Guest User

Untitled

a guest
Jun 9th, 2018
171
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.24 KB | None | 0 0
  1. import asyncio
  2. import json
  3. import os
  4. import struct
  5. import sys
  6. import time
  7. from plexapi.myplex import MyPlexAccount
  8.  
  9.  
  10. ### EDIT SETTINGS ###
  11.  
  12. PLEX_SERVER = 'Server Name'
  13. PLEX_USERNAME = 'Username'
  14. PLEX_PASSWORD = 'Password'
  15. PLEX_HOME_USER_OVERRIDE = '' # Username override if you are using a Managed User logged in using the admin account
  16.  
  17. ### OPTIONAL SETTINGS ###
  18.  
  19. DISCORD_CLIENT_ID = '409127705980829707'
  20.  
  21.  
  22. ### CODE BELOW ###
  23.  
  24. PREVIOUS_STATE = None
  25. PREVIOUS_SESSION_KEY = None
  26. PREVIOUS_RATING_KEY = None
  27.  
  28.  
  29. class DiscordRPC:
  30. def __init__(self, client_id):
  31. if sys.platform == 'linux' or sys.platform == 'darwin':
  32. self.ipc_path = (os.environ.get('XDG_RUNTIME_DIR', None) or os.environ.get('TMPDIR', None) or
  33. os.environ.get('TMP', None) or os.environ.get('TEMP', None) or '/tmp') + '/discord-ipc-0'
  34. self.loop = asyncio.get_event_loop()
  35. elif sys.platform == 'win32':
  36. self.ipc_path = r'\\?\pipe\discord-ipc-0'
  37. self.loop = asyncio.ProactorEventLoop()
  38. self.sock_reader: asyncio.StreamReader = None
  39. self.sock_writer: asyncio.StreamWriter = None
  40. self.client_id = client_id
  41.  
  42. async def read_output(self):
  43. print("reading output")
  44. data = await self.sock_reader.read(1024)
  45. code, length = struct.unpack('<ii', data[:8])
  46. print(f'OP Code: {code}; Length: {length}\nResponse:\n{json.loads(data[8:].decode("utf-8"))}\n')
  47.  
  48. def send_data(self, op: int, payload: dict):
  49. payload = json.dumps(payload)
  50. data = self.sock_writer.write(struct.pack('<ii', op, len(payload)) + payload.encode('utf-8'))
  51.  
  52. async def handshake(self):
  53. if sys.platform == 'linux' or sys.platform == 'darwin':
  54. self.sock_reader, self.sock_writer = await asyncio.open_unix_connection(self.ipc_path, loop=self.loop)
  55. elif sys.platform == 'win32':
  56. self.sock_reader = asyncio.StreamReader(loop=self.loop)
  57. reader_protocol = asyncio.StreamReaderProtocol(self.sock_reader, loop=self.loop)
  58. self.sock_writer, _ = await self.loop.create_pipe_connection(lambda: reader_protocol, self.ipc_path)
  59. self.send_data(0, {'v': 1, 'client_id': self.client_id})
  60. data = await self.sock_reader.read(1024)
  61. code, length = struct.unpack('<ii', data[:8])
  62. print(f'OP Code: {code}; Length: {length}\nResponse:\n{json.loads(data[8:].decode("utf-8"))}\n')
  63.  
  64. def send_rich_presence(self, activity):
  65. current_time = time.time()
  66. payload = {
  67. "cmd": "SET_ACTIVITY",
  68. "args": {
  69. "activity": activity,
  70. "pid": os.getpid()
  71. },
  72. "nonce": f'{current_time:.20f}'
  73. }
  74. print("sending data")
  75. sent = self.send_data(1, payload)
  76. self.loop.run_until_complete(self.read_output())
  77.  
  78. def close(self):
  79. self.sock_writer.close()
  80. self.loop.close()
  81.  
  82. def start(self):
  83. self.loop.run_until_complete(self.handshake())
  84.  
  85.  
  86. def clear_rich_presence():
  87. # The Discord rich presence payload
  88. activity = {
  89. 'details': 'Nothing is playing',
  90. 'assets': {
  91. 'large_text': 'Plex',
  92. 'large_image': 'plex_logo',
  93. },
  94. }
  95.  
  96. # Set Discord rich presence
  97. RPC.send_rich_presence(activity)
  98.  
  99.  
  100. def process_alert(data):
  101. if data.get('type') == 'playing':
  102. session_data = data.get('PlaySessionStateNotification', [])[0]
  103. state = session_data.get('state', 'stopped')
  104. session_key = session_data.get('sessionKey', None)
  105. rating_key = session_data.get('ratingKey', None)
  106. view_offset = session_data.get('viewOffset', 0)
  107.  
  108. if session_key and session_key.isdigit():
  109. session_key = int(session_key)
  110. else:
  111. return
  112.  
  113. if rating_key and rating_key.isdigit():
  114. rating_key = int(rating_key)
  115. else:
  116. return
  117.  
  118. global PREVIOUS_STATE
  119. global PREVIOUS_SESSION_KEY
  120. global PREVIOUS_RATING_KEY
  121.  
  122. # Clear the rich presence if the session is stopped
  123. if state == 'stopped' and PREVIOUS_SESSION_KEY == session_key and PREVIOUS_RATING_KEY == rating_key:
  124. PREVIOUS_STATE = None
  125. PREVIOUS_SESSION_KEY = None
  126. PREVIOUS_RATING_KEY = None
  127. clear_rich_presence()
  128. return
  129. elif state == 'stopped':
  130. return
  131.  
  132. # If Plex server admin, make sure the alert is for the current user
  133. if plex_admin:
  134. for session in plex.sessions():
  135. if session.sessionKey == session_key:
  136. if PLEX_HOME_USER_OVERRIDE and session.usernames[0].lower() == PLEX_HOME_USER_OVERRIDE.lower():
  137. break
  138. if not PLEX_HOME_USER_OVERRIDE and session.usernames[0].lower() == PLEX_USERNAME.lower():
  139. break
  140. else:
  141. return
  142.  
  143. # Skip if the session key and state hasn't changed
  144. if PREVIOUS_STATE == state and PREVIOUS_SESSION_KEY == session_key and PREVIOUS_RATING_KEY == rating_key:
  145. return
  146.  
  147. # Save the session
  148. PREVIOUS_STATE = state
  149. PREVIOUS_SESSION_KEY = session_key
  150. PREVIOUS_RATING_KEY = rating_key
  151. metadata = plex.fetchItem(rating_key)
  152.  
  153. # Format Discord rich presence text based on media type
  154. media_type = metadata.type
  155.  
  156. if media_type == 'movie':
  157. title = metadata.title
  158. subtitle = str(metadata.year)
  159. elif media_type == 'episode':
  160. title = f'{metadata.grandparentTitle} - {metadata.title}'
  161. subtitle = f'S{metadata.parentIndex} ยท E{metadata.index}'
  162. elif media_type == 'track':
  163. title = f'{metadata.grandparentTitle} - {metadata.title}'
  164. subtitle = metadata.parentTitle
  165. else:
  166. return
  167.  
  168. # The Discord rich presence payload
  169. activity = {
  170. 'details': title,
  171. 'state': subtitle,
  172. 'assets': {
  173. 'large_text': 'Plex',
  174. 'large_image': 'plex_logo',
  175. 'small_text': state.capitalize(),
  176. 'small_image': state
  177. },
  178. }
  179.  
  180. # Set the timestamp
  181. if state == 'playing':
  182. current_time = int(time.time())
  183. start_time = current_time - view_offset / 1000
  184. activity['timestamps'] = {'start': start_time}
  185.  
  186. # Set Discord rich presence
  187. RPC.send_rich_presence(activity)
  188.  
  189.  
  190. if __name__ == "__main__":
  191. account = MyPlexAccount(PLEX_USERNAME, PLEX_PASSWORD)
  192. plex = account.resource(PLEX_SERVER).connect()
  193. plex_admin = (account.email == plex.myPlexUsername or account.username == plex.myPlexUsername)
  194. plex.startAlertListener(process_alert)
  195.  
  196. RPC = DiscordRPC(DISCORD_CLIENT_ID) # Send the client ID to the rpc module
  197. RPC.start() # Start the RPC connection
  198. clear_rich_presence() # Clear rich presence
  199. time.sleep(10) # Delay to make sure initial state is set
  200.  
  201. try:
  202. while True:
  203. time.sleep(3600)
  204. continue
  205. except KeyboardInterrupt:
  206. print("Exiting Discord RPC")
  207. RPC.close()
Add Comment
Please, Sign In to add comment