rsshub
Version:
Make RSS Great Again!
241 lines (239 loc) • 7.46 kB
JavaScript
import { n as init_esm_shims, t as __dirname } from "./esm-shims-CzJ_djXG.mjs";
import { t as logger_default } from "./logger-Czu8UMNd.mjs";
import { t as ofetch_default } from "./ofetch-BIyrKU3Y.mjs";
import { t as parseDate } from "./parse-date-BrP7mxXf.mjs";
import { t as art } from "./render-BQo6B4tL.mjs";
import { Buffer } from "node:buffer";
import path from "node:path";
import crypto from "node:crypto";
//#region lib/routes/mercari/util.ts
init_esm_shims();
const rootURL = "https://api.mercari.jp/";
const rootProductURL = "https://jp.mercari.com/item/";
const rootShopProductURL = "https://jp.mercari.com/shops/product/";
const searchURL = `${rootURL}v2/entities:search`;
const itemInfoURL = `${rootURL}items/get`;
const shopItemInfoURL = `${rootURL}v1/marketplaces/shops/products/`;
const uuidv4 = () => crypto.randomUUID();
const MercariStatus = {
default: "",
onsale: "STATUS_ON_SALE",
soldout: "STATUS_SOLD_OUT"
};
const MercariSort = {
default: "SORT_DEFAULT",
create_time: "SORT_CREATED_TIME",
like: "SORT_NUM_LIKES",
score: "SORT_SCORE",
price: "SORT_PRICE"
};
const MercariOrder = {
desc: "ORDER_DESC",
asc: "ORDER_ASC"
};
function bytesToBase64URL(b) {
return b.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
}
function strToBase64URL(s) {
return bytesToBase64URL(Buffer.from(s, "utf-8"));
}
function publicKeyToJWK(publicKey) {
const jwk = publicKey.export({ format: "jwk" });
return {
crv: "P-256",
kty: "EC",
x: jwk.x,
y: jwk.y
};
}
function publicKeyToHeader(publicKey) {
return {
typ: "dpop+jwt",
alg: "ES256",
jwk: publicKeyToJWK(publicKey)
};
}
function derDecode(der) {
let offset = 0;
if (der[offset++] !== 48) throw new Error("Invalid DER signature");
const lenInfo = readDerLength(der, offset);
offset += lenInfo.bytesRead;
if (der[offset++] !== 2) throw new Error("Expected INTEGER for R");
const rLen = readDerLength(der, offset);
offset += rLen.bytesRead;
const r = der.subarray(offset, offset + rLen.length);
offset += rLen.length;
if (der[offset++] !== 2) throw new Error("Expected INTEGER for S");
const sLen = readDerLength(der, offset);
offset += sLen.bytesRead;
const s = der.subarray(offset, offset + sLen.length);
offset += sLen.length;
if (offset !== der.length) throw new Error("Extra bytes in DER signature");
return {
r: fixBufferLength(r, 32),
s: fixBufferLength(s, 32)
};
}
function readDerLength(buf, offset) {
const byte = buf[offset];
if (byte < 128) return {
length: byte,
bytesRead: 1
};
const bytesCount = byte & 127;
if (bytesCount > 4) throw new Error("DER length too long");
let length = 0;
for (let i = 0; i < bytesCount; i++) length = length << 8 | buf[offset + 1 + i];
return {
length,
bytesRead: 1 + bytesCount
};
}
function fixBufferLength(buffer, length) {
if (buffer.length > length) {
const start = buffer.length - length;
return buffer.subarray(start);
}
if (buffer.length < length) return Buffer.concat([Uint8Array.from(Buffer.alloc(length - buffer.length)), Uint8Array.from(buffer)]);
return buffer;
}
function generateDPOP({ uuid, method, url }) {
const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
const payload = {
iat: Math.floor(Date.now() / 1e3),
jti: uuid,
htu: url,
htm: method.toUpperCase()
};
const header = publicKeyToHeader(publicKey);
const signingInput = `${strToBase64URL(JSON.stringify(header))}.${strToBase64URL(JSON.stringify(payload))}`;
const sign = crypto.createSign("SHA256");
sign.update(signingInput);
const { r, s } = derDecode(sign.sign(privateKey));
return `${signingInput}.${bytesToBase64URL(Buffer.concat([Uint8Array.from(r), Uint8Array.from(s)]))}`;
}
const fetchFromMercari = async (url, data, method = "POST") => {
const DPOP = generateDPOP({
uuid: uuidv4(),
method,
url
});
const options = {
method,
headers: new Headers({
DPOP,
"X-Platform": "web",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/json; charset=utf-8"
}),
body: method === "POST" ? JSON.stringify(data) : void 0,
query: method === "GET" ? data : void 0
};
try {
return await ofetch_default(url, options);
} catch (error) {
throw new Error(`API request failed: ${error}`);
}
};
const pageToPageToken = (page) => {
if (page === 0) return "";
return `v1:${page}`;
};
const fetchSearchItems = async (sort, order, status, keyword, options = {}) => {
const data = {
userId: `MERCARI_BOT_${uuidv4()}`,
pageSize: 120,
pageToken: pageToPageToken(0),
searchSessionId: uuidv4(),
indexRouting: "INDEX_ROUTING_UNSPECIFIED",
thumbnailTypes: [],
searchCondition: {
keyword,
excludeKeyword: options.excludeKeyword || "",
sort,
order,
status: status || [],
sizeId: [],
categoryId: options.categoryId || [],
brandId: options.brandId || [],
sellerId: [],
priceMin: options.priceMin || 0,
priceMax: options.priceMax || 0,
itemConditionId: options.itemConditionId || [],
shippingPayerId: [],
shippingFromArea: [],
shippingMethod: [],
colorId: [],
hasCoupon: false,
attributes: options.attributes || [],
itemTypes: options.itemTypes || [],
skuIds: [],
shopIds: [],
excludeShippingMethodIds: []
},
serviceFrom: "suruga",
withItemBrand: true,
withItemSize: false,
withItemPromotions: true,
withItemSizes: true,
withShopname: false,
useDynamicAttribute: true,
withSuggestedItems: true,
withOfferPricePromotion: true,
withProductSuggest: true,
withParentProducts: false,
withProductArticles: true,
withSearchConditionId: true,
withAuction: true
};
logger_default.debug(JSON.stringify(data));
return await fetchFromMercari(searchURL, data, "POST");
};
const fetchItemDetail = (item_id, item_type, country_code) => {
if (item_type === "ITEM_TYPE_BEYOND") return fetchFromMercari(shopItemInfoURL + item_id, {
view: "FULL",
imageType: "JPEG"
}, "GET");
return fetchFromMercari(itemInfoURL, {
id: item_id,
country_code,
include_item_attributes: true,
include_product_page_component: true,
include_non_ui_item_attributes: true,
include_donation: true,
include_offer_like_coupon_display: true,
include_offer_coupon_display: true,
include_item_attributes_sections: true,
include_auction: false
}, "GET");
};
const formatItemDetail = (detail) => {
if (detail.displayName) {
const shopItemDetail = detail;
return {
title: shopItemDetail.displayName,
description: art(path.join(__dirname, "templates/shopItem-d63f5360.art"), shopItemDetail),
pubDate: parseDate(shopItemDetail.createTime),
guid: shopItemDetail.name,
link: `${rootShopProductURL}${shopItemDetail.name}`,
image: shopItemDetail.thumbnail,
language: "ja",
author: shopItemDetail.productDetail.shop.displayName,
updated: parseDate(shopItemDetail.updateTime)
};
}
const itemDetail = detail;
return {
title: itemDetail.data.name,
description: art(path.join(__dirname, "templates/item-fb30dd01.art"), itemDetail),
pubDate: parseDate(itemDetail.data.created * 1e3),
guid: itemDetail.data.id,
link: `${rootProductURL}${itemDetail.data.id}`,
image: itemDetail.data.thumbnails[0],
language: "ja",
author: itemDetail.data.seller.name,
updated: parseDate(itemDetail.data.updated * 1e3)
};
};
//#endregion
export { fetchSearchItems as a, fetchItemDetail as i, MercariSort as n, formatItemDetail as o, MercariStatus as r, MercariOrder as t };