frontend.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import logging
  2. import time
  3. import json
  4. from typing import Optional
  5. import pykka
  6. import requests
  7. from mopidy.core import CoreListener
  8. logger = logging.getLogger(__name__)
  9. class WebhooksFrontend(pykka.ThreadingActor, CoreListener):
  10. def __init__(self, config, core):
  11. super().__init__()
  12. self.config = config
  13. self.webhook_urls = []
  14. self.last_start_time = None
  15. def on_start(self):
  16. self.webhook_urls = self.config["webhooks"]["urls"].split(",")
  17. self.webhook_tokens = self.config["webhooks"]["tokens"].split(",")
  18. logger.info(f"Parsing webhook URLs and tokens: {self.webhook_urls}")
  19. def _build_post_data(self, track, time_position: Optional[int]=None) -> dict:
  20. artists = ", ".join(sorted([a.name for a in track.artists]))
  21. artists_list = [a for a in track.artists]
  22. try:
  23. musicbrainz_artist_id = artists_list[0].musicbrainz_id
  24. except IndexError:
  25. musicbrainz_artist_id = None
  26. duration = track.length and track.length // 1000 or 0
  27. album_name = ""
  28. if track.album:
  29. album_name = getattr(track.album, "name")
  30. return {
  31. "name": track.name,
  32. "artist": artists,
  33. "album": album_name,
  34. "track_number": track.track_no,
  35. "run_time_ticks": track.length,
  36. "run_time": str(duration),
  37. "playback_time_ticks": time_position,
  38. "musicbrainz_track_id": track.musicbrainz_id if track.album else "",
  39. "musicbrainz_album_id": track.album.musicbrainz_id if track.album else "",
  40. "musicbrainz_artist_id": musicbrainz_artist_id,
  41. "mopidy_uri": track.uri,
  42. "primary_artist_mopidy_uri": artists_list[0].uri,
  43. }
  44. def _post_update_to_webhooks(self, post_data: dict, status: str):
  45. post_data["status"] = status
  46. for index, webhook_url in enumerate(self.webhook_urls):
  47. token = ""
  48. headers = {}
  49. try:
  50. token = self.webhook_tokens[index]
  51. except IndexError:
  52. logger.info(f"No token found for Webhook URL: {webhook_url}")
  53. if token:
  54. headers["Authorization"] = f"Token {token}"
  55. response = requests.post(
  56. webhook_url, json=json.dumps(post_data), headers=headers
  57. )
  58. logger.info(response)
  59. def track_playback_started(self, tl_track):
  60. track = tl_track.track
  61. artists = ", ".join(sorted([a.name for a in track.artists]))
  62. self.last_start_time = int(time.time())
  63. logger.debug(f"Now playing track: {artists} - {track.name}")
  64. post_data = self._build_post_data(tl_track.track, time_position=0)
  65. # Build post data to send to urls
  66. if not self.webhook_urls:
  67. logger.info("No webhook URLS are configured ")
  68. return
  69. logger.info(f"Scrobbling via webhooks: {artists} - {track.name}")
  70. self._post_update_to_webhooks(post_data, "started")
  71. def track_playback_ended(self, tl_track, time_position):
  72. track = tl_track.track
  73. artists = ", ".join(sorted([a.name for a in track.artists]))
  74. duration = track.length and track.length // 1000 or 0
  75. time_position_sec = time_position // 1000
  76. post_data = self._build_post_data(tl_track.track, time_position=time_position)
  77. if time_position_sec < duration // 2 and time_position_sec < 240:
  78. logger.debug(
  79. "Track not played long enough to scrobble. (50% or 240s)"
  80. )
  81. return
  82. if self.last_start_time is None:
  83. self.last_start_time = int(time.time()) - duration
  84. logger.info(
  85. f"Scrobbling finished via webhooks: {artists} - {track.name}"
  86. )
  87. self._post_update_to_webhooks(post_data, "stopped")
  88. def track_playback_paused(self, tl_track, time_position):
  89. track = tl_track.track
  90. artists = ", ".join(sorted([a.name for a in track.artists]))
  91. duration = track.length and track.length // 1000 or 0
  92. time_position_sec = time_position // 1000
  93. post_data = self._build_post_data(tl_track.track, time_position=time_position)
  94. if self.last_start_time is None:
  95. self.last_start_time = int(time.time()) - duration
  96. logger.info(
  97. f"Scrobbling paused via webhooks: {artists} - {track.name}"
  98. )
  99. self._post_update_to_webhooks(post_data, "paused")
  100. def track_playback_resumed(self, tl_track, time_position):
  101. track = tl_track.track
  102. artists = ", ".join(sorted([a.name for a in track.artists]))
  103. self.last_start_time = int(time.time())
  104. logger.debug(f"Now resuming track: {artists} - {track.name}")
  105. post_data = self._build_post_data(tl_track.track, time_position=time_position)
  106. # Build post data to send to urls
  107. if not self.webhook_urls:
  108. logger.info("No webhook URLS are configured ")
  109. return
  110. logger.info(f"Scrobbling via webhooks: {artists} - {track.name}")
  111. self._post_update_to_webhooks(post_data, "resumed")