You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

110 lines
3.3 KiB

4 years ago
  1. """
  2. Support for discovery using GDM (Good Day Mate), multicast protocol by Plex.
  3. Inspired by
  4. hippojay's plexGDM:
  5. https://github.com/hippojay/script.plexbmc.helper/resources/lib/plexgdm.py
  6. iBaa's PlexConnect: https://github.com/iBaa/PlexConnect/PlexAPI.py
  7. """
  8. import socket
  9. import struct
  10. class GDM:
  11. """Base class to discover GDM services."""
  12. def __init__(self):
  13. self.entries = []
  14. self.last_scan = None
  15. def scan(self):
  16. """Scan the network."""
  17. self.update()
  18. def all(self):
  19. """Return all found entries.
  20. Will scan for entries if not scanned recently.
  21. """
  22. self.scan()
  23. return list(self.entries)
  24. def find_by_content_type(self, value):
  25. """Return a list of entries that match the content_type."""
  26. self.scan()
  27. return [entry for entry in self.entries
  28. if value in entry['data']['Content_Type']]
  29. def find_by_data(self, values):
  30. """Return a list of entries that match the search parameters."""
  31. self.scan()
  32. return [entry for entry in self.entries
  33. if all(item in entry['data'].items()
  34. for item in values.items())]
  35. def update(self):
  36. """Scan for new GDM services.
  37. Example of the dict list assigned to self.entries by this function:
  38. [{'data': {
  39. 'Content-Type': 'plex/media-server',
  40. 'Host': '53f4b5b6023d41182fe88a99b0e714ba.plex.direct',
  41. 'Name': 'myfirstplexserver',
  42. 'Port': '32400',
  43. 'Resource-Identifier': '646ab0aa8a01c543e94ba975f6fd6efadc36b7',
  44. 'Updated-At': '1444852697',
  45. 'Version': '0.9.12.13.1464-4ccd2ca',
  46. },
  47. 'from': ('10.10.10.100', 32414)}]
  48. """
  49. gdm_ip = '239.0.0.250' # multicast to PMS
  50. gdm_port = 32414
  51. gdm_msg = 'M-SEARCH * HTTP/1.0'.encode('ascii')
  52. gdm_timeout = 1
  53. self.entries = []
  54. # setup socket for discovery -> multicast message
  55. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  56. sock.settimeout(gdm_timeout)
  57. # Set the time-to-live for messages for local network
  58. sock.setsockopt(socket.IPPROTO_IP,
  59. socket.IP_MULTICAST_TTL,
  60. struct.pack("B", gdm_timeout))
  61. try:
  62. # Send data to the multicast group
  63. sock.sendto(gdm_msg, (gdm_ip, gdm_port))
  64. # Look for responses from all recipients
  65. while True:
  66. try:
  67. data, server = sock.recvfrom(1024)
  68. data = data.decode('utf-8')
  69. if '200 OK' in data.splitlines()[0]:
  70. data = {k: v.strip() for (k, v) in (
  71. line.split(':') for line in
  72. data.splitlines() if ':' in line)}
  73. self.entries.append({'data': data,
  74. 'from': server})
  75. except socket.timeout:
  76. break
  77. finally:
  78. sock.close()
  79. def main():
  80. """Test GDM discovery."""
  81. from pprint import pprint
  82. gdm = GDM()
  83. pprint("Scanning GDM...")
  84. gdm.update()
  85. pprint(gdm.entries)
  86. if __name__ == "__main__":
  87. main()