we're so back

This commit is contained in:
Nathan Rashleigh committed 2024-09-08 23:16:47 +10:00
1 parent 986b61962a
commit b340fc7b61
9 files changed
+3251 -146

No files matched your search

+1
View File
@@ -0,0 +1 @@
node_modules/
+10 -1
View File
@@ -1,4 +1,13 @@
FROM node:16
FROM node:20
WORKDIR /opt/crates/api
COPY . ./
# puppeteer deps
RUN apt-get update
RUN apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils
RUN npm i
# not sure why doesn't do it itself
RUN node node_modules/puppeteer/install.mjs
CMD ["npm", "start"]
+1 -1
View File
@@ -28,7 +28,7 @@ async function start() {
api,
events,
notify,
} = createServices(log);
} = await createServices(log);
await notify.listen();
await discord.connect();
+3186 -91
View File
File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -34,6 +34,7 @@
"bunyan": "^1.8.15",
"cheerio": "^1.0.0-rc.12",
"discord.js": "^14.7.1",
"esrun": "^3.2.26",
"koa": "^2.13.4",
"koa-body": "^5.0.0",
"koa-passport": "^5.0.0",
@@ -42,7 +43,10 @@
"koa-ws": "^1.0.1",
"lodash": "^4.17.21",
"passport-local": "^1.0.0",
"pg-listen": "^1.7.0"
"pg-listen": "^1.7.0",
"puppeteer": "^23.3.0",
"puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-stealth": "^2.11.2"
},
"overrides": {
"koa-passport": {
+40 -48
View File
@@ -1,33 +1,21 @@
import fs from 'fs/promises';
import axios from 'axios';
import cheerio from 'cheerio';
import Bluebird from 'bluebird';
import pptr from 'puppeteer-extra'
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
import type Log from 'bunyan';
import type { PrismaClient, Want, User, Listing } from '@prisma/client';
import type { Want, User } from '@prisma/client';
import { logAxiosError, wantDetails } from './utils';
import { wantDetails } from './utils';
import { Pg } from './pg';
type ApiResponse = [string | null, any | null];
declare module 'axios' {
export interface AxiosInstance {
request(config: AxiosRequestConfig): Promise<ApiResponse>;
get(url: string, config?: AxiosRequestConfig): Promise<ApiResponse>;
delete(url: string, config?: AxiosRequestConfig): Promise<ApiResponse>;
head(url: string, config?: AxiosRequestConfig): Promise<ApiResponse>;
post(url: string, data?: any, config?: AxiosRequestConfig): Promise<ApiResponse>;
put(url: string, data?: any, config?: AxiosRequestConfig): Promise<ApiResponse>;
patch(url: string, data?: any, config?: AxiosRequestConfig): Promise<ApiResponse>;
}
}
export default function createDiscogs(log: Log, pg: Pg) {
export default async function createDiscogs(log: Log, pg: Pg) {
const TOKEN = process.env.DISCOGS_TOKEN;
const apiBase = 'https://api.discogs.com'
const discogsBase = 'https://www.discogs.com'
pptr.use(StealthPlugin())
const browser = await pptr.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] })
log.info('browser created')
const api = axios.create({
headers: {
@@ -38,22 +26,16 @@ export default function createDiscogs(log: Log, pg: Pg) {
});
function onSuccess(response) {
return [null, response.data];
return response.data;
}
function onError(error) {
if (error.response.status === 429) {
return [new Error('rate limited, try again in 1 minute'), null];
}
if (error.response) return [error.response.data, null];
if (error.request) return ['Network Error', null];
return ['Unknown Error', null];
throw error;
}
api.interceptors.response.use(onSuccess, onError);
async function wantListSync(username: string, user: User): Promise<Want[]> {
const [err, res] = await api.get(`${apiBase}/users/${username}/wants`);
if (err) throw err;
const res = await api.get(`${apiBase}/users/${username}/wants`);
const { wants, pagination } = res;
const releases = await Promise.all(wants.map(want => release(want.id)));
@@ -73,8 +55,7 @@ export default function createDiscogs(log: Log, pg: Pg) {
}
async function release(id: DiscogsRelease): Promise<any> {
const [err, data] = await api.get(`${apiBase}/releases/${id}`);
if (err) throw err;
const data = await api.get(`${apiBase}/releases/${id}`);
return data;
}
@@ -83,29 +64,41 @@ export default function createDiscogs(log: Log, pg: Pg) {
url.searchParams.append('query', query);
url.searchParams.append('type', 'release');
url.searchParams.append('format', 'vinyl');
const [err, res] = await api.get(url.href);
if (err) throw err;
const res = await api.get(url.href);
const { results } = res;
return results;
}
async function inventory(user: string) {
const [err, res] = await api.get(`${apiBase}/users/${user}/inventory`);
if (err) throw err;
const res = await api.get(`${apiBase}/users/${user}/inventory`);
return res.listings;
}
async function listings(want: Want) {
const [err, res] = await api.get(`https://www.discogs.com/sell/release/${want.release}?limit=250`);
if (err) throw err;
const page = await browser.newPage()
const url = `https://www.discogs.com/sell/release/${want.release}?limit=250`
const $ = cheerio.load(res);
const ids = $('.item_description_title[href]')
.map(function getListingId() {
const id = Number.parseInt(this.attribs.href.replace('/sell/item/', ''), 10);
return id;
})
.get();
log.info({ url }, 'fetching')
const res = await page.goto(url)
log.info(res)
----------------
not catching the rate limiting here i thnk
if (res.status() === 429) throw new Error("rate limited")
const els = await page.$$('.item_description_title[href]')
const hrefs = await Promise.all(els.map(e => e.evaluate(e => e.href)))
const ids = hrefs
.map(h => new URL(h))
.map(p => p.pathname.replace('/sell/item/', ''))
.map(p => Number.parseInt(p, 10))
await page.close()
const unlisted = await pg.listingRmUnlisted(want, ids);
if (unlisted) log.info({ unlisted: unlisted, ...wantDetails(want) }, 'missing listings removed');
@@ -133,8 +126,7 @@ export default function createDiscogs(log: Log, pg: Pg) {
if (listing) return listing;
log.debug('fetching listing', id);
const [err, discogsListing] = await api.get(`${apiBase}/marketplace/listings/${id}`);
if (err) throw err;
const discogsListing = await api.get(`${apiBase}/marketplace/listings/${id}`);
log.debug(discogsListing);
return pg.listingUpsert(discogsListing, want);
}
+2 -1
View File
@@ -36,7 +36,8 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers, discord)
await pg.wantSynced(want);
return listings;
} catch (e) {
if (e.message.includes('rate limited')) {
log.error(e)
if (e.status === 429) {
log.warn('rate limited, trying again in 60s');
await setTimeout(60 * 1000);
if (attempts > MAX_ATTEMPTS) {
+2 -2
View File
@@ -9,11 +9,11 @@ import createOffers from './offers';
import createPg from './pg';
import createDiscord from './discord';
export default function createServices(log) {
export default async function createServices(log) {
const prisma = new PrismaClient();
const discord = createDiscord(log);
const pg = createPg(log, prisma);
const discogs = createDiscogs(log, pg);
const discogs = await createDiscogs(log, pg);
const offers = createOffers(log, pg);
const events = createEvents(log, pg, discogs, offers, discord);
const notify = createNotify(log, pg, events);