@@ -2,6 +2,12 @@ import { PROMO_TYPES, SOURCE_IDS } from "../constants.js";
22import { buildSteamUrlFromStableId , createHash , decodeHtmlEntities , fetchJsonWithTimeout , normalizeWhitespace , stripHtml } from "../utils.js" ;
33
44const SEARCH_RESULTS_URL = "https://store.steampowered.com/search/results/?query&start=0&count=100&dynamic_data=&sort_by=_ASC&specials=1&maxprice=free&supportedlang=english&ndl=1&infinite=1" ;
5+ const FREE_PRICE_SEARCH_PAGE_SIZE = 100 ;
6+ const FREE_PRICE_SEARCH_PAGE_COUNT = 4 ;
7+
8+ function buildFreePriceSearchUrl ( start ) {
9+ return `https://store.steampowered.com/search/results/?query&start=${ start } &count=${ FREE_PRICE_SEARCH_PAGE_SIZE } &dynamic_data=&sort_by=Price_ASC&maxprice=free&category1=998&supportedlang=english&ndl=1&infinite=1` ;
10+ }
511
612function isDiscountedToZero ( priceText , row ) {
713 const finalPriceMatch = / d a t a - p r i c e - f i n a l = " ( \d + ) " / i. exec ( row ) ;
@@ -17,7 +23,9 @@ function isDiscountedToZero(priceText, row) {
1723 return hasZeroFinalPrice && ( hasDiscountedOriginalPrice || hasFullDiscount ) ;
1824}
1925
20- export function parseSearchRows ( html ) {
26+ export function parseSearchRows ( html , options = { } ) {
27+ const allowFreeLabel = options . allowFreeLabel !== false ;
28+ const rawTypeLabel = options . rawTypeLabel || "Store special" ;
2129 const rows = String ( html || "" ) . match ( / < a \b [ \s \S ] * ?c l a s s = " [ ^ " ] * s e a r c h _ r e s u l t _ r o w [ ^ " ] * " [ \s \S ] * ?< \/ a > / gi) || [ ] ;
2230 const promotions = [ ] ;
2331
@@ -29,8 +37,9 @@ export function parseSearchRows(html) {
2937 const title = titleMatch ? normalizeWhitespace ( decodeHtmlEntities ( stripHtml ( titleMatch [ 1 ] ) ) ) : "" ;
3038 const priceText = normalizeWhitespace ( stripHtml ( priceMatch ? priceMatch [ 1 ] : "" ) ) ;
3139 const isFreeLabel = / \b f r e e \b / i. test ( priceText ) ;
40+ const discountedToZero = isDiscountedToZero ( priceText , row ) ;
3241
33- if ( ! href || ! title || ( ! isFreeLabel && ! isDiscountedToZero ( priceText , row ) ) ) {
42+ if ( ! href || ! title || ( ! discountedToZero && ( ! allowFreeLabel || ! isFreeLabel ) ) ) {
3443 continue ;
3544 }
3645
@@ -51,7 +60,7 @@ export function parseSearchRows(html) {
5160 title,
5261 url : href || buildSteamUrlFromStableId ( stableId ) ,
5362 promoType : PROMO_TYPES . FREE_TO_KEEP ,
54- rawTypeLabel : "Store special" ,
63+ rawTypeLabel,
5564 sourceId : SOURCE_IDS . STORE_SEARCH ,
5665 sourceFingerprint : createHash ( row ) ,
5766 rowText
@@ -64,8 +73,19 @@ export function parseSearchRows(html) {
6473export const steamStoreSearchProvider = {
6574 id : SOURCE_IDS . STORE_SEARCH ,
6675 async fetchPromotions ( ) {
67- const response = await fetchJsonWithTimeout ( SEARCH_RESULTS_URL ) ;
68- const promotions = parseSearchRows ( response ?. results_html || "" ) ;
76+ const [ specialsResponse , ...freePriceResponses ] = await Promise . all ( [
77+ fetchJsonWithTimeout ( SEARCH_RESULTS_URL ) ,
78+ ...Array . from ( { length : FREE_PRICE_SEARCH_PAGE_COUNT } , ( _ , index ) => {
79+ return fetchJsonWithTimeout ( buildFreePriceSearchUrl ( index * FREE_PRICE_SEARCH_PAGE_SIZE ) ) . catch ( ( ) => null ) ;
80+ } )
81+ ] ) ;
82+ const promotions = [
83+ ...parseSearchRows ( specialsResponse ?. results_html || "" ) ,
84+ ...freePriceResponses . flatMap ( ( response ) => parseSearchRows ( response ?. results_html || "" , {
85+ allowFreeLabel : false ,
86+ rawTypeLabel : "Store free price"
87+ } ) )
88+ ] ;
6989
7090 return {
7191 promotions,
0 commit comments