Use mmcli for statuses, remove unnecessary code, expire at song end
This commit is contained in:
parent
1817824541
commit
4540315735
1 changed files with 39 additions and 78 deletions
115
mpd_mm.py
115
mpd_mm.py
|
@ -1,23 +1,17 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import subprocess
|
||||||
import requests
|
|
||||||
import datetime
|
import datetime
|
||||||
import mpd
|
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import mpd
|
||||||
|
|
||||||
NAME = "MPD MM now playing status"
|
NAME = "MPD MM now playing status"
|
||||||
VERSION = "0.0.1"
|
VERSION = "0.0.1"
|
||||||
|
|
||||||
### Config ########
|
### Config ########
|
||||||
|
|
||||||
# E.g. https://mattermost.zeus.gent/api
|
|
||||||
MM_API = os.getenv("MM_API")
|
|
||||||
# Get one with mmcli, or by looking in your browser cookie. E.g. gh3cd6ef10abij2kl2nm9op8qr
|
|
||||||
MM_API_ACCESS_TOKEN = os.getenv("MM_API_ACCESS_TOKEN")
|
|
||||||
|
|
||||||
MPD_HOST = "/var/lib/mpd/socket"
|
MPD_HOST = "/var/lib/mpd/socket"
|
||||||
MPD_PORT = None
|
MPD_PORT = None
|
||||||
# or to connect with TCP
|
# or to connect with TCP
|
||||||
|
@ -27,81 +21,32 @@ MPD_PORT = None
|
||||||
EMOJI_PAUSED = "pause_button"
|
EMOJI_PAUSED = "pause_button"
|
||||||
EMOJI_PLAYING = "musical_note"
|
EMOJI_PLAYING = "musical_note"
|
||||||
|
|
||||||
# Make the status automatically expire after this many minutes in case this script fails to clear
|
|
||||||
# its status when it's done, e.g. due to a loss of internet connection.
|
|
||||||
# This expiration is handled by the Mattermost server.
|
|
||||||
# Set to 0 or None to disable.
|
|
||||||
AUTO_EXPIRE_MINUTES = 60
|
|
||||||
|
|
||||||
###################
|
###################
|
||||||
|
|
||||||
|
|
||||||
HEADERS = {
|
LOGGER = logging.getLogger("mpd_mm")
|
||||||
"Authorization": f"Bearer {MM_API_ACCESS_TOKEN}",
|
|
||||||
"User-Agent": f"{NAME}/{VERSION}"
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger("MPD MM now playing")
|
|
||||||
|
|
||||||
MPD_STATE_PLAY = "play"
|
MPD_STATE_PLAY = "play"
|
||||||
MPD_STATE_PAUSE = "pause"
|
MPD_STATE_PAUSE = "pause"
|
||||||
|
|
||||||
# Util {{{1
|
|
||||||
# ----
|
|
||||||
|
|
||||||
class FastLoopAvoider:
|
|
||||||
def __init__(self, logger, threshold_seconds=1, warn_count=10, stop_count=20, sleep=0):
|
|
||||||
self.threshold_seconds = threshold_seconds
|
|
||||||
self.warn_count = warn_count
|
|
||||||
self.stop_count = stop_count
|
|
||||||
self.sleep = sleep
|
|
||||||
self._last_loop_start = 0
|
|
||||||
self._fast_loops = 0
|
|
||||||
self.logger = logging.getLogger(logger)
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
now = time.time()
|
|
||||||
if now - self._last_loop_start < self.threshold_seconds:
|
|
||||||
self._fast_loops += 1
|
|
||||||
self._last_loop_start = now
|
|
||||||
|
|
||||||
if self.stop_count and self._fast_loops == self.stop_count:
|
|
||||||
self.logger.error(f"Stopping because loop reached {self._fast_loops} fast runs")
|
|
||||||
return False
|
|
||||||
if self.warn_count and self._fast_loops == self.warn_count:
|
|
||||||
if self.stop_count:
|
|
||||||
self.logger.warn(f"Loop reached {self._fast_loops} fast runs, stopping at {self.stop_count}")
|
|
||||||
else:
|
|
||||||
self.logger.warn(f"Loop reached {self._fast_loops} fast runs")
|
|
||||||
|
|
||||||
if self.sleep:
|
|
||||||
time.sleep(self.sleep)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Mattermost stuff {{{1
|
# Mattermost stuff {{{1
|
||||||
# ----------------
|
# ----------------
|
||||||
|
|
||||||
def set_status(emoji, text, expires_datetime):
|
def set_status(emoji, text, expires_datetime):
|
||||||
expires = expires_datetime.isoformat() if expires_datetime else None
|
LOGGER.info(f"Custom status expiring {expires_datetime or 'never'}: :{emoji}: {text}")
|
||||||
LOGGER.info(f"Setting custom status to :{emoji}: {text} expiring {expires or 'never'}")
|
subprocess.run([
|
||||||
|
"mmcli", "customstatus",
|
||||||
r = requests.put(
|
"--until", expires_datetime.isoformat() if expires_datetime else "",
|
||||||
f"{MM_API}/v4/users/me/status/custom",
|
"--emoji", emoji,
|
||||||
json={
|
"--", text
|
||||||
"emoji": emoji,
|
], check=True)
|
||||||
"text": text,
|
|
||||||
"expires_at": expires
|
|
||||||
},
|
|
||||||
headers=HEADERS
|
|
||||||
)
|
|
||||||
r.raise_for_status()
|
|
||||||
|
|
||||||
|
|
||||||
def clear_status():
|
def clear_status():
|
||||||
LOGGER.info("Clearing custom status")
|
LOGGER.info("Clearing status")
|
||||||
r = requests.delete(f"{MM_API}/v4/users/me/status/custom", headers=HEADERS)
|
subprocess.run(["mmcli", "customstatus"], check=True)
|
||||||
r.raise_for_status()
|
|
||||||
|
|
||||||
# MPD stuff {{{1
|
# MPD stuff {{{1
|
||||||
# ---------
|
# ---------
|
||||||
|
@ -112,33 +57,49 @@ def song_string(song_info):
|
||||||
return f"{artist} – {title}"
|
return f"{artist} – {title}"
|
||||||
|
|
||||||
|
|
||||||
def set_status_from_mpd(mpd_client):
|
def formatted_status(mpd_client):
|
||||||
status = mpd_client.status()
|
status = mpd_client.status()
|
||||||
state = status.get("state")
|
state = status.get("state")
|
||||||
LOGGER.debug(f"Player state: {state!r}")
|
LOGGER.debug(f"Player state: {state!r}")
|
||||||
if state not in (MPD_STATE_PLAY, MPD_STATE_PAUSE):
|
if state not in (MPD_STATE_PLAY, MPD_STATE_PAUSE):
|
||||||
clear_status()
|
return None
|
||||||
return
|
|
||||||
|
|
||||||
song = mpd_client.currentsong()
|
song = mpd_client.currentsong()
|
||||||
song_str = song_string(song)
|
song_str = song_string(song)
|
||||||
emoji = EMOJI_PAUSED if state == MPD_STATE_PAUSE else EMOJI_PLAYING
|
emoji = EMOJI_PAUSED if state == MPD_STATE_PAUSE else EMOJI_PLAYING
|
||||||
|
|
||||||
if AUTO_EXPIRE_MINUTES:
|
|
||||||
now = datetime.datetime.now().astimezone()
|
|
||||||
expire = now + datetime.timedelta(seconds=AUTO_EXPIRE_MINUTES * 60)
|
|
||||||
else:
|
|
||||||
expire = None
|
expire = None
|
||||||
|
elapsed = status.get("elapsed")
|
||||||
|
duration = status.get("duration")
|
||||||
|
if state == MPD_STATE_PLAY and elapsed is not None and duration:
|
||||||
|
now = datetime.datetime.now().astimezone()
|
||||||
|
try:
|
||||||
|
# 1 second extra to allow some time to set the new song without flickering status
|
||||||
|
expire = now + datetime.timedelta(seconds=1 + float(duration) - float(elapsed))
|
||||||
|
except ValueError as e:
|
||||||
|
LOGGER.error("Could not calculate expiry time", exc_info=e)
|
||||||
|
|
||||||
|
return (emoji, song_str, expire)
|
||||||
|
|
||||||
|
|
||||||
|
# Driving stuff {{{1
|
||||||
|
# -------------
|
||||||
|
|
||||||
|
def set_status_from_mpd(mpd_client):
|
||||||
|
status = formatted_status(mpd_client)
|
||||||
|
if status is None:
|
||||||
|
clear_status()
|
||||||
|
else:
|
||||||
|
emoji, song_str, expire = status
|
||||||
set_status(emoji, song_str, expire)
|
set_status(emoji, song_str, expire)
|
||||||
|
|
||||||
|
|
||||||
def loop(mpd_client, on_status_change):
|
def loop(mpd_client, on_status_change):
|
||||||
fla = FastLoopAvoider("MPD-MM main loop", sleep=1)
|
while True:
|
||||||
while fla.loop():
|
|
||||||
mpd_client.idle("player")
|
mpd_client.idle("player")
|
||||||
LOGGER.debug("Got event from MPD")
|
LOGGER.debug("Got event from MPD")
|
||||||
on_status_change(mpd_client)
|
on_status_change(mpd_client)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
def main(mpd_host, mpd_port):
|
def main(mpd_host, mpd_port):
|
||||||
|
|
Loading…
Reference in a new issue