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'; export default function discogs(log: Log, pg: Pg) { const 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 user(discogs: string) { const user = await pg.userFindName(discogs); if (user) return user; log.debug('fetching user', discogs); const { data } = await api.get(`${apiBase}/users/${discogs}`); return pg.userUpsert(data.username, data.username, data); } async function wantlist(user: User): Promise { const stored = await pg.userWants(user); if (stored.length) return stored; if (!user.discogs) { log.error(user, 'attempted to fetch wantlist of non discogs user'); throw new Error('User is not associated with a discogs account'); } return wantlistLatest(user); } async function wantlistLatest(user: User): Promise { const { username } = user.data as { username: string }; const { data: { wants, pagination }} = await api.get(`${apiBase}/users/${username}/wants`); return Bluebird.mapSeries(wants, async r => pg.wantUpsert(r, user)); } async function release(id: DiscogsRelease): Promise { const { data } = await api.get(`${apiBase}/releases/${id}`); return data; } 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'); 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}`); return pg.listingUpsert(res.data, want); } catch (e) { return handleApiError(e); } } return { user, wantlist, wantlistLatest, inventory, listings, listing, release, }; }