@@ -316,6 +316,24 @@ const cleanTicker = (ticker: string): string => {
316316 return ticker . split ( ":" ) [ 0 ] ?. trim ( ) ?? ticker ;
317317} ;
318318
319+ const sanitizeCapitolTradesUrl = ( href : string ) : string => {
320+ if ( ! href ) return "" ;
321+
322+ try {
323+ const parsed = new URL ( href , "https://www.capitoltrades.com" ) ;
324+ const isAllowedHost =
325+ parsed . hostname === "www.capitoltrades.com" ||
326+ parsed . hostname === "capitoltrades.com" ;
327+ const isTradePath = parsed . pathname . startsWith ( "/trades/" ) ;
328+
329+ if ( ! isAllowedHost || ! isTradePath ) return "" ;
330+
331+ return parsed . toString ( ) ;
332+ } catch {
333+ return "" ;
334+ }
335+ } ;
336+
319337export const parseCapitolTradesHTML = ( html : string ) : CongressTrade [ ] => {
320338 const $ = cheerio . load ( html ) ;
321339 const trades : CongressTrade [ ] = [ ] ;
@@ -380,11 +398,7 @@ export const parseCapitolTradesHTML = (html: string): CongressTrade[] => {
380398 $ ( row ) . find ( "a[href*='/trades/']" ) . attr ( "href" ) ??
381399 politicianCell . find ( "a[href*='/trades/']" ) . attr ( "href" ) ??
382400 "" ;
383- const url = rowLink
384- ? rowLink . startsWith ( "http" )
385- ? rowLink
386- : `https://www.capitoltrades.com${ rowLink } `
387- : "" ;
401+ const url = sanitizeCapitolTradesUrl ( rowLink ) ;
388402
389403 const tradeDate = parseTradeDate ( tradeDateText ) ;
390404 if ( ! tradeDate ) {
@@ -486,7 +500,13 @@ export const getTradingViewUrl = (ticker: string): string => {
486500
487501export const formatTradeItem = (
488502 trade : CongressTrade ,
489- ) : { text : string ; detail : string ; url : string ; linkText : string } => {
503+ ) : {
504+ text : string ;
505+ detail : string ;
506+ detailUrl : string ;
507+ url : string ;
508+ linkText : string ;
509+ } => {
490510 const prefix = trade . hot ? "🔥 " : "" ;
491511 const action = trade . type === "buy" ? "purchased" : "sold" ;
492512 const text = `${ prefix } ${ formatChamber ( trade . chamber ) } ${ trade . politician } (${ formatPartyState ( trade ) } ) ${ action } ${ trade . ticker } ` ;
@@ -497,6 +517,7 @@ export const formatTradeItem = (
497517 return {
498518 text,
499519 detail,
520+ detailUrl : trade . url ,
500521 url : getTradingViewUrl ( trade . ticker ) ,
501522 linkText : trade . ticker ,
502523 } ;
@@ -551,6 +572,7 @@ export const deduplicateTrades = (
551572 const totalAmountLower = group . reduce ( ( sum , t ) => sum + t . amountLower , 0 ) ;
552573 const maxScore = Math . max ( ...group . map ( ( t ) => t . score ) ) ;
553574 const relevantTrade = group . find ( ( t ) => t . committeeRelevance ) ;
575+ const detailTrade = group . find ( ( t ) => t . url ) ?? first ;
554576 result . push ( {
555577 politician : first . politician ,
556578 party : first . party ,
@@ -563,7 +585,7 @@ export const deduplicateTrades = (
563585 maxScore,
564586 hot : maxScore >= 6 ,
565587 trades : group ,
566- url : first . url ,
588+ url : detailTrade . url ,
567589 committeeRelevance : relevantTrade ?. committeeRelevance ?? null ,
568590 } ) ;
569591 }
@@ -594,7 +616,13 @@ const formatCompactAmount = (amount: number): string => {
594616
595617export const formatDeduplicatedItem = (
596618 entry : CongressTrade | GroupedTrade ,
597- ) : { text : string ; detail : string ; url : string ; linkText : string } => {
619+ ) : {
620+ text : string ;
621+ detail : string ;
622+ detailUrl : string ;
623+ url : string ;
624+ linkText : string ;
625+ } => {
598626 if ( ! ( "count" in entry ) ) return formatTradeItem ( entry ) ;
599627
600628 const prefix = entry . hot ? "🔥 " : "" ;
@@ -608,6 +636,7 @@ export const formatDeduplicatedItem = (
608636 return {
609637 text,
610638 detail,
639+ detailUrl : entry . url ,
611640 url : getTradingViewUrl ( entry . ticker ) ,
612641 linkText : entry . ticker ,
613642 } ;
@@ -743,8 +772,14 @@ export const congressTradesSource: DataSource = {
743772 title : "Congress Trades" ,
744773 icon : "🏛" ,
745774 items : deduplicated . map ( ( entry ) => {
746- const { text, detail, url, linkText } = formatDeduplicatedItem ( entry ) ;
747- return { text, detail, ...( url ? { url, linkText } : { } ) } ;
775+ const { text, detail, detailUrl, url, linkText } =
776+ formatDeduplicatedItem ( entry ) ;
777+ return {
778+ text,
779+ detail,
780+ ...( detailUrl ? { detailUrl } : { } ) ,
781+ ...( url ? { url, linkText } : { } ) ,
782+ } ;
748783 } ) ,
749784 } ;
750785 } catch ( error ) {
0 commit comments