Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ local listenbrainz = require "listenbrainz"
local cache = require "cache"
local stats = require "stats"
local history = require "history"
local batch = require "batch"

-- === CLI ARGUMENTS ===

Expand All @@ -35,6 +36,8 @@ Options:
--export [file] Export stats to CSV
--history [n] Show recent search history (default: 10)
--clear-history Clear search history
--import <file> Import scrobbles from CSV or JSON file
--create-import Create example import file

Environment Variables:
DEMEL_LOG_LEVEL Set log verbosity (0=SILENT, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG)
Expand Down Expand Up @@ -81,6 +84,18 @@ for i, arg in ipairs(arg) do
elseif arg == "--clear-history" then
history.clear()
os.exit(0)
elseif arg == "--import" then
local filename = arg[i + 1]
if not filename then
print("[ERROR] Please specify a file to import")
os.exit(1)
end
-- Handle import in special mode (needs modules loaded)
_G.BATCH_IMPORT_FILE = filename
elseif arg == "--create-import" then
batch.create_example_csv()
batch.create_example_json()
os.exit(0)
end
end

Expand All @@ -96,6 +111,56 @@ print("----------------")
if not gemini.check_connection() then os.exit(1) end
if not listenbrainz.check_connection() then os.exit(1) end

-- Handle batch import if requested
if _G.BATCH_IMPORT_FILE then
print("\n\27[1mBatch Import Mode\27[0m")
print("----------------")

local entries = batch.import_file(_G.BATCH_IMPORT_FILE)
if not entries or #entries == 0 then
print("[ERROR] No valid entries found in file")
os.exit(1)
end

print(string.format("[INFO] Found %d entries to import", #entries))
print("[INFO] This will scrobble them to ListenBrainz")
io.write("Continue? (y/N) > ")
local confirm = io.read()

if confirm ~= "y" and confirm ~= "Y" then
print("Cancelled.")
os.exit(0)
end

local success_count = 0
local fail_count = 0

for i, entry in ipairs(entries) do
local artist = entry.artist
local title = entry.title
local album = entry.album or "Unknown Album"
local timestamp = entry.timestamp or os.time()

io.write(string.format("[%d/%d] %s - %s... ", i, #entries, artist, title))

local ok = listenbrainz.submit_listen(artist, title, album, timestamp)
if ok then
stats.record_scrobble(artist, title, album, timestamp)
history.add_entry("batch import", artist, title, album)
print("✓")
success_count = success_count + 1
-- Rate limit: wait 1 second between scrobbles
os.execute("sleep 1")
else
print("✗")
fail_count = fail_count + 1
end
end

print(string.format("\n[INFO] Import complete: %d success, %d failed", success_count, fail_count))
os.exit(0)
end

print("\n\27[32mSystem Ready.\27[0m Type 'exit' or 'quit' to leave.")

-- === HELPERS ===
Expand Down
132 changes: 132 additions & 0 deletions src/batch.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
-- Batch import module for importing multiple scrobbles from file
local cjson = require "cjson"

local M = {}

function M.parse_csv(filename)
local file = io.open(filename, "r")
if not file then
print("[ERROR] Could not open file: " .. filename)
return nil
end

local entries = {}
local line_num = 0

for line in file:lines() do
line_num = line_num + 1

-- Skip header line
if line_num == 1 then
goto continue
end

-- Parse CSV line (simple parsing, doesn't handle quotes with commas)
local fields = {}
for field in line:gmatch("[^,]+") do
table.insert(fields, field:match("^%s*(.-)%s*$")) -- trim whitespace
end

if #fields >= 2 then
table.insert(entries, {
artist = fields[1],
title = fields[2],
album = fields[3] or nil,
timestamp = tonumber(fields[4]) or os.time()
})
end

::continue::
end

file:close()
return entries
end

function M.parse_json(filename)
local file = io.open(filename, "r")
if not file then
print("[ERROR] Could not open file: " .. filename)
return nil
end

local content = file:read("*a")
file:close()

local status, data = pcall(cjson.decode, content)
if not status then
print("[ERROR] Invalid JSON format")
return nil
end

-- Expected format: array of {artist, title, album?, timestamp?}
return data
end

function M.import_file(filename, format)
-- Auto-detect format if not specified
if not format then
if filename:match("%.json$") then
format = "json"
elseif filename:match("%.csv$") then
format = "csv"
else
print("[ERROR] Unknown file format. Use .csv or .json")
return nil
end
end

local entries
if format == "csv" then
entries = M.parse_csv(filename)
elseif format == "json" then
entries = M.parse_json(filename)
else
print("[ERROR] Unsupported format: " .. format)
return nil
end

return entries
end

function M.create_example_csv(filename)
filename = filename or "import_example.csv"
local file = io.open(filename, "w")
if not file then
print("[ERROR] Could not create file: " .. filename)
return false
end

file:write("artist,title,album,timestamp\n")
file:write("Pink Floyd,Comfortably Numb,The Wall,\n")
file:write("Led Zeppelin,Stairway to Heaven,Led Zeppelin IV,\n")
file:write("Black Sabbath,Iron Man,Paranoid,\n")

file:close()
print("[SUCCESS] Created example file: " .. filename)
print("[INFO] Edit this file and use: demel --import " .. filename)
return true
end

function M.create_example_json(filename)
filename = filename or "import_example.json"
local file = io.open(filename, "w")
if not file then
print("[ERROR] Could not create file: " .. filename)
return false
end

local example = {
{artist = "Pink Floyd", title = "Comfortably Numb", album = "The Wall"},
{artist = "Led Zeppelin", title = "Stairway to Heaven", album = "Led Zeppelin IV"},
{artist = "Black Sabbath", title = "Iron Man", album = "Paranoid"}
}

file:write(cjson.encode(example))
file:close()
print("[SUCCESS] Created example file: " .. filename)
print("[INFO] Edit this file and use: demel --import " .. filename)
return true
end

return M
Loading