degesch: add a last-fm "now playing" plugin
This commit is contained in:
parent
32f719dec7
commit
19b09a8cec
6
NEWS
6
NEWS
|
@ -6,7 +6,11 @@
|
|||
|
||||
* degesch: resolve remote addresses asynchronously
|
||||
|
||||
* degesch: various bugfixes
|
||||
* degesch: Lua API was improved and extended
|
||||
|
||||
* degesch: added a basic last.fm "now playing" plugin
|
||||
|
||||
* Various bugfixes
|
||||
|
||||
|
||||
0.9.2 (2015-12-31)
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue