summaryrefslogtreecommitdiff
path: root/plugins/xC/last-fm.lua
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-08-06 16:12:15 +0200
committerPřemysl Eric Janouch <p@janouch.name>2021-08-06 16:43:59 +0200
commit50057d5149dda340b3b47aca4096f4a6ec66b9ee (patch)
tree79323d20b17c2c8e32942a1ac9b84d9da3041c6d /plugins/xC/last-fm.lua
parent1f64710e795b0c5434d15813d4f1f568467ca087 (diff)
downloadxK-50057d5149dda340b3b47aca4096f4a6ec66b9ee.tar.gz
xK-50057d5149dda340b3b47aca4096f4a6ec66b9ee.tar.xz
xK-50057d5149dda340b3b47aca4096f4a6ec66b9ee.zip
Come up with sillier names for the binaries
I'm not entirely sure, but it looks like some people might not like jokes about the Holocaust. On a more serious note, the project has become more serious over the 7 or so years of its existence.
Diffstat (limited to 'plugins/xC/last-fm.lua')
-rw-r--r--plugins/xC/last-fm.lua178
1 files changed, 178 insertions, 0 deletions
diff --git a/plugins/xC/last-fm.lua b/plugins/xC/last-fm.lua
new file mode 100644
index 0000000..3bdfed2
--- /dev/null
+++ b/plugins/xC/last-fm.lua
@@ -0,0 +1,178 @@
+--
+-- last-fm.lua: "now playing" feature using the last.fm API
+--
+-- Dependencies: lua-cjson (from luarocks e.g.)
+--
+-- I call this style closure-oriented programming
+--
+-- Copyright (c) 2016, Přemysl Eric Janouch <p@janouch.name>
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+--
+
+local cjson = require "cjson"
+
+-- Setup configuration to load last.fm API credentials from
+local user, api_key
+xC.setup_config {
+ user = {
+ type = "string",
+ comment = "last.fm username",
+ on_change = function (v) user = v end
+ },
+ api_key = {
+ type = "string",
+ comment = "last.fm API key",
+ on_change = function (v) api_key = v end
+ },
+}
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+-- Generic error reporting
+local report_error = function (buffer, error)
+ buffer:log ("last-fm error: " .. error)
+end
+
+-- Process data return by the server and extract the now playing song
+local process = function (buffer, data, action)
+ -- There's no reasonable Lua package to parse HTTP that I could find
+ local s, e, v, status, message = string.find (data, "(%S+) (%S+) .+\r\n")
+ if not s then return "server returned unexpected data" end
+ if status ~= "200" then return status .. " " .. message end
+
+ local s, e = string.find (data, "\r\n\r\n")
+ if not s then return "server returned unexpected data" end
+
+ local parser = cjson.new ()
+ data = parser.decode (string.sub (data, e + 1))
+ if not data.recenttracks or not data.recenttracks.track then
+ return "invalid response" end
+
+ -- Need to make some sense of the XML automatically converted to JSON
+ local text_of = function (node)
+ if type (node) ~= "table" then return node end
+ return node["#text"] ~= "" and node["#text"] or nil
+ end
+
+ local name, artist, album
+ for i, track in ipairs (data.recenttracks.track) do
+ if track["@attr"] and track["@attr"].nowplaying then
+ if track.name then name = text_of (track.name) end
+ if track.artist then artist = text_of (track.artist) end
+ if track.album then album = text_of (track.album) end
+ end
+ end
+
+ if not name then
+ action (false)
+ else
+ local np = "\"" .. name .. "\""
+ if artist then np = np .. " by " .. artist end
+ if album then np = np .. " from " .. album end
+ action (np)
+ end
+end
+
+-- Set up the connection and make the request
+local on_connected = function (buffer, c, host, action)
+ -- Buffer data in the connection object
+ c.data = ""
+ c.on_data = function (data)
+ c.data = c.data .. data
+ end
+
+ -- And process it after we receive everything
+ c.on_eof = function ()
+ error = process (buffer, c.data, action)
+ if error then report_error (buffer, error) end
+ c:close ()
+ end
+ c.on_error = function (e)
+ report_error (buffer, e)
+ end
+
+ -- Make the unencrypted HTTP request
+ local url = "/2.0/?method=user.getrecenttracks&user=" .. user ..
+ "&limit=1&api_key=" .. api_key .. "&format=json"
+ c:send ("GET " .. url .. " HTTP/1.1\r\n")
+ c:send ("User-agent: last-fm.lua\r\n")
+ c:send ("Host: " .. host .. "\r\n")
+ c:send ("Connection: close\r\n")
+ c:send ("\r\n")
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+-- Avoid establishing more than one connection at a time
+local running
+
+-- Initiate a connection to last.fm servers
+async, await = xC.async, coroutine.yield
+local make_request = function (buffer, action)
+ if not user or not api_key then
+ report_error (buffer, "configuration is incomplete")
+ return
+ end
+
+ if running then running:cancel () end
+ running = async.go (function ()
+ local c, host, e = await (async.dial ("ws.audioscrobbler.com", 80))
+ if e then
+ report_error (buffer, e)
+ else
+ on_connected (buffer, c, host, action)
+ end
+ running = nil
+ end)
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+local now_playing
+
+local tell_song = function (buffer)
+ if now_playing == nil then
+ buffer:log ("last-fm: I don't know what you're listening to")
+ elseif not now_playing then
+ buffer:log ("last-fm: not playing anything right now")
+ else
+ buffer:log ("last-fm: now playing: " .. now_playing)
+ end
+end
+
+local send_song = function (buffer)
+ if not now_playing then
+ tell_song (buffer)
+ else
+ buffer:execute ("/me is listening to " .. now_playing)
+ end
+end
+
+-- Hook input to simulate new commands
+xC.hook_input (function (hook, buffer, input)
+ if input == "/np" then
+ make_request (buffer, function (np)
+ now_playing = np
+ send_song (buffer)
+ end)
+ elseif input == "/np?" then
+ make_request (buffer, function (np)
+ now_playing = np
+ tell_song (buffer)
+ end)
+ elseif input == "/np!" then
+ send_song (buffer)
+ else
+ return input
+ end
+end)