1+ import type { Route } from '@/types' ;
2+ import ofetch from '@/utils/ofetch' ;
3+ import { parseDate } from '@/utils/parse-date' ;
4+ import { load } from 'cheerio' ;
5+
6+ export const route : Route = {
7+ path : '/:category?' ,
8+ categories : [ 'finance' ] ,
9+ example : '/polymarket/trending' ,
10+ parameters : {
11+ category : {
12+ description : 'Category slug, e.g. trending, breaking, politics, geopolitics, crypto, finance, iran, economy, tech, sports, culture' ,
13+ default : 'trending' ,
14+ } ,
15+ } ,
16+ features : {
17+ requireConfig : false ,
18+ requirePuppeteer : false ,
19+ antiCrawler : false ,
20+ supportBT : false ,
21+ supportPodcast : false ,
22+ supportScihub : false ,
23+ } ,
24+ radar : [
25+ {
26+ source : [ 'polymarket.com' , 'polymarket.com/:category' ] ,
27+ target : '/:category' ,
28+ } ,
29+ ] ,
30+ name : 'Markets' ,
31+ url : 'polymarket.com' ,
32+ maintainers : [ 'heki' ] ,
33+ handler,
34+ } ;
35+
36+ // Helper function to find query with events data
37+ function findEventsQuery ( queries , category ) {
38+ for ( const query of queries ) {
39+ const queryKey = query ?. queryKey || [ ] ;
40+ const data = query ?. state ?. data ;
41+
42+ // Check if this query has pages with events
43+ if ( data ?. pages ?. [ 0 ] ?. events ?. length > 0 ) {
44+ // For category pages, check if queryKey matches the category
45+ if ( category !== 'trending' ) {
46+ const keyStr = JSON . stringify ( queryKey ) ;
47+ if ( keyStr . includes ( category ) || keyStr . includes ( 'markets' ) ) {
48+ return data . pages ;
49+ }
50+ } else {
51+ return data . pages ;
52+ }
53+ }
54+ }
55+ return null ;
56+ }
57+
58+ async function handler ( ctx ) {
59+ const category = ctx . req . param ( 'category' ) || 'trending' ;
60+ const baseUrl = 'https://polymarket.com' ;
61+
62+ let url : string ;
63+ if ( category === 'breaking' ) {
64+ url = `${ baseUrl } /breaking` ;
65+ } else if ( category === 'trending' ) {
66+ url = baseUrl ;
67+ } else {
68+ url = `${ baseUrl } /${ category } ` ;
69+ }
70+
71+ const response = await ofetch ( url , {
72+ headers : {
73+ Accept : 'text/html' ,
74+ } ,
75+ } ) ;
76+
77+ const $ = load ( response ) ;
78+ const nextDataScript = $ ( 'script#__NEXT_DATA__' ) . html ( ) ;
79+
80+ if ( ! nextDataScript ) {
81+ throw new Error ( 'Failed to find __NEXT_DATA__' ) ;
82+ }
83+
84+ const nextData = JSON . parse ( nextDataScript ) ;
85+ const queries = nextData . props . pageProps . dehydratedState . queries ;
86+
87+ let items : any [ ] ;
88+
89+ if ( category === 'breaking' ) {
90+ // Breaking page: find query with markets array
91+ let markets : any [ ] = [ ] ;
92+ for ( const query of queries ) {
93+ if ( query ?. state ?. data ?. markets ?. length > 0 ) {
94+ markets = query . state . data . markets ;
95+ break ;
96+ }
97+ }
98+
99+ items = markets . map ( ( market ) => {
100+ const outcomes = market . outcomePrices
101+ ? market . outcomePrices . map ( ( price , i ) => `Option ${ i + 1 } : ${ ( Number ( price ) * 100 ) . toFixed ( 1 ) } %` ) . join ( ' | ' )
102+ : '' ;
103+
104+ return {
105+ title : market . question ,
106+ description : `
107+ <p><strong>Odds:</strong> ${ outcomes } </p>
108+ <p><strong>24h Change:</strong> ${ market . oneDayPriceChange ? ( market . oneDayPriceChange * 100 ) . toFixed ( 1 ) + '%' : 'N/A' } </p>
109+ ${ market . image ? `<img src="${ market . image } " alt="${ market . question } " style="max-width: 100%;">` : '' }
110+ ` ,
111+ link : `${ baseUrl } /event/${ market . slug } ` ,
112+ pubDate : parseDate ( market . events ?. [ 0 ] ?. startDate || market . updatedAt ) ,
113+ } ;
114+ } ) ;
115+ } else {
116+ // Trending or category pages: find events array
117+ const pages = findEventsQuery ( queries , category ) ;
118+
119+ if ( ! pages ) {
120+ throw new Error ( 'No events found for this category' ) ;
121+ }
122+
123+ const events = pages [ 0 ] ?. events || [ ] ;
124+
125+ items = events . map ( ( event ) => {
126+ // Build description from markets
127+ const marketsHtml = event . markets
128+ ?. slice ( 0 , 3 )
129+ . map ( ( market ) => {
130+ const outcomes = market . outcomes || [ ] ;
131+ const prices = market . outcomePrices || [ ] ;
132+ const oddsDisplay = outcomes . map ( ( o , i ) => `${ o } : ${ ( Number ( prices [ i ] ) * 100 ) . toFixed ( 1 ) } %` ) . join ( ' | ' ) ;
133+ return `<li><strong>${ market . question } </strong><br>${ oddsDisplay } </li>` ;
134+ } )
135+ . join ( '' ) || '' ;
136+
137+ return {
138+ title : event . title ,
139+ description : `
140+ ${ event . description ? `<p>${ event . description } </p>` : '' }
141+ <p><strong>Volume:</strong> $${ Number ( event . volume || 0 ) . toLocaleString ( ) } </p>
142+ ${ event . live ? '<p>🔴 <strong>LIVE</strong></p>' : '' }
143+ ${ marketsHtml ? `<h4>Markets:</h4><ul>${ marketsHtml } </ul>` : '' }
144+ ${ event . image ? `<img src="${ event . image } " alt="${ event . title } " style="max-width: 100%;">` : '' }
145+ ` ,
146+ link : `${ baseUrl } /event/${ event . slug } ` ,
147+ pubDate : parseDate ( event . startDate || event . createdAt ) ,
148+ category : event . tags ?. map ( ( t ) => t . label || t ) || [ ] ,
149+ } ;
150+ } ) ;
151+ }
152+
153+ const categoryName = category . charAt ( 0 ) . toUpperCase ( ) + category . slice ( 1 ) ;
154+
155+ return {
156+ title : `Polymarket - ${ categoryName } ` ,
157+ link : url ,
158+ item : items ,
159+ } ;
160+ }
0 commit comments