import fs from 'fs/promises'; import axios from 'axios'; import cheerio from 'cheerio'; import Bluebird from 'bluebird'; import type Log from 'bunyan'; import type { PrismaClient, Want, User, Listing } from '@prisma/client'; import { logAxiosError } from './utils'; import { Pg } from './pg'; // margh // vanderbabe // jmcdermid1 export default function discogs(log: Log, pg: Pg) { const TOKEN = 'Discogs token=wRTXuYZGOPsIQgKRGKCAkUkidGtvpSfQgPWkYIpD'; const apiBase = 'https://api.discogs.com' const discogsBase = 'https://www.discogs.com' const api = axios.create({ headers: { 'Authorization': TOKEN, // discogs only returns the first page without a desktop u/a 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0', }, }); async function handleApiError(e) { logAxiosError(e); if (e.response.status === 429) { throw new Error('rate limited'); } return null; } async function wantListSync(username: string, user: User): Promise { const { data: { wants, pagination }} = await api.get(`${apiBase}/users/${username}/wants`); const releases = await Promise.all(wants.map(want => release(want.id))); // TODO filter releases to be vinyl format only // convert want object to actual release and ignore duplicate errors const results = await Promise.allSettled(releases.map(release => pg.wantCreate(release, user))); // @ts-ignore const additions = results.filter(r => r.status === 'fulfilled').map(r => r.value); return additions; } async function release(id: DiscogsRelease): Promise { const { data } = await api.get(`${apiBase}/releases/${id}`); return data; } async function search(artist: string, title: string) { const url = new URL(`${apiBase}/database/search`); url.searchParams.append('artist', artist); url.searchParams.append('title', title); url.searchParams.append('type', 'release'); url.searchParams.append('format', 'vinyl'); const { data: { results } } = await api.get(url.href); return results; } async function inventory(user: string) { const res = await api.get(`${apiBase}/users/${user}/inventory`); return res.data.listings; } async function listings(want: Want) { const res = await api.get(`https://www.discogs.com/sell/release/${want.release}?limit=250`); const $ = cheerio.load(res.data); const ids = $('.item_description_title[href]') .map(function getListingId() { const id = Number.parseInt(this.attribs.href.replace('/sell/item/', ''), 10); return id; }) .get(); const unlisted = await pg.listingRmUnlisted(want, ids); if (unlisted) log.info({ wantId: want.id, unlisted: unlisted }, 'missing listings removed'); // preferentially take listings from sellers if we hit req limits return Bluebird.mapSeries(ids, async id => listing(id, want)); } async function listing(id, want) { const listing = await pg.listingFind(id); if (listing) return listing; try { log.debug('fetching listing', id); const res = await api.get(`${apiBase}/marketplace/listings/${id}`); log.debug(res.data); return pg.listingUpsert(res.data, want); } catch (e) { return handleApiError(e); } } return { wantListSync, inventory, listings, listing, release, search, }; }