functional base, unused encryption for pw

This commit is contained in:
jrosh 2024-10-16 21:56:30 +02:00
commit a00e041add
5 changed files with 857 additions and 0 deletions

265
extensions/vlc_subsonic.lua Normal file
View file

@ -0,0 +1,265 @@
---@diagnostic disable: need-check-nil
function descriptor()
return {
title = "VLC Subsonic client",
version = "1.0",
author = "jrosh",
shortdesc = "VLC Subsonic client",
description = "Play music from your subsonic server with VLC as client",
}
end
function activate()
if (read_config_file()) then
open_playlists_dialog()
else
open_login_dialog()
end
end
function deactivate()
dlg:delete()
end
function open_login_dialog()
dlg = vlc.dialog(descriptor().title)
dlg:add_label("<center><h3>Enter your server details</h3></center>", 1,1,1,1)
local host_w = dlg:add_text_input("", 1,2,1,1)
local user_w = dlg:add_text_input("", 1,3,1,1)
local pass_w = dlg:add_text_input("", 1,4,1,1)
local status_label = dlg:add_label("", 1,7,1,1)
local test_button dlg:add_button("Test connection", function()
local host = host_w:get_text()
local user = user_w:get_text()
local pass = pass_w:get_text()
status_label:set_text("")
local result = test_connection(host, user, pass)
status_label:set_text(result)
end, 1,5,1,1)
local save_button dlg:add_button("Save config and login", function()
local host = host_w:get_text()
local user = user_w:get_text()
local pass = pass_w:get_text()
--local encryption_key = generateRandomAESKey()
--local encrypted_pass = encrypt_password(pass, encryption_key)
--write_config_file(host, user, encrypted_pass, encryption_key)
write_config_file(host, user, pass)
status_label:set_text("")
status_label:set_text("Config saved, please reload the extension")
dlg:del_widget(host_w, user_w, pass_w, test_button, save_button)
open_playlists_dialog()
end, 1,6,1,1)
dlg:show()
end
function test_connection(hostname, username, password)
local url_s = generate_salt(6)
local url_t = calculate_md5(password .. url_s)
local url = hostname.."/rest/ping?&u="..username.."&t="..url_t.."&s="..url_s.."&v=1.16.1&c=vlc"
local stream = vlc.stream(url)
if not stream then
vlc.msg.err("Failed to open stream for URL: " .. url)
return nil
end
local chunk_data = ""
local testing_chunk = stream:read(1024)
if testing_chunk then
chunk_data = (chunk_data .. testing_chunk:gsub("\n", ""))
else
vlc.msg.err("Failed to read data from stream")
end
local response = chunk_data:match('status="([^"]+)"')
return response
end
function open_playlists_dialog()
if not dlg then
dlg = vlc.dialog(descriptor().title)
end
config = read_config_file() -- keep global at this position
--password = decrypt_password(config['password'], os.getenv("VLC_SUBSONIC_EXTENSION"))
local url_s = generate_salt(6)
local url_t = calculate_md5(config["password"] .. url_s)
--local url = string.format("%s/rest/getPlaylists?&u=%s&t=%s&s=%s&v=1.16.1&c=vlc", config['hostname'], config['username'], url_t, url_s )
local url = config['hostname'] .. "/rest/getPlaylists?&u=" .. config['username'] .. "&t=" .. url_t .. "&s="..url_s.."&v=1.16.1&c=vlc"
local playlist_table = fetch_xml_data(url)
local playlists = {}
local names = {}
local ids = {}
-- occurrences of name="value" and 'playlist id="value"'
playlist_table:gsub('name="([^"]*)"', function(name)
table.insert(names, name)
end)
playlist_table:gsub('playlist%s+id="([^"]*)"', function(playlist_id)
table.insert(ids, playlist_id)
end)
-- combining names and ids into the playlists table
for i = 1, math.max(#names, #ids) do
local name = names[i] or "Unnamed"
local playlist_id = ids[i] or "No ID"
table.insert(playlists, {name = name, playlist_id = playlist_id})
end
-- display playlists in the grid
local col = 0
local row = 0
for index, playlist in ipairs(playlists) do
row = ( index % 10 ) + 2
col = math.ceil(index / 10)
dlg:add_button(playlist.name, function() add_to_local_playlist(playlist.playlist_id) end, col, row)
end
dlg:add_label("<center><h3>Playlists</h3></center>", 1,1, col)
dlg:show()
end
function add_to_local_playlist(playlist_id)
local url_s = generate_salt(6)
local url_t = calculate_md5(config['password'] .. url_s)
local playlist_url = config['hostname'] .. "/rest/getPlaylist?id="..playlist_id.."&u=".. config['username'] .."&t="..url_t.."&s="..url_s.."&v=1.16.1&c=vlc"
local songs_table = fetch_xml_data(playlist_url)
vlc.playlist.clear()
for song in string.gmatch(songs_table, "<entry.-</entry>") do
local id = string.match(song, 'id="([^"]-)"')
local title = string.match(song, 'title="([^"]-)"')
local url_s = generate_salt(6)
local url_t = calculate_md5(config['password'] .. url_s)
local song_url = config['hostname'] .. "/rest/stream?id=".. id .."&u=".. config['username'] .."&t="..url_t.."&s="..url_s.."&v=1.16.1&c=vlc"
vlc.playlist.add({{path=song_url, title=title}})
end
--vlc.playlist.goto(0) <- doesn't launch the extension. goto() was added as an internal lua function from 5.2 but VLC sould use 5.1. idk whats going on
end
function fetch_xml_data(url)
local stream = vlc.stream(url)
if not stream then
vlc.msg.err("Failed to open stream for URL: " .. url)
return nil
end
local xml_data = ""
-- Read a initial chunk of data to see if we get a response
local initial_chunk = stream:read(1024) -- Read the first 1024 bytes
if initial_chunk then
xml_data = (xml_data .. initial_chunk:gsub("\n", "")) -- Append the initial chunk
else
vlc.msg.err("Failed to read data from stream")
end
-- Continue reading until EOF
while true do
local chunk = stream:read(1024) -- Read more data in chunks
if not chunk or chunk == "" then
break
end
xml_data = (xml_data .. chunk:gsub("\n", "")) -- Append each chunk to the complete data
end
return xml_data
end
function calculate_md5(str)
local command = string.format("echo -n %s | openssl dgst -md5", str)
local handle = io.popen(command)
local md5hash = handle:read("*a")
handle:close()
return md5hash:match("= (%x+)")
end
function generate_salt(length)
local salt = ""
for i = 1, length do
local char = string.char(math.random() < 0.33 and math.random(48, 57) or math.random() < 0.66 and math.random(65, 90) or math.random(97, 122))
salt = salt .. char
end
return salt
end
function file_exists(filename)
local file = io.open(filename, "rb")
if file then
file:close()
end
return file ~= nil
end
function read_config_file()
local config_file = vlc.config.configdir() .. "/vlc_subsonic.conf"
local config = {}
local file = io.open(config_file, "r")
if not file then
return nil
end
for line in file:lines() do
local key, value = line:match("([^=]+)=([^=]*)")
if key and value then
key = key:match("^%s*(.-)%s*$")
value = value:match("^%s*(.-)%s*$")
config[key] = value
end
end
file:close()
return config
end
function write_config_file(hostname, username, password)
local config_file = vlc.config.configdir() .. "/vlc_subsonic.conf"
local file, err = io.open(config_file, "w")
if file then
io.output(file)
io.write("hostname=" .. hostname .. "\n")
io.write("username=" .. username .. "\n")
io.write("password=" .. password)
file:close()
else
vlc.msg.err("Error: Could not open file for writing. " .. err)
end
end
-- not in use from here
function encrypt_password(password, key)
print(password .. " -> " .. key)
local command = string.format("echo -n '%s' | openssl enc -base64 -aes-256-cbc -e -pass pass:%s -pbkdf2", password, key)
local handle = io.popen(command)
local encryptedPassword = handle:read("*a")
handle:close()
return encryptedPassword:gsub("%s+", "")
end
function decrypt_password(encryptedPassword, key)
print(encryptedPassword .. " -> " .. key)
local command = string.format("echo '%s' | openssl enc -base64 -aes-256-cbc -d -pass pass:%s -pbkdf2", encryptedPassword, key)
local handle = io.popen(command)
local decryptedPassword = handle:read("*a")
handle:close()
return decryptedPassword:gsub("%s+", "")
end
function generateRandomAESKey()
local key = {}
math.randomseed(os.time())
for i = 1, 16 do
local byte = math.random(0, 255)
table.insert(key, string.char(byte))
end
return table.concat(key)
end
function set_or_update_env_var(var_value) --doesnt work (writes new key each time)
local homerc = vlc.config.homedir() .. "/.bashrc"
local file = io.open(homerc, "r")
local rccontent = file:read("*a")
file:close()
local new_env_var = string.format("export VLC_SUBSONIC_EXTENSION=\"%s\"", var_value)
if rccontent:find("^export VLC_SUBSONIC_EXTENSION=") then
rccontent = rccontent:gsub("export VLC_SUBSONIC_EXTENSION=\"[^\"]*\"", new_env_var)
else
rccontent = rccontent .. "\n" .. new_env_var
end
local file = io.open(homerc, "w")
file:write(rccontent)
file:close()
end