-- Copyright 2025 David Vazgenovich Shakaryan local util = require('util') local catalogue = {} local mt = {} mt.__index = mt function catalogue.new() local t = setmetatable({data = {}}, mt) t:add({ type = 'group', id = 'root', name = '/', }) t:add({ type = 'group', id = 'favourites', parent_id = 'root', name = 'Favourites', lazy = true, -- prevent infinite recursion on search }) return t end function mt:get(id) return self.data[id] end function mt:_ensure_catchall(section_id) local id = section_id .. ':category:catchall' local entry = self.data[id] if entry then return entry end return self:add({ section = section_id, type = 'group', group_type = 'category', id = id, parent_id = section_id .. ':category:0', -- non-ascii symbol to sort near end name = '∗CATCHALL∗', }) end function mt:add(entry) self.data[entry.id] = entry if entry.type == 'group' then entry.children = {} end if not entry.parent_id then return entry end local parent = self.data[entry.parent_id] -- dump any entries referencing nonexistent categories into a single -- catchall category if not parent then parent = self:_ensure_catchall(entry.section) entry.parent_id = parent.id end if parent.id == entry.id then entry.parent_id = nil else parent.children[#parent.children+1] = entry end return entry end function mt:load_xc_section(section) self:add({ section = section.id, type = 'group', group_type = 'category', id = section.id .. ':category:0', parent_id = 'root', name = section.name, }) -- currently, this will not correctly handle subcategories which come -- before their parent category for _, v in ipairs(section.categories) do self:add({ section = section.id, type = 'group', group_type = 'category', id = section.id .. ':category:' .. v.category_id, parent_id = section.id .. ':category:' .. v.parent_id, name = util.strip(v.category_name), }) end for _, v in ipairs(section.elements) do local vv = { section = section.id, parent_id = section.id .. ':category:' .. v.category_id, name = util.strip(v.name), } if section.type == 'series' then vv.type = 'group' vv.group_type = 'series' vv.id = section.id .. ':series:' .. v.series_id vv.series_id = v.series_id vv.img_url = util.strip_ne(v.cover) vv.lazy = true -- avoid API calls on search else vv.type = 'stream' vv.id = section.id .. ':stream:' .. v.stream_id vv.stream_type = v.stream_type vv.stream_id = v.stream_id vv.img_url = util.strip_ne(v.stream_icon) vv.epg_channel_id = util.strip_ne(v.epg_channel_id) end self:add(vv) end end function mt:path_to_root(entry) local path = {} local curr = entry while curr.parent_id and curr.parent_id ~= 'root' do curr = self:get(curr.parent_id) if not curr then return end path[#path+1] = curr end if curr.parent_id ~= 'root' then return end return path end function mt:path_from_root(entry) local path = self:path_to_root(entry) if path then util.reverse(path) end return path end return catalogue