complete-computing-environment/npbot.org

6.3 KiB

My "Now Playing" on Fediverse

,#+CCE_ANSIBLE: npbot

When I am active on the Fediverse and I am Intentionally Listening to some Important Albums, it's nice to be able to share those tracks with others. I amalgamate all my listening stats ("scrobbles") on a roam:Last.FM Pro account, except for some years where I was only using gnu.fm instances which don't exist any more. Anyways, this bot includes a youtube link, or a link to my roam:MPD Server HTTP stream.

digraph {

  subgraph cluster_0 {
    label="server"
    "server mpd" -> "server mpdscribble"
    "npbot"
  }
  "server mpdscribble" -> "LastFM"

  subgraph cluster_1 {
    label="laptop"
    "laptop mpd" -> "laptop mpdscribble"
  }
  "laptop mpdscribble" -> "LastFM"

  subgraph cluster_2 {
    label="phone"
    "notifications" -> "scrooble"
  }
  scrooble -> LastFM

  LastFM -> npbot -> "botsin.space"
}

/rrix/complete-computing-environment/src/branch/main/data/scrobble-dataflow.png

NEXT fix npbot…

Container build using Ansible Bender and Podman

This generates a dynamic playbook and then tells you how to run it:

cat <<EOF | python ~/org/cce/make-container-playbook.py
template: ~/org/cce/containers/simple.yml
friendly: Now Playing on Fediverse
service_name: npbot
build_roles:
- nowplaying-bot-build
build_reqs:
- python-devel
- python-pip
- libffi-devel
- python3-cffi
cmd: ananas /config.cfg
task_tags:
- comms
- media
- npbot
EOF
- name: requirements copied in
  copy:
    src: requirements.txt
    dest: "{{build_dir}}/requirements.txt"
  
- name: python dependencies installed
  pip:
    state: present
    requirements: "{{build_dir}}/requirements.txt"

- name: files installed
  copy:
    src: "{{item}}"
    dest: /
  with_items:
  - nowplaying.py
  - config.cfg

Nowplaying plugin source for roam:Ananas

pylast==2.1.0
ananas==1.0.0b17
requests==2.18.4
import requests
from pylast import LastFMNetwork
from ananas import PineappleBot, ConfigurationError, interval

def search_youtube(q, max_results=1, key=None):
    if not key:
        raise ValueError('search_youtube() requires a key to authorize with the Youtube v3 API')

    r = requests.get('https://www.googleapis.com/youtube/v3/search',
                     params={'q': q, 'type': 'video', 'maxResults': max_results,
                             'part': 'snippet', 'key': key})

    items = []
    for item in r.json().get('items', []):
        items.append({'id': item['id']['videoId'], 'title': item['snippet']['title']})

    return items

class NowplayingBot(PineappleBot):
    def init(self):
        self.last_posted_track = None

    def start(self):
        for k in ['lastfm_api_key', 'lastfm_api_secret', 'lastfm_username', 'lastfm_password_hash',
                  'youtube_key', 'use_last_played']:
            if k not in self.config:
                raise ConfigurationError(f"NowplayingBot requires a '{k}'")

        self.lastfm = LastFMNetwork(api_key=self.config.lastfm_api_key, api_secret=self.config.lastfm_api_secret,
                                    username=self.config.lastfm_username,
                                    password_hash=self.config.lastfm_password_hash)

        self.post_np()

    @interval(30)
    def post_np(self):
        # grab the track from the last.fm api
        if self.config.use_last_played == 'yes':
            currently_playing = self.lastfm.get_user(self.config.lastfm_username).get_recent_tracks(1)[0][0]
        else:
            currently_playing = self.lastfm.get_user(self.config.lastfm_username).get_now_playing()

        # don't try to post if nothing is being played
        if currently_playing is None:
            return

        # don't repost if we've already posted about this track
        if currently_playing.__hash__() == self.last_posted_track:
            return
        else:
            self.last_posted_track = currently_playing.__hash__()

        # make a best-effort guess at the youtube link for this track
        yt_search = search_youtube(str(currently_playing), key=self.config.youtube_key)
        if len(yt_search) > 0:
            yt_link = f"https://www.youtube.com/watch?v={yt_search[0]['id']}"
        else:
            yt_link = '🎇 no youtube link, sorry 🎇'

        # template the post
        post_template = '''\
#np #nowplaying {artist} - {track}

{yt_link} - https://music.fontkeming.fail/mpd (live)
'''

        post = post_template.format(artist=currently_playing.get_artist().get_name(properly_capitalized=True),
                                    track=currently_playing.get_title(), yt_link=yt_link)

        # do the thing
        self.mastodon.status_post(post, visibility='unlisted')
[np]
class = nowplaying.NowplayingBot
domain = botsin.space
client_id = 5cd455c225935b1fa0585cc48e81a2985c41a0517330a3e39d30aa5a437e930e
client_secret = 7c645a9444d55f11f1f6f42bbd2fa7e611207e0045b1cbc789a67280885037cb
access_token = eaeaae60f1feebba5d7fc27c8b43ed0159ab5c06f20b52d4a27eabfa1e3b2bff
lastfm_api_key = da6e1d6f81f6813500c5900de742e06c
lastfm_api_secret = 915f9884d0868f19529ec25bf59d6718
lastfm_username = aes256thetic
lastfm_password_hash = ce92be174edf74689ce89d48b1613267
youtube_key = AIzaSyCJhDRlK5qzisk_dvv-crGe54EOd2bjfoI
use_last_played = no