const prefix = '!!!TEST!!!/' async function getCollection(path) { const collections = Zotero.Collections.getByLibrary(Zotero.Libraries.userLibraryID, true) let parentKey = false let coll for (const name of path.split('/').map(p => p.trim()).filter(p => p)) { coll = collections.find(c => c.name === name && c.parentKey === parentKey) if (!coll) { coll = new Zotero.Collection({ libraryID: Zotero.Libraries.userLibraryID, name }) if (parentKey) coll.parentKey = parentKey await coll.saveTx() } parentKey = coll.key } return coll } async function getTagItems(libraryID, tagID) { var sql = "SELECT itemID FROM itemTags JOIN items USING (itemID) " + "WHERE tagID=? AND libraryID=? AND items.itemID NOT IN (SELECT itemID FROM deletedItems)" return Zotero.DB.columnQueryAsync(sql, [tagID, libraryID]) } async function convert() { const now = Date.now() let added = 0 let tags = await Zotero.Tags.getAll(Zotero.Libraries.userLibraryID); tags = tags.map(tag => tag.tag || tag || '').filter(tag => tag.includes('/')).sort() for (let tag of tags) { let itemIDs = await getTagItems(Zotero.Libraries.userLibraryID, Zotero.Tags.getID(tag)) if (!itemIDs.length) continue const coll = await getCollection(prefix + tag) await Zotero.DB.executeTransaction(async function () { await coll.addItems(itemIDs) }) added += itemIDs.length } return `${new Date} ${Date.now() - now}ms: ${added} assigned` } return await convert()