111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
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<Want[]> {
|
|
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<any> {
|
|
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,
|
|
};
|
|
} |