we're so back
This commit is contained in:
1 parent
986b61962a
commit
b340fc7b61
9 files changed
+3251
-146
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
+10
-1
@@ -1,4 +1,13 @@
|
|||||||
FROM node:16
|
FROM node:20
|
||||||
WORKDIR /opt/crates/api
|
WORKDIR /opt/crates/api
|
||||||
COPY . ./
|
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"]
|
CMD ["npm", "start"]
|
||||||
+1
-1
@@ -28,7 +28,7 @@ async function start() {
|
|||||||
api,
|
api,
|
||||||
events,
|
events,
|
||||||
notify,
|
notify,
|
||||||
} = createServices(log);
|
} = await createServices(log);
|
||||||
|
|
||||||
await notify.listen();
|
await notify.listen();
|
||||||
await discord.connect();
|
await discord.connect();
|
||||||
|
|||||||
Generated
+3186
-91
File diff suppressed because it is too large.
Load diff
+5
-1
@@ -34,6 +34,7 @@
|
|||||||
"bunyan": "^1.8.15",
|
"bunyan": "^1.8.15",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"discord.js": "^14.7.1",
|
"discord.js": "^14.7.1",
|
||||||
|
"esrun": "^3.2.26",
|
||||||
"koa": "^2.13.4",
|
"koa": "^2.13.4",
|
||||||
"koa-body": "^5.0.0",
|
"koa-body": "^5.0.0",
|
||||||
"koa-passport": "^5.0.0",
|
"koa-passport": "^5.0.0",
|
||||||
@@ -42,7 +43,10 @@
|
|||||||
"koa-ws": "^1.0.1",
|
"koa-ws": "^1.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"passport-local": "^1.0.0",
|
"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": {
|
"overrides": {
|
||||||
"koa-passport": {
|
"koa-passport": {
|
||||||
|
|||||||
+40
-48
@@ -1,33 +1,21 @@
|
|||||||
import fs from 'fs/promises';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import cheerio from 'cheerio';
|
import pptr from 'puppeteer-extra'
|
||||||
import Bluebird from 'bluebird';
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
|
||||||
|
|
||||||
import type Log from 'bunyan';
|
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';
|
import { Pg } from './pg';
|
||||||
|
|
||||||
type ApiResponse = [string | null, any | null];
|
export default async function createDiscogs(log: Log, pg: Pg) {
|
||||||
|
|
||||||
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) {
|
|
||||||
const TOKEN = process.env.DISCOGS_TOKEN;
|
const TOKEN = process.env.DISCOGS_TOKEN;
|
||||||
const apiBase = 'https://api.discogs.com'
|
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({
|
const api = axios.create({
|
||||||
headers: {
|
headers: {
|
||||||
@@ -38,22 +26,16 @@ export default function createDiscogs(log: Log, pg: Pg) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function onSuccess(response) {
|
function onSuccess(response) {
|
||||||
return [null, response.data];
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError(error) {
|
function onError(error) {
|
||||||
if (error.response.status === 429) {
|
throw error;
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
api.interceptors.response.use(onSuccess, onError);
|
api.interceptors.response.use(onSuccess, onError);
|
||||||
|
|
||||||
async function wantListSync(username: string, user: User): Promise<Want[]> {
|
async function wantListSync(username: string, user: User): Promise<Want[]> {
|
||||||
const [err, res] = await api.get(`${apiBase}/users/${username}/wants`);
|
const res = await api.get(`${apiBase}/users/${username}/wants`);
|
||||||
if (err) throw err;
|
|
||||||
const { wants, pagination } = res;
|
const { wants, pagination } = res;
|
||||||
|
|
||||||
const releases = await Promise.all(wants.map(want => release(want.id)));
|
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> {
|
async function release(id: DiscogsRelease): Promise<any> {
|
||||||
const [err, data] = await api.get(`${apiBase}/releases/${id}`);
|
const data = await api.get(`${apiBase}/releases/${id}`);
|
||||||
if (err) throw err;
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,29 +64,41 @@ export default function createDiscogs(log: Log, pg: Pg) {
|
|||||||
url.searchParams.append('query', query);
|
url.searchParams.append('query', query);
|
||||||
url.searchParams.append('type', 'release');
|
url.searchParams.append('type', 'release');
|
||||||
url.searchParams.append('format', 'vinyl');
|
url.searchParams.append('format', 'vinyl');
|
||||||
const [err, res] = await api.get(url.href);
|
const res = await api.get(url.href);
|
||||||
if (err) throw err;
|
|
||||||
const { results } = res;
|
const { results } = res;
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function inventory(user: string) {
|
async function inventory(user: string) {
|
||||||
const [err, res] = await api.get(`${apiBase}/users/${user}/inventory`);
|
const res = await api.get(`${apiBase}/users/${user}/inventory`);
|
||||||
if (err) throw err;
|
|
||||||
return res.listings;
|
return res.listings;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listings(want: Want) {
|
async function listings(want: Want) {
|
||||||
const [err, res] = await api.get(`https://www.discogs.com/sell/release/${want.release}?limit=250`);
|
const page = await browser.newPage()
|
||||||
if (err) throw err;
|
const url = `https://www.discogs.com/sell/release/${want.release}?limit=250`
|
||||||
|
|
||||||
const $ = cheerio.load(res);
|
log.info({ url }, 'fetching')
|
||||||
const ids = $('.item_description_title[href]')
|
|
||||||
.map(function getListingId() {
|
const res = await page.goto(url)
|
||||||
const id = Number.parseInt(this.attribs.href.replace('/sell/item/', ''), 10);
|
|
||||||
return id;
|
log.info(res)
|
||||||
})
|
|
||||||
.get();
|
----------------
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
const unlisted = await pg.listingRmUnlisted(want, ids);
|
||||||
if (unlisted) log.info({ unlisted: unlisted, ...wantDetails(want) }, 'missing listings removed');
|
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;
|
if (listing) return listing;
|
||||||
|
|
||||||
log.debug('fetching listing', id);
|
log.debug('fetching listing', id);
|
||||||
const [err, discogsListing] = await api.get(`${apiBase}/marketplace/listings/${id}`);
|
const discogsListing = await api.get(`${apiBase}/marketplace/listings/${id}`);
|
||||||
if (err) throw err;
|
|
||||||
log.debug(discogsListing);
|
log.debug(discogsListing);
|
||||||
return pg.listingUpsert(discogsListing, want);
|
return pg.listingUpsert(discogsListing, want);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -36,7 +36,8 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers, discord)
|
|||||||
await pg.wantSynced(want);
|
await pg.wantSynced(want);
|
||||||
return listings;
|
return listings;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.includes('rate limited')) {
|
log.error(e)
|
||||||
|
if (e.status === 429) {
|
||||||
log.warn('rate limited, trying again in 60s');
|
log.warn('rate limited, trying again in 60s');
|
||||||
await setTimeout(60 * 1000);
|
await setTimeout(60 * 1000);
|
||||||
if (attempts > MAX_ATTEMPTS) {
|
if (attempts > MAX_ATTEMPTS) {
|
||||||
|
|||||||
+2
-2
@@ -9,11 +9,11 @@ import createOffers from './offers';
|
|||||||
import createPg from './pg';
|
import createPg from './pg';
|
||||||
import createDiscord from './discord';
|
import createDiscord from './discord';
|
||||||
|
|
||||||
export default function createServices(log) {
|
export default async function createServices(log) {
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const discord = createDiscord(log);
|
const discord = createDiscord(log);
|
||||||
const pg = createPg(log, prisma);
|
const pg = createPg(log, prisma);
|
||||||
const discogs = createDiscogs(log, pg);
|
const discogs = await createDiscogs(log, pg);
|
||||||
const offers = createOffers(log, pg);
|
const offers = createOffers(log, pg);
|
||||||
const events = createEvents(log, pg, discogs, offers, discord);
|
const events = createEvents(log, pg, discogs, offers, discord);
|
||||||
const notify = createNotify(log, pg, events);
|
const notify = createNotify(log, pg, events);
|
||||||
|
|||||||
+4
-1
@@ -13,7 +13,10 @@ services:
|
|||||||
- "./db:/var/lib/postgresql/data"
|
- "./db:/var/lib/postgresql/data"
|
||||||
|
|
||||||
api:
|
api:
|
||||||
image: node:16
|
image: node:20
|
||||||
|
build:
|
||||||
|
context: ./api
|
||||||
|
dockerfile: ./api.Dockerfile
|
||||||
working_dir: /opt/mcrates/api
|
working_dir: /opt/mcrates/api
|
||||||
depends_on:
|
depends_on:
|
||||||
- 'pg'
|
- 'pg'
|
||||||
|
|||||||
Reference in new issue
Block a user