|
import * as fs from 'fs'; |
|
import { parse } from 'csv-parse/sync'; |
|
|
|
interface SofaItem { |
|
id: string; |
|
list_name: string; |
|
type: string; |
|
api_id: string; |
|
api_source: string; |
|
external_link: string; |
|
image_url: string; |
|
title: string; |
|
item_description: string; |
|
date_added: string; |
|
item_status: string; |
|
} |
|
|
|
function formatListyDate(isoString: string | undefined): string { |
|
if (!isoString) return ''; |
|
const date = new Date(isoString); |
|
if (isNaN(date.getTime())) return ''; |
|
|
|
const pad = (n: number) => n.toString().padStart(2, '0'); |
|
return `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()}, ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; |
|
} |
|
|
|
function deriveUrl(item: SofaItem): string { |
|
if (item.external_link) return item.external_link; |
|
if (item.api_source === 'tmdb' && item.api_id) { |
|
return `https://www.themoviedb.org/${item.type === 'tv' ? 'tv' : 'movie'}/${item.api_id}`; |
|
} |
|
return ''; |
|
} |
|
|
|
function convertSofaToListy(csvFilePath: string, jsonFilePath: string) { |
|
console.log(`Reading CSV from ${csvFilePath}...`); |
|
|
|
// Read and parse the CSV |
|
const csvData = fs.readFileSync(csvFilePath, 'utf-8'); |
|
|
|
const records: SofaItem[] = parse(csvData, { |
|
columns: true, |
|
skip_empty_lines: true, |
|
relax_quotes: true |
|
}); |
|
|
|
// Group items by their list_name |
|
const listsMap = new Map<string, SofaItem[]>(); |
|
|
|
for (const record of records) { |
|
const listName = record.list_name || 'Uncategorized'; |
|
if (!listsMap.has(listName)) { |
|
listsMap.set(listName, []); |
|
} |
|
listsMap.get(listName)!.push(record); |
|
} |
|
|
|
const currentDateFormatted = formatListyDate(new Date().toISOString()); |
|
let customOrderIndex = 0; |
|
|
|
// Map to Listy's structure |
|
const listyLists = Array.from(listsMap.entries()).map(([listName, items]) => { |
|
const listyItems = items.map(item => { |
|
const attributes = [ |
|
{ key: "RATING", value: "0.0" } // Defaulting to 0.0 |
|
]; |
|
|
|
if (item.image_url) { |
|
attributes.push({ key: "COVER", value: item.image_url }); |
|
} |
|
|
|
if (item.item_description) { |
|
// Remove extra whitespace/newlines from descriptions |
|
attributes.push({ key: "DESCRIPTION", value: item.item_description.trim() }); |
|
} |
|
|
|
const url = deriveUrl(item); |
|
|
|
if (url) { |
|
try { |
|
const urlObj = new URL(url); |
|
|
|
attributes.push({ |
|
key: "FAVICON", |
|
value: `https://www.google.com/s2/favicons?sz=32&domain=${urlObj.hostname}` |
|
}); |
|
} catch (e) { |
|
// Fail silently on bad URLs |
|
} |
|
} |
|
|
|
return { |
|
type: "Generic", // Listy sets individual items to 'Generic' |
|
url: url, |
|
title: item.title, |
|
attributes: attributes, |
|
marked: item.item_status === 'completed', |
|
dateAdded: formatListyDate(item.date_added) || currentDateFormatted |
|
}; |
|
}); |
|
|
|
// Determine list creation date from the oldest item in that list |
|
const listCreateDate = items[0]?.date_added ? formatListyDate(items[0].date_added) : currentDateFormatted; |
|
customOrderIndex++; |
|
|
|
return { |
|
title: listName, |
|
type: "Movies", |
|
items: listyItems, |
|
update: currentDateFormatted, |
|
customOrderIndex: customOrderIndex, |
|
icon: "film", |
|
create: listCreateDate, |
|
needFetchAfterImport: false |
|
}; |
|
}); |
|
|
|
const listyExport = { |
|
appVersion: "1.7.2", |
|
engine_version: 26, |
|
lists: listyLists, |
|
appBuild: "20260330", |
|
appPlatform: "MacOS", |
|
appName: "Listy", |
|
appWebSite: "https://listy.is", |
|
date_export: currentDateFormatted |
|
}; |
|
|
|
|
|
fs.writeFileSync(jsonFilePath, JSON.stringify(listyExport, null, 2)); |
|
|
|
console.log(`✅ Successfully converted ${records.length} items across ${listyLists.length} lists.`); |
|
console.log(`Saved output to ${jsonFilePath}`); |
|
} |
|
|
|
convertSofaToListy('items.csv', 'ItemsToImport.listy'); |