aboutsummaryrefslogtreecommitdiff
path: root/plugins/degesch/last-fm.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/degesch/last-fm.lua')
-rw-r--r--plugins/degesch/last-fm.lua155
1 files changed, 155 insertions, 0 deletions
diff --git a/plugins/degesch/last-fm.lua b/plugins/degesch/last-fm.lua
new file mode 100644
index 0000000..54f5d1c
--- /dev/null
+++ b/plugins/degesch/last-fm.lua
@@ -0,0 +1,155 @@
+--
+-- 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 Janouch <p.janouch@gmail.com>
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted, provided that the above
+-- copyright notice and this permission notice appear in all copies.
+--
+-- 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
+degesch.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)
+ -- There's no reasonable Lua package to parse HTTP that I could find
+ local s, e, v, status, message = string.find (data, "(%S+) (%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["#text"] end
+ return node
+ 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
+ buffer:log ("Not playing anything right now")
+ else
+ local np = "Now playing: \"" .. name .. "\""
+ if artist then np = np .. " by " .. artist end
+ if album then np = np .. " from " .. album end
+ buffer:log (np)
+ end
+end
+
+-- Set up the connection and make the request
+local on_connected = function (buffer, c, host)
+ -- 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)
+ 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
+local make_request = function (buffer)
+ if not user or not api_key then
+ report_error (buffer, "configuration is incomplete")
+ return
+ end
+
+ if running then running.abort () end
+
+ running = degesch.connect ("ws.audioscrobbler.com", 80, {
+ on_success = function (c, host)
+ on_connected (buffer, c, host)
+ running = nil
+ end,
+ on_error = function (e)
+ report_error (buffer, e)
+ running = nil
+ end
+ })
+end
+
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+-- TODO:
+-- /np? to just retrieve the song and print it in the buffer
+-- /np! to execute "/me is listening to " .. last retrieved song
+-- /np to do both in succession
+
+-- Hook input to simulate new commands
+degesch.hook_input (function (hook, buffer, input)
+ if input == "/np" then
+ make_request (buffer)
+ else
+ return input
+ end
+end)