const getProductData = require('get-product-name') const u64 = require('u64') class Wishlist { static async new (username) { const instance = new this({ username }) await instance.fetch() return instance } constructor (opts) { this.username = opts.username } async fetch () { try { this.doc = await _CC.usersDb.get(this.username) } catch { throw new Error(_CC.lang('WISHLIST_FETCH_FAIL')) } this.items = this.doc.wishlist } async save () { try { const { rev } = await _CC.usersDb.put(this.doc) this.doc._rev = rev } catch { await this.fetch() throw new Error(_CC.lang('WISHLIST_CONFLICT')) } } async get (id) { const item = this.items.find(item => item.id === id) if (!item) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING')) return item } async itemsVisibleToUser (username) { const addedBySelfAtTop = async (items) => { return [ ...items.filter(item => item.addedBy === this.username), ...items.filter(item => item.addedBy !== this.username) ] } if (this.username === username) { return this.items .filter(item => item.addedBy === username) } return addedBySelfAtTop(this.items) } async add ({ itemUrlOrName, suggest, note, addedBy }) { if (!itemUrlOrName) { throw new Error(_CC.lang('WISHLIST_URL_REQUIRED')) } const item = {} const nonFatalErrors = [] const potentialUrl = itemUrlOrName.split(' ').pop() const url = parseURL(potentialUrl) let productData try { if (url) productData = await getProductData(url, _CC.config.proxyServer) } catch (err) { nonFatalErrors.push(err.toString()) } item.id = u64.encode(new Date().getTime().toString()) item.name = (productData ? productData.name : '') item.price = productData?.price item.image = productData?.image item.addedBy = addedBy item.pledgedBy = (addedBy === this.username || suggest ? undefined : addedBy) item.note = note if (url) item.url = url if (!url) item.name = itemUrlOrName this.items.push(item) await this.save() return { nonFatalErrors } } async remove (id) { const index = this.items.findIndex(item => item.id === id) if (index === -1) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING')) this.items.splice(index, 1) await this.save() } async pledge (id, user) { const item = await this.get(id) item.pledgedBy = user await this.save() } async unpledge (id) { const item = await this.get(id) item.pledgedBy = undefined await this.save() } async move (id, places) { if (places === 0) throw new Error('places should never be 0') const index = this.items.findIndex(item => item.id === id) if (index === -1) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING')) while (this.items[index + places] && this.items[index + places].addedBy !== this.username) { if (places < 0) { places-- } else { places++ } } if (index < 0 || index >= this.items.length || index + places < 0 || index + places >= this.items.length) { throw new Error(_CC.lang('WISHLIST_MOVE_INVALID')) } const item = this.items.splice(index, 1)[0] this.items.splice(index + places, 0, item) await this.save() } async moveTop (id) { const index = this.items.findIndex(item => item.id === id) if (index === -1) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING')) const item = this.items.splice(index, 1)[0] this.items.unshift(item) await this.save() } async setItemData (id, data) { const item = await this.get(id) for (const key of [ 'name', 'note', 'url', 'price', 'image' ]) { if (!Object.prototype.hasOwnProperty.call(data, key)) { throw new Error(_CC.lang('NOTE_MISSING_PROP', key)) } item[key] = data[key] } await this.save() } async refreshItemData (id) { const item = await this.get(id) if (!item.url) { throw new Error(_CC.lang('WISHLIST_REFRESH_NO_URL')) } const productData = await getProductData(item.url) if (!productData) return; // short-circuit when there's no data for (const key of ['name', 'price', 'image']) { if (productData[key]) item[key] = productData[key] } await this.save() } } function parseURL (string) { try { const url = new URL(string) if (_CC.config.wishlist.smile) { if (url.hostname === 'www.amazon.com') url.hostname = 'smile.amazon.com' } if (url) return url } catch {} } module.exports = { Wishlist }