// Variables used by Scriptable. // These must be at the very top of the file. Do not edit. // icon-color: pink; icon-glyph: headphones-alt; // Last.fm to Mastodon // by @zicklepop@nyan.lol // // Set a profile field to your currently playing track // // Requirements: // - API Key from Last.fm: https://www.last.fm/api // - Token from Mastodon with permission scopes for: // `read:account` and `write:account` // - Find it on your Mastodon instance website under // Settings -> Development -> New Application // Script Constants // - Ignore, just defining before use // Continue down to config const LASTFM_URL_IN_VALUE = 'LASTFM_URL_IN_VALUE' const LASTFM_IN_NAME = 'LASTFM_IN_NAME' // Config // - Put in your details below // - Find Field By: // - When set to LASTFM_URL_IN_VALUE the script will // look for a last.fm url in a field value and set // the title of the field to the current track // - When set to LASTFM_IN_NAME the script will look // for a field title that is `last.fm` (case-insenstive) // and set the value to the current track const LASTFM_USERNAME = '' const LASTFM_API_KEY = '' const MASTODON_INSTANCE = '' // ie: social.lol const MASTODON_TOKEN = '' const FIND_FIELD_BY = LASTFM_URL_IN_VALUE // or LASTFM_IN_NAME // More Script Constants // - You can ignore this too const LASTFM_API = `http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${LASTFM_USERNAME}&api_key=${LASTFM_API_KEY}&format=json&limit=1` const MASTODON_GET_ACCOUNT_API = `https://${MASTODON_INSTANCE}/api/v1/accounts/verify_credentials` const MASTODON_UPDATE_FIELDS_API = `https://${MASTODON_INSTANCE}/api/v1/accounts/update_credentials` const MASTODON_HEADERS = { 'Content-Type': 'application/json', Authorization: `Bearer ${MASTODON_TOKEN}`, } // Last FM Related Functions function formatTrackString(track) { const { artist, album, name } = track const albumName = album['#text'] const artistName = artist['#text'] // If you want to change the format of the track // value, you would do it here. return `${artistName} - ${albumName} - ${name}` } async function getLatestTrack() { const res = new Request(LASTFM_API) const data = await res.loadJSON() const track = data.recenttracks.track[0] return formatTrackString(track) } // Mastodon Related Functions function findProfileField(el, i) { const { name, value } = el if (FIND_FIELD_BY === LASTFM_URL_IN_VALUE) { return value.toLowerCase().indexOf('last.fm/user/') >= 0 } if (FIND_FIELD_BY === LASTFM_IN_NAME) { return name.toLowerCase().indexOf('last.fm') >= 0 } return false } function buildFieldParams(fields) { return fields.reduce((out, { name, value }, index) => { const prefix = index === 0 ? '?' : '&' return `${out}${prefix}fields_attributes[${index}][name]=${encodeURIComponent( name )}&fields_attributes[${index}][value]=${encodeURIComponent(value)}` }, '') } async function getFields() { const res = new Request(MASTODON_GET_ACCOUNT_API) res.headers = MASTODON_HEADERS const data = await res.loadJSON() const { source } = data const { fields } = source const targetField = fields.find(findProfileField) const index = fields.indexOf(targetField) return { index, fields } } async function updateMastodonField() { const latestTrack = await getLatestTrack() const { fields, index } = await getFields() let hasChange = false if (FIND_FIELD_BY === LASTFM_URL_IN_VALUE) { if (fields[index].name !== latestTrack) { hasChange = true fields[index].name = latestTrack } } if (FIND_FIELD_BY === LASTFM_IN_NAME) { if (fields[index].value !== latestTrack) { hasChange = true fields[index].value = latestTrack } } if (hasChange) { const apiUrl = `${MASTODON_UPDATE_FIELDS_API}${buildFieldParams(fields)}` const res = new Request(apiUrl) res.headers = MASTODON_HEADERS res.method = 'PATCH' const data = await res.loadJSON() if (data.error) { console.log(`Error: ${data.error}`) } else { console.log('Updated') } } else { console.log('No change') } } await updateMastodonField()