last.fm-to-mastodon.js
· 4.1 KiB · JavaScript
Raw
// 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()
| 1 | // Variables used by Scriptable. |
| 2 | // These must be at the very top of the file. Do not edit. |
| 3 | // icon-color: pink; icon-glyph: headphones-alt; |
| 4 | // Last.fm to Mastodon |
| 5 | // by @zicklepop@nyan.lol |
| 6 | // |
| 7 | // Set a profile field to your currently playing track |
| 8 | // |
| 9 | // Requirements: |
| 10 | // - API Key from Last.fm: https://www.last.fm/api |
| 11 | // - Token from Mastodon with permission scopes for: |
| 12 | // `read:account` and `write:account` |
| 13 | // - Find it on your Mastodon instance website under |
| 14 | // Settings -> Development -> New Application |
| 15 | |
| 16 | // Script Constants |
| 17 | // - Ignore, just defining before use |
| 18 | // Continue down to config |
| 19 | |
| 20 | const LASTFM_URL_IN_VALUE = 'LASTFM_URL_IN_VALUE' |
| 21 | const LASTFM_IN_NAME = 'LASTFM_IN_NAME' |
| 22 | |
| 23 | // Config |
| 24 | // - Put in your details below |
| 25 | // - Find Field By: |
| 26 | // - When set to LASTFM_URL_IN_VALUE the script will |
| 27 | // look for a last.fm url in a field value and set |
| 28 | // the title of the field to the current track |
| 29 | // - When set to LASTFM_IN_NAME the script will look |
| 30 | // for a field title that is `last.fm` (case-insenstive) |
| 31 | // and set the value to the current track |
| 32 | |
| 33 | const LASTFM_USERNAME = '' |
| 34 | const LASTFM_API_KEY = '' |
| 35 | const MASTODON_INSTANCE = '' // ie: social.lol |
| 36 | const MASTODON_TOKEN = '' |
| 37 | const FIND_FIELD_BY = LASTFM_URL_IN_VALUE // or LASTFM_IN_NAME |
| 38 | |
| 39 | // More Script Constants |
| 40 | // - You can ignore this too |
| 41 | |
| 42 | const LASTFM_API = `http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${LASTFM_USERNAME}&api_key=${LASTFM_API_KEY}&format=json&limit=1` |
| 43 | const MASTODON_GET_ACCOUNT_API = `https://${MASTODON_INSTANCE}/api/v1/accounts/verify_credentials` |
| 44 | const MASTODON_UPDATE_FIELDS_API = `https://${MASTODON_INSTANCE}/api/v1/accounts/update_credentials` |
| 45 | const MASTODON_HEADERS = { |
| 46 | 'Content-Type': 'application/json', |
| 47 | Authorization: `Bearer ${MASTODON_TOKEN}`, |
| 48 | } |
| 49 | |
| 50 | // Last FM Related Functions |
| 51 | |
| 52 | function formatTrackString(track) { |
| 53 | const { artist, album, name } = track |
| 54 | const albumName = album['#text'] |
| 55 | const artistName = artist['#text'] |
| 56 | |
| 57 | // If you want to change the format of the track |
| 58 | // value, you would do it here. |
| 59 | return `${artistName} - ${albumName} - ${name}` |
| 60 | } |
| 61 | |
| 62 | async function getLatestTrack() { |
| 63 | const res = new Request(LASTFM_API) |
| 64 | const data = await res.loadJSON() |
| 65 | const track = data.recenttracks.track[0] |
| 66 | return formatTrackString(track) |
| 67 | } |
| 68 | |
| 69 | // Mastodon Related Functions |
| 70 | |
| 71 | function findProfileField(el, i) { |
| 72 | const { name, value } = el |
| 73 | if (FIND_FIELD_BY === LASTFM_URL_IN_VALUE) { |
| 74 | return value.toLowerCase().indexOf('last.fm/user/') >= 0 |
| 75 | } |
| 76 | if (FIND_FIELD_BY === LASTFM_IN_NAME) { |
| 77 | return name.toLowerCase().indexOf('last.fm') >= 0 |
| 78 | } |
| 79 | return false |
| 80 | } |
| 81 | |
| 82 | function buildFieldParams(fields) { |
| 83 | return fields.reduce((out, { name, value }, index) => { |
| 84 | const prefix = index === 0 ? '?' : '&' |
| 85 | return `${out}${prefix}fields_attributes[${index}][name]=${encodeURIComponent( |
| 86 | name |
| 87 | )}&fields_attributes[${index}][value]=${encodeURIComponent(value)}` |
| 88 | }, '') |
| 89 | } |
| 90 | |
| 91 | async function getFields() { |
| 92 | const res = new Request(MASTODON_GET_ACCOUNT_API) |
| 93 | res.headers = MASTODON_HEADERS |
| 94 | const data = await res.loadJSON() |
| 95 | const { source } = data |
| 96 | const { fields } = source |
| 97 | const targetField = fields.find(findProfileField) |
| 98 | const index = fields.indexOf(targetField) |
| 99 | return { index, fields } |
| 100 | } |
| 101 | |
| 102 | async function updateMastodonField() { |
| 103 | const latestTrack = await getLatestTrack() |
| 104 | const { fields, index } = await getFields() |
| 105 | let hasChange = false |
| 106 | |
| 107 | if (FIND_FIELD_BY === LASTFM_URL_IN_VALUE) { |
| 108 | if (fields[index].name !== latestTrack) { |
| 109 | hasChange = true |
| 110 | fields[index].name = latestTrack |
| 111 | } |
| 112 | } |
| 113 | if (FIND_FIELD_BY === LASTFM_IN_NAME) { |
| 114 | if (fields[index].value !== latestTrack) { |
| 115 | hasChange = true |
| 116 | fields[index].value = latestTrack |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | if (hasChange) { |
| 121 | const apiUrl = `${MASTODON_UPDATE_FIELDS_API}${buildFieldParams(fields)}` |
| 122 | const res = new Request(apiUrl) |
| 123 | res.headers = MASTODON_HEADERS |
| 124 | res.method = 'PATCH' |
| 125 | const data = await res.loadJSON() |
| 126 | |
| 127 | if (data.error) { |
| 128 | console.log(`Error: ${data.error}`) |
| 129 | } else { |
| 130 | console.log('Updated') |
| 131 | } |
| 132 | } else { |
| 133 | console.log('No change') |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | await updateMastodonField() |
| 138 |