← back to Rentv V1
Localize all rentv.com photos + drop link-outs (serve everything locally)
f934418ae00b09a86f9287422666664363ef5010 · 2026-07-23 14:07:33 -0700 · Steve Abrams
- Shared scripts/lib/localize.cjs downloads rentv.com property photos into
public/img/news/ and returns local /img/news/ paths (fail-closed to '').
- Wired into pull-news.mjs, pull-deals.mjs (durable across cron) and
server.js fetchArticle (live article hero). Backfilled news.json + deals.json.
- article.html: removed the two clickable 'View on RENTV.com' link-outs;
attribution kept as plain text.
- .gitignore the downloaded photo cache (regenerable), keep dir via .gitkeep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M .gitignoreM data/deals.jsonM data/markets.jsonM data/news.jsonM data/ticker.jsonM public/article.htmlA public/img/news/.gitkeepA scripts/lib/localize.cjsM scripts/pull-deals.mjsM scripts/pull-news.mjsM server.js
Diff
commit f934418ae00b09a86f9287422666664363ef5010
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 23 14:07:33 2026 -0700
Localize all rentv.com photos + drop link-outs (serve everything locally)
- Shared scripts/lib/localize.cjs downloads rentv.com property photos into
public/img/news/ and returns local /img/news/ paths (fail-closed to '').
- Wired into pull-news.mjs, pull-deals.mjs (durable across cron) and
server.js fetchArticle (live article hero). Backfilled news.json + deals.json.
- article.html: removed the two clickable 'View on RENTV.com' link-outs;
attribution kept as plain text.
- .gitignore the downloaded photo cache (regenerable), keep dir via .gitkeep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
.gitignore | 4 ++++
data/deals.json | 56 +++++++++++++++++++++++-----------------------
data/markets.json | 2 +-
data/news.json | 58 ++++++++++++++++++++++++------------------------
data/ticker.json | 2 +-
public/article.html | 8 +++----
public/img/news/.gitkeep | 0
scripts/lib/localize.cjs | 53 +++++++++++++++++++++++++++++++++++++++++++
scripts/pull-deals.mjs | 6 +++++
scripts/pull-news.mjs | 7 ++++++
server.js | 3 +++
11 files changed, 135 insertions(+), 64 deletions(-)
diff --git a/.gitignore b/.gitignore
index 961ccec..1b5d619 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,7 @@ node_modules/
.DS_Store
data/subscribers.jsonl
data/posts.json
+
+# Localized rentv.com photos — regenerable cache, served locally (not source)
+public/img/news/*
+!public/img/news/.gitkeep
diff --git a/data/deals.json b/data/deals.json
index 5847be9..82b2f51 100644
--- a/data/deals.json
+++ b/data/deals.json
@@ -7,7 +7,7 @@
"id": "34238",
"title": "Gilbert, AZ Multifamily Community Refinanced with New Loan for $51.8 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34238",
- "image": "https://www.rentv.com/images/dynamic/news_20296_4353EElliotRdGilbertAZDarcieGiacchettoJul212026.jpg",
+ "image": "/img/news/news_20296_4353EElliotRdGilbertAZDarcieGiacchettoJul212026.jpg",
"txn_type": "Financing",
"property_type": "Multifamily",
"city": "Gilbert",
@@ -26,7 +26,7 @@
"id": "34236",
"title": "The Swig Company Obtains Refi on Office High-Rise in San Franciscos Financial District",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34236",
- "image": "https://www.rentv.com/images/dynamic/news_20294_220MontgomeryStSFNancyAmaralA2N2Jul162026.jpg",
+ "image": "/img/news/news_20294_220MontgomeryStSFNancyAmaralA2N2Jul162026.jpg",
"txn_type": "Financing",
"property_type": "Office",
"city": "San Francisco",
@@ -45,7 +45,7 @@
"id": "34233",
"title": "Tailwind Investment Group Spends $65 Mil on Two Sacramento Res Properties",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34233",
- "image": "https://www.rentv.com/images/dynamic/news_20291_6633ValleyHiDrSacramentoDavidHulmeTailwindJul182026.jpg",
+ "image": "/img/news/news_20291_6633ValleyHiDrSacramentoDavidHulmeTailwindJul182026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Sacramento",
@@ -64,7 +64,7 @@
"id": "34239",
"title": "Fresno Multifamily Sale Works Out to $177k per Unit",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34239",
- "image": "https://www.rentv.com/images/dynamic/news_20297_585WestSierraAveFresnoDaveEbelingJul212026.jpg",
+ "image": "/img/news/news_20297_585WestSierraAveFresnoDaveEbelingJul212026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Fresno",
@@ -83,7 +83,7 @@
"id": "34235",
"title": "Retail Portfolio in Downtown Long Beach Fetches $30 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34235",
- "image": "https://www.rentv.com/images/dynamic/news_20293_MosiacLongBeachAnneMonaghanJul202026.jpg",
+ "image": "/img/news/news_20293_MosiacLongBeachAnneMonaghanJul202026.jpg",
"txn_type": "Sale",
"property_type": "Retail",
"city": "Long Beach",
@@ -102,7 +102,7 @@
"id": "34224",
"title": "Shorenstein Buys 77k sf Bay Area Office Building",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34224",
- "image": "https://www.rentv.com/images/dynamic/news_20283_550AllertonRedwoodCityNickTrombolaNWMKJul162026.jpg",
+ "image": "/img/news/news_20283_550AllertonRedwoodCityNickTrombolaNWMKJul162026.jpg",
"txn_type": "Sale",
"property_type": "Office",
"city": "Bay Area",
@@ -121,7 +121,7 @@
"id": "34226",
"title": "Carson Distribution Facility Acquired by WareSpace in a $23 Mil Deal",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34226",
- "image": "https://www.rentv.com/images/dynamic/news_20285_860SandhillAveCarsonElisabethManvilleSmartJul162026.jpg",
+ "image": "/img/news/news_20285_860SandhillAveCarsonElisabethManvilleSmartJul162026.jpg",
"txn_type": "Sale",
"property_type": "Industrial",
"city": "Phoenix",
@@ -140,7 +140,7 @@
"id": "34218",
"title": "Camden Property Trust Buys Gilbert, AZ Res Community in $124 Mil Deal",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34218",
- "image": "https://www.rentv.com/images/dynamic/news_20278_3150ERayRdGilbertKimHoidalCBREJul132026.jpg",
+ "image": "/img/news/news_20278_3150ERayRdGilbertKimHoidalCBREJul132026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Gilbert",
@@ -178,7 +178,7 @@
"id": "34227",
"title": "MG Properties Buys 188-Unit Portland Res Asset",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34227",
- "image": "https://www.rentv.com/images/dynamic/news_20286_TupeloAlleyPortlandPaulKaseburgMGPropJul162026.jpg",
+ "image": "/img/news/news_20286_TupeloAlleyPortlandPaulKaseburgMGPropJul162026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Portland",
@@ -197,7 +197,7 @@
"id": "34216",
"title": "Meta Housing Well Underway with 300-Unit Res Community in Woodland Hills",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34216",
- "image": "https://www.rentv.com/images/dynamic/news_20277_21300WOxnardStWHillsHollyJenvyJul152026.jpg",
+ "image": "/img/news/news_20277_21300WOxnardStWHillsHollyJenvyJul152026.jpg",
"txn_type": "Development",
"property_type": "Multifamily",
"city": "Phoenix",
@@ -216,7 +216,7 @@
"id": "34214",
"title": "Intercontinental Real Estate Corporation Closes $69 Mil Retail Buy in Washington",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34214",
- "image": "https://www.rentv.com/images/dynamic/news_20274_1402LakeTappsPkwySEAuburnWABruceBeckJul152026.jpg",
+ "image": "/img/news/news_20274_1402LakeTappsPkwySEAuburnWABruceBeckJul152026.jpg",
"txn_type": "Sale",
"property_type": "Retail",
"city": "Auburn",
@@ -235,7 +235,7 @@
"id": "34208",
"title": "Eagle Partners and The Vistria Group Spend $132 Mil on Garden Grove Mixed-Income Res Community",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34208",
- "image": "https://www.rentv.com/images/dynamic/news_20267_12091BayportStGardenGroveCOSTARAnnieNoebelCornersCommJul142026.jpg",
+ "image": "/img/news/news_20267_12091BayportStGardenGroveCOSTARAnnieNoebelCornersCommJul142026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Phoenix",
@@ -254,7 +254,7 @@
"id": "34204",
"title": "Oceanside Res Community Trades for $77.5 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34204",
- "image": "https://www.rentv.com/images/dynamic/news_20262_3851SherbourneDrOceansideDANKIRKSEYClaudiaLipmannCBREJul142026.jpg",
+ "image": "/img/news/news_20262_3851SherbourneDrOceansideDANKIRKSEYClaudiaLipmannCBREJul142026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Phoenix",
@@ -273,7 +273,7 @@
"id": "34210",
"title": "BKM Capital Partners Boosts Pac Northwest Holdings with Purchase of 400k sf Industrial Buy in Washington",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34210",
- "image": "https://www.rentv.com/images/dynamic/news_20270_SouthcenterWestBusinessParkTukwilaWASuleAygorenConnectJul142026.jpg",
+ "image": "/img/news/news_20270_SouthcenterWestBusinessParkTukwilaWASuleAygorenConnectJul142026.jpg",
"txn_type": "Sale",
"property_type": "Industrial",
"city": "Tukwila",
@@ -292,7 +292,7 @@
"id": "34203",
"title": "The Bascom Group Pays Over $53 Mil for OC Res Community",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34203",
- "image": "https://www.rentv.com/images/dynamic/news_20261_6968CampusDrBuenaParkJerryFinkBascomJul132026.jpg",
+ "image": "/img/news/news_20261_6968CampusDrBuenaParkJerryFinkBascomJul132026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Phoenix",
@@ -311,7 +311,7 @@
"id": "34199",
"title": "Leasing Assignment for 650k sf San Francisco Office Tower Awarded to CBRE",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34199",
- "image": "https://www.rentv.com/images/dynamic/news_20259_415NatomaSFranciscoIrineSpivakCBREJun162026.jpg",
+ "image": "/img/news/news_20259_415NatomaSFranciscoIrineSpivakCBREJun162026.jpg",
"txn_type": "Lease",
"property_type": "Office",
"city": "San Francisco",
@@ -330,7 +330,7 @@
"id": "34202",
"title": "SummerHill Apartment Communities Breaks Ground on New 251-Unit Bay Area Res Development",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34202",
- "image": "https://www.rentv.com/images/dynamic/news_20260_11ElcaminoRealSMateoAnneMonaghanJul72026.jpg",
+ "image": "/img/news/news_20260_11ElcaminoRealSMateoAnneMonaghanJul72026.jpg",
"txn_type": "Development",
"property_type": "Multifamily",
"city": "Bay Area",
@@ -368,7 +368,7 @@
"id": "34195",
"title": "Borstein Enterprises Picks Up 282k sf OC Industrial Asset",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34195",
- "image": "https://www.rentv.com/images/dynamic/news_20256_15339BarrancaPkwyIrvineSavannahDurbinCWJul62026.jpg",
+ "image": "/img/news/news_20256_15339BarrancaPkwyIrvineSavannahDurbinCWJul62026.jpg",
"txn_type": "Sale",
"property_type": "Industrial",
"city": "Phoenix",
@@ -387,7 +387,7 @@
"id": "34188",
"title": "Chino Distribution Facility Leased by Auto Parts Distributor in $42 Mil Deal",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34188",
- "image": "https://www.rentv.com/images/dynamic/news_20252_16380EuclidAveChinoElisabethManvilleSmartJul22026.jpg",
+ "image": "/img/news/news_20252_16380EuclidAveChinoElisabethManvilleSmartJul22026.jpg",
"txn_type": "Lease",
"property_type": "Industrial",
"city": "Phoenix",
@@ -406,7 +406,7 @@
"id": "34186",
"title": "Santa Clara Industrial Property Trades for $25.75 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34186",
- "image": "https://www.rentv.com/images/dynamic/news_20250_651MartinAveSClaraGraceLewisJLLJul12026.jpg",
+ "image": "/img/news/news_20250_651MartinAveSClaraGraceLewisJLLJul12026.jpg",
"txn_type": "Sale",
"property_type": "Industrial",
"city": "Phoenix",
@@ -425,7 +425,7 @@
"id": "34181",
"title": "Redwood West and Panattoni Buy The Pike Destination Center in Long Beach",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34181",
- "image": "https://www.rentv.com/images/dynamic/news_20245_95SPineAveLongBeachJessicaSpauldingJun302026.jpg",
+ "image": "/img/news/news_20245_95SPineAveLongBeachJessicaSpauldingJun302026.jpg",
"txn_type": "Sale",
"property_type": "Retail",
"city": "Long Beach",
@@ -444,7 +444,7 @@
"id": "34180",
"title": "Kennedy Wilson and Jamison Form Partnership to Develop 4,000 Units of Affordable Housing in Los Angeles",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34180",
- "image": "https://www.rentv.com/images/dynamic/news_20244_350SHopeBruceBeckJune302026.jpg",
+ "image": "/img/news/news_20244_350SHopeBruceBeckJune302026.jpg",
"txn_type": "Development",
"property_type": "Office",
"city": "Los Angeles",
@@ -463,7 +463,7 @@
"id": "34086",
"title": "Oak Park Investments and Arise Investments Acquire Westlake Village Warehouse/Office Building in $19 Mil Transaction",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34086",
- "image": "https://www.rentv.com/images/dynamic/news_20161_5785LinderoCanyonRdWstlkVillageDannySoroudiAriseJune22026.jpg",
+ "image": "/img/news/news_20161_5785LinderoCanyonRdWstlkVillageDannySoroudiAriseJune22026.jpg",
"txn_type": "Sale",
"property_type": "Office",
"city": "Phoenix",
@@ -482,7 +482,7 @@
"id": "34177",
"title": "Holland Partner Group Closes $105 Mil Bay Area Res Buy",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34177",
- "image": "https://www.rentv.com/images/dynamic/news_20242_1432WestSanCarlosSt.jpg",
+ "image": "/img/news/news_20242_1432WestSanCarlosSt.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Bay Area",
@@ -501,7 +501,7 @@
"id": "34172",
"title": "Haven Housing buys 264-Unit Res Community in Austin Metro Area",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34172",
- "image": "https://www.rentv.com/images/dynamic/news_20237_15001StrathavenPassPflugervilleTXElliottGolanConnectJun262026.jpg",
+ "image": "/img/news/news_20237_15001StrathavenPassPflugervilleTXElliottGolanConnectJun262026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Austin",
@@ -520,7 +520,7 @@
"id": "34174",
"title": "MG Properties Acquires Two Multifamily Properties in Gresham, OR in Deals Totaling $60.8 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34174",
- "image": "https://www.rentv.com/images/dynamic/news_20239_ParksideApartmentsGreshamORPaulKasebergMGPropJune262026.jpg",
+ "image": "/img/news/news_20239_ParksideApartmentsGreshamORPaulKasebergMGPropJune262026.jpg",
"txn_type": "Sale",
"property_type": "Multifamily",
"city": "Gresham",
@@ -539,7 +539,7 @@
"id": "34171",
"title": "Leading AI Firm Leases 1.1 msf of Space at Luke Field in Glendale, AZ",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34171",
- "image": "https://www.rentv.com/images/dynamic/news_20236_7733LitchfieldRdGlendaleAZStaceyHershauerJun242026.jpg",
+ "image": "/img/news/news_20236_7733LitchfieldRdGlendaleAZStaceyHershauerJun242026.jpg",
"txn_type": "Lease",
"property_type": "Industrial",
"city": "Glendale",
@@ -558,7 +558,7 @@
"id": "34169",
"title": "Seattle Area Res Community Refinanced with $82.5 Mil Loan from Mesa West Capital",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34169",
- "image": "https://www.rentv.com/images/dynamic/news_20233_OlinFieldsEverettWABruceBeckJun242026.jpg",
+ "image": "/img/news/news_20233_OlinFieldsEverettWABruceBeckJun242026.jpg",
"txn_type": "Financing",
"property_type": "Multifamily",
"city": "Seattle",
diff --git a/data/markets.json b/data/markets.json
index 85a6671..f95b0ed 100644
--- a/data/markets.json
+++ b/data/markets.json
@@ -1,5 +1,5 @@
{
- "fetched_at": "2026-07-23T20:45:57.860Z",
+ "fetched_at": "2026-07-23T21:06:23.228Z",
"source": "Yahoo Finance — live (CBOE Treasury yields, Dow Jones/MSCI REIT indices)",
"curve": [
{
diff --git a/data/news.json b/data/news.json
index 0433c35..5688775 100644
--- a/data/news.json
+++ b/data/news.json
@@ -1,6 +1,6 @@
{
"source": "rentv.com — live",
- "fetched_at": "2026-07-23T20:45:57.532Z",
+ "fetched_at": "2026-07-23T21:06:22.958Z",
"count": 30,
"items": [
{
@@ -8,56 +8,56 @@
"title": "Gilbert, AZ Multifamily Community Refinanced with New Loan for $51.8 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34238",
"cat": "Financing",
- "image": "https://www.rentv.com/images/dynamic/news_20296_4353EElliotRdGilbertAZDarcieGiacchettoJul212026.jpg"
+ "image": "/img/news/news_20296_4353EElliotRdGilbertAZDarcieGiacchettoJul212026.jpg"
},
{
"id": "34236",
"title": "The Swig Company Obtains Refi on Office High-Rise in San Franciscos Financial District",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34236",
"cat": "Financing",
- "image": "https://www.rentv.com/images/dynamic/news_20294_220MontgomeryStSFNancyAmaralA2N2Jul162026.jpg"
+ "image": "/img/news/news_20294_220MontgomeryStSFNancyAmaralA2N2Jul162026.jpg"
},
{
"id": "34233",
"title": "Tailwind Investment Group Spends $65 Mil on Two Sacramento Res Properties",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34233",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20291_6633ValleyHiDrSacramentoDavidHulmeTailwindJul182026.jpg"
+ "image": "/img/news/news_20291_6633ValleyHiDrSacramentoDavidHulmeTailwindJul182026.jpg"
},
{
"id": "34239",
"title": "Fresno Multifamily Sale Works Out to $177k per Unit",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34239",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20297_585WestSierraAveFresnoDaveEbelingJul212026.jpg"
+ "image": "/img/news/news_20297_585WestSierraAveFresnoDaveEbelingJul212026.jpg"
},
{
"id": "34235",
"title": "Retail Portfolio in Downtown Long Beach Fetches $30 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34235",
"cat": "Retail",
- "image": "https://www.rentv.com/images/dynamic/news_20293_MosiacLongBeachAnneMonaghanJul202026.jpg"
+ "image": "/img/news/news_20293_MosiacLongBeachAnneMonaghanJul202026.jpg"
},
{
"id": "34224",
"title": "Shorenstein Buys 77k sf Bay Area Office Building",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34224",
"cat": "Sales",
- "image": "https://www.rentv.com/images/dynamic/news_20283_550AllertonRedwoodCityNickTrombolaNWMKJul162026.jpg"
+ "image": "/img/news/news_20283_550AllertonRedwoodCityNickTrombolaNWMKJul162026.jpg"
},
{
"id": "34226",
"title": "Carson Distribution Facility Acquired by WareSpace in a $23 Mil Deal",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34226",
"cat": "Sales",
- "image": "https://www.rentv.com/images/dynamic/news_20285_860SandhillAveCarsonElisabethManvilleSmartJul162026.jpg"
+ "image": "/img/news/news_20285_860SandhillAveCarsonElisabethManvilleSmartJul162026.jpg"
},
{
"id": "34218",
"title": "Camden Property Trust Buys Gilbert, AZ Res Community in $124 Mil Deal",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34218",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20278_3150ERayRdGilbertKimHoidalCBREJul132026.jpg"
+ "image": "/img/news/news_20278_3150ERayRdGilbertKimHoidalCBREJul132026.jpg"
},
{
"id": "34225",
@@ -71,63 +71,63 @@
"title": "MG Properties Buys 188-Unit Portland Res Asset",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34227",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20286_TupeloAlleyPortlandPaulKaseburgMGPropJul162026.jpg"
+ "image": "/img/news/news_20286_TupeloAlleyPortlandPaulKaseburgMGPropJul162026.jpg"
},
{
"id": "34216",
"title": "Meta Housing Well Underway with 300-Unit Res Community in Woodland Hills",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34216",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20277_21300WOxnardStWHillsHollyJenvyJul152026.jpg"
+ "image": "/img/news/news_20277_21300WOxnardStWHillsHollyJenvyJul152026.jpg"
},
{
"id": "34214",
"title": "Intercontinental Real Estate Corporation Closes $69 Mil Retail Buy in Washington",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34214",
"cat": "Retail",
- "image": "https://www.rentv.com/images/dynamic/news_20274_1402LakeTappsPkwySEAuburnWABruceBeckJul152026.jpg"
+ "image": "/img/news/news_20274_1402LakeTappsPkwySEAuburnWABruceBeckJul152026.jpg"
},
{
"id": "34208",
"title": "Eagle Partners and The Vistria Group Spend $132 Mil on Garden Grove Mixed-Income Res Community",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34208",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20267_12091BayportStGardenGroveCOSTARAnnieNoebelCornersCommJul142026.jpg"
+ "image": "/img/news/news_20267_12091BayportStGardenGroveCOSTARAnnieNoebelCornersCommJul142026.jpg"
},
{
"id": "34204",
"title": "Oceanside Res Community Trades for $77.5 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34204",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20262_3851SherbourneDrOceansideDANKIRKSEYClaudiaLipmannCBREJul142026.jpg"
+ "image": "/img/news/news_20262_3851SherbourneDrOceansideDANKIRKSEYClaudiaLipmannCBREJul142026.jpg"
},
{
"id": "34210",
"title": "BKM Capital Partners Boosts Pac Northwest Holdings with Purchase of 400k sf Industrial Buy in Washington",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34210",
"cat": "Industrial",
- "image": "https://www.rentv.com/images/dynamic/news_20270_SouthcenterWestBusinessParkTukwilaWASuleAygorenConnectJul142026.jpg"
+ "image": "/img/news/news_20270_SouthcenterWestBusinessParkTukwilaWASuleAygorenConnectJul142026.jpg"
},
{
"id": "34203",
"title": "The Bascom Group Pays Over $53 Mil for OC Res Community",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34203",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20261_6968CampusDrBuenaParkJerryFinkBascomJul132026.jpg"
+ "image": "/img/news/news_20261_6968CampusDrBuenaParkJerryFinkBascomJul132026.jpg"
},
{
"id": "34199",
"title": "Leasing Assignment for 650k sf San Francisco Office Tower Awarded to CBRE",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34199",
"cat": "Sales",
- "image": "https://www.rentv.com/images/dynamic/news_20259_415NatomaSFranciscoIrineSpivakCBREJun162026.jpg"
+ "image": "/img/news/news_20259_415NatomaSFranciscoIrineSpivakCBREJun162026.jpg"
},
{
"id": "34202",
"title": "SummerHill Apartment Communities Breaks Ground on New 251-Unit Bay Area Res Development",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34202",
"cat": "Development",
- "image": "https://www.rentv.com/images/dynamic/news_20260_11ElcaminoRealSMateoAnneMonaghanJul72026.jpg"
+ "image": "/img/news/news_20260_11ElcaminoRealSMateoAnneMonaghanJul72026.jpg"
},
{
"id": "34196",
@@ -141,77 +141,77 @@
"title": "Borstein Enterprises Picks Up 282k sf OC Industrial Asset",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34195",
"cat": "Industrial",
- "image": "https://www.rentv.com/images/dynamic/news_20256_15339BarrancaPkwyIrvineSavannahDurbinCWJul62026.jpg"
+ "image": "/img/news/news_20256_15339BarrancaPkwyIrvineSavannahDurbinCWJul62026.jpg"
},
{
"id": "34188",
"title": "Chino Distribution Facility Leased by Auto Parts Distributor in $42 Mil Deal",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34188",
"cat": "Leases",
- "image": "https://www.rentv.com/images/dynamic/news_20252_16380EuclidAveChinoElisabethManvilleSmartJul22026.jpg"
+ "image": "/img/news/news_20252_16380EuclidAveChinoElisabethManvilleSmartJul22026.jpg"
},
{
"id": "34186",
"title": "Santa Clara Industrial Property Trades for $25.75 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34186",
"cat": "Industrial",
- "image": "https://www.rentv.com/images/dynamic/news_20250_651MartinAveSClaraGraceLewisJLLJul12026.jpg"
+ "image": "/img/news/news_20250_651MartinAveSClaraGraceLewisJLLJul12026.jpg"
},
{
"id": "34181",
"title": "Redwood West and Panattoni Buy The Pike Destination Center in Long Beach",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34181",
"cat": "Retail",
- "image": "https://www.rentv.com/images/dynamic/news_20245_95SPineAveLongBeachJessicaSpauldingJun302026.jpg"
+ "image": "/img/news/news_20245_95SPineAveLongBeachJessicaSpauldingJun302026.jpg"
},
{
"id": "34180",
"title": "Kennedy Wilson and Jamison Form Partnership to Develop 4,000 Units of Affordable Housing in Los Angeles",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34180",
"cat": "Development",
- "image": "https://www.rentv.com/images/dynamic/news_20244_350SHopeBruceBeckJune302026.jpg"
+ "image": "/img/news/news_20244_350SHopeBruceBeckJune302026.jpg"
},
{
"id": "34086",
"title": "Oak Park Investments and Arise Investments Acquire Westlake Village Warehouse/Office Building in $19 Mil Transaction",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34086",
"cat": "Industrial",
- "image": "https://www.rentv.com/images/dynamic/news_20161_5785LinderoCanyonRdWstlkVillageDannySoroudiAriseJune22026.jpg"
+ "image": "/img/news/news_20161_5785LinderoCanyonRdWstlkVillageDannySoroudiAriseJune22026.jpg"
},
{
"id": "34177",
"title": "Holland Partner Group Closes $105 Mil Bay Area Res Buy",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34177",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20242_1432WestSanCarlosSt.jpg"
+ "image": "/img/news/news_20242_1432WestSanCarlosSt.jpg"
},
{
"id": "34172",
"title": "Haven Housing buys 264-Unit Res Community in Austin Metro Area",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34172",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20237_15001StrathavenPassPflugervilleTXElliottGolanConnectJun262026.jpg"
+ "image": "/img/news/news_20237_15001StrathavenPassPflugervilleTXElliottGolanConnectJun262026.jpg"
},
{
"id": "34174",
"title": "MG Properties Acquires Two Multifamily Properties in Gresham, OR in Deals Totaling $60.8 Mil",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34174",
"cat": "Multifamily",
- "image": "https://www.rentv.com/images/dynamic/news_20239_ParksideApartmentsGreshamORPaulKasebergMGPropJune262026.jpg"
+ "image": "/img/news/news_20239_ParksideApartmentsGreshamORPaulKasebergMGPropJune262026.jpg"
},
{
"id": "34171",
"title": "Leading AI Firm Leases 1.1 msf of Space at Luke Field in Glendale, AZ",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34171",
"cat": "Leases",
- "image": "https://www.rentv.com/images/dynamic/news_20236_7733LitchfieldRdGlendaleAZStaceyHershauerJun242026.jpg"
+ "image": "/img/news/news_20236_7733LitchfieldRdGlendaleAZStaceyHershauerJun242026.jpg"
},
{
"id": "34169",
"title": "Seattle Area Res Community Refinanced with $82.5 Mil Loan from Mesa West Capital",
"url": "https://www.rentv.com/content/homepage/mainnews/news/34169",
"cat": "Financing",
- "image": "https://www.rentv.com/images/dynamic/news_20233_OlinFieldsEverettWABruceBeckJun242026.jpg"
+ "image": "/img/news/news_20233_OlinFieldsEverettWABruceBeckJun242026.jpg"
}
]
}
\ No newline at end of file
diff --git a/data/ticker.json b/data/ticker.json
index 1d422ed..71a4f97 100644
--- a/data/ticker.json
+++ b/data/ticker.json
@@ -1,5 +1,5 @@
{
- "fetched_at": "2026-07-23T20:45:57.833Z",
+ "fetched_at": "2026-07-23T21:06:23.213Z",
"la_office_vac": {
"label": "LA OFFICE VAC",
"value": "23.1%",
diff --git a/public/article.html b/public/article.html
index 7223914..e48d29a 100644
--- a/public/article.html
+++ b/public/article.html
@@ -92,7 +92,6 @@
document.getElementById('today').textContent = new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});
const esc=s=>(s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
const id=(location.pathname.split('/').filter(Boolean).pop()||'').replace(/[^0-9]/g,'');
-const RENTV='https://www.rentv.com/content/homepage/mainnews/news/'+id;
// Generic titles the reader proxy returns when rentv.com doesn't server-render the story.
const GENERIC=/^RENTV\.com\b|commercial real estate news and property/i;
async function load(){
@@ -108,17 +107,16 @@ async function load(){
// Prefer the feed's real headline; fall back to the proxy title only if it isn't the generic site title.
const title = item.title || (d.title && !GENERIC.test(d.title) ? d.title : 'RENTV Story');
const cat = item.cat || 'RENTV';
- const src = item.url || d.url || RENTV;
const hasBody = Array.isArray(d.body) && d.body.length>0;
if(!item.title && !hasBody && art0.status!=='fulfilled'){
- art.innerHTML='<p class="err">This story couldn’t be loaded right now. <a href="'+esc(RENTV)+'" target="_blank" rel="noopener">Read it on RENTV.com ↗</a><br><br><a href="/">← Back to the homepage</a></p>';
+ art.innerHTML='<p class="err">This story couldn’t be loaded right now.<br><br><a href="/">← Back to the homepage</a></p>';
return;
}
document.title=title+' — RENTV.com';
const hero=(d.image && !GENERIC.test(d.image))?'<div class="hero"><img src="'+esc(d.image)+'" alt="" loading="lazy" onerror="this.parentNode.style.background=\'linear-gradient(135deg,#1f2937,#c8102e)\';this.remove()"></div>':'<div class="hero"></div>';
const body=hasBody
? d.body.map(p=>'<p>'+esc(p)+'</p>').join('')
- : '<p>This story is published in full on RENTV.com. Read the complete report — including deal terms, parties and broker details — at the source link below.</p>';
+ : '<p>The full report — including deal terms, parties and broker details — is being republished here. Check back shortly for the complete story.</p>';
art.innerHTML=
'<div class="crumb"><a href="/">Home</a> › '+(['sales','leases','financing','development','multifamily','industrial','retail'].indexOf(cat.toLowerCase())>=0?'<a href="/'+esc(cat.toLowerCase())+'">'+esc(cat)+'</a>':'<span>'+esc(cat)+'</span>')+' › <span>Story</span></div>'+
'<span class="chip">'+esc(cat)+' · Western U.S.</span>'+
@@ -126,7 +124,7 @@ async function load(){
'<div class="byl">By RENTV Staff · live from rentv.com'+(d.fetched_at?(' · fetched '+new Date(d.fetched_at).toLocaleTimeString()):'')+'</div>'+
hero+
'<div class="body">'+body+'</div>'+
- '<div class="src">📰 Republished live from RENTV.com. <a href="'+esc(src)+'" target="_blank" rel="noopener">View the full story on RENTV.com ↗</a></div>'+
+ '<div class="src">📰 Republished live from RENTV.com · Western U.S. commercial real estate news.</div>'+
'<a class="back" href="/">← Back to all CRE news</a>';
}
const hamBtn=document.getElementById('hamBtn');
diff --git a/public/img/news/.gitkeep b/public/img/news/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/lib/localize.cjs b/scripts/lib/localize.cjs
new file mode 100644
index 0000000..16dafe7
--- /dev/null
+++ b/scripts/lib/localize.cjs
@@ -0,0 +1,53 @@
+'use strict';
+// Shared image localizer — the single source of truth for "never hotlink rentv.com".
+// Downloads a remote (rentv.com) property photo into public/img/news/ and returns the
+// LOCAL path (/img/news/<file>) the site serves itself. Used by BOTH the cron
+// (scripts/pull-news.mjs, via createRequire) and the live article proxy (server.js),
+// so a single fix covers every path that could otherwise leak an rentv.com asset URL.
+//
+// Contract:
+// - already-local ("/img/..." or "") → returned unchanged (idempotent, safe to re-run).
+// - download OK → returns "/img/news/<sanitized-filename>" (served by express.static).
+// - download FAIL → returns "" so the caller renders its gradient placeholder,
+// NEVER a leftover rentv.com URL (fail closed, not open).
+const fs = require('fs');
+const path = require('path');
+
+const IMG_DIR = path.join(__dirname, '..', '..', 'public', 'img', 'news');
+
+// Derive a safe, stable local filename from the remote URL's basename.
+function localName(url) {
+ const base = url.split('?')[0].split('#')[0].split('/').pop() || '';
+ const safe = base.replace(/[^A-Za-z0-9._-]/g, '_').slice(-160);
+ return safe || ('img_' + Buffer.from(url).toString('hex').slice(0, 16) + '.jpg');
+}
+
+async function localizeImage(url) {
+ if (!url || typeof url !== 'string') return '';
+ if (url.startsWith('/')) return url; // already local — leave it
+ if (!/^https?:\/\//i.test(url)) return ''; // not a fetchable absolute URL
+
+ const file = localName(url);
+ const dest = path.join(IMG_DIR, file);
+ const localPath = '/img/news/' + file;
+
+ // Cache hit: already downloaded and non-empty → reuse, no re-fetch.
+ try { if (fs.statSync(dest).size > 0) return localPath; } catch { /* not cached yet */ }
+
+ try {
+ fs.mkdirSync(IMG_DIR, { recursive: true });
+ const r = await fetch(url, {
+ headers: { 'User-Agent': 'Mozilla/5.0 RENTV-localizer' },
+ signal: AbortSignal.timeout(15000),
+ });
+ if (!r.ok) return '';
+ const buf = Buffer.from(await r.arrayBuffer());
+ if (!buf.length) return '';
+ fs.writeFileSync(dest, buf);
+ return localPath;
+ } catch {
+ return ''; // network/timeout → placeholder, not a leak
+ }
+}
+
+module.exports = { localizeImage, IMG_DIR };
diff --git a/scripts/pull-deals.mjs b/scripts/pull-deals.mjs
index d72eced..f2270b9 100644
--- a/scripts/pull-deals.mjs
+++ b/scripts/pull-deals.mjs
@@ -10,8 +10,12 @@
import { writeFileSync, readFileSync, mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
+import { createRequire } from 'node:module';
const HERE = dirname(fileURLToPath(import.meta.url));
const DATA = join(HERE, '..', 'data'); mkdirSync(DATA, { recursive: true });
+// Localize deal-card photos into public/img/news/ — the Deals grid serves them locally,
+// never hotlinking rentv.com. Same shared localizer as the news pipeline.
+const { localizeImage } = createRequire(import.meta.url)('./lib/localize.cjs');
async function fetchDecoded(url) {
const r = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 RENTV-v1' }, signal: AbortSignal.timeout(20000) });
@@ -140,6 +144,8 @@ try {
summary: summarize(it.title, body) || null,
});
}
+ // Download each remote photo locally and rewrite image → /img/news/<file> (fail → '' placeholder).
+ for (const d of deals) { d.image = await localizeImage(d.image); }
const payload = { source: 'rentv.com — live deal listings', fetched_at: new Date().toISOString(), count: deals.length, deals };
writeFileSync(join(DATA, 'deals.json'), JSON.stringify(payload, null, 1));
const withAmt = deals.filter((d) => d.amount).length;
diff --git a/scripts/pull-news.mjs b/scripts/pull-news.mjs
index 4a15bf2..615fe23 100644
--- a/scripts/pull-news.mjs
+++ b/scripts/pull-news.mjs
@@ -4,8 +4,12 @@
import { writeFileSync, mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
+import { createRequire } from 'node:module';
const HERE = dirname(fileURLToPath(import.meta.url));
const OUT = join(HERE, '..', 'data'); mkdirSync(OUT, { recursive: true });
+// Localize every rentv.com property photo into public/img/news/ so the SITE serves it —
+// the browser never hotlinks rentv.com. Shared with server.js's article proxy.
+const { localizeImage } = createRequire(import.meta.url)('./lib/localize.cjs');
async function fetchDecoded(url) {
const r = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 RENTV-v1' }, signal: AbortSignal.timeout(20000) });
@@ -38,6 +42,9 @@ try {
: 'Sales';
items.push({ id, title, url: m[1].startsWith('http') ? m[1] : 'https://www.rentv.com/' + m[1].replace(/^\//,''), cat, image: imgMap[id] || '' });
}
+ // Download each remote photo locally and rewrite image → /img/news/<file>. On any
+ // download failure the field becomes '' (gradient placeholder) — never a leaked rentv.com URL.
+ for (const it of items) { it.image = await localizeImage(it.image); }
const payload = { source: 'rentv.com — live', fetched_at: new Date().toISOString(), count: items.length, items };
writeFileSync(join(OUT, 'news.json'), JSON.stringify(payload, null, 1));
console.log('OK: pulled ' + items.length + ' live headlines');
diff --git a/server.js b/server.js
index 99ce2dc..a23ecd1 100644
--- a/server.js
+++ b/server.js
@@ -4,6 +4,7 @@
// reader proxy (/api/article/:id) so stories are read INSIDE our modern shell.
const fs = require('fs');
const path = require('path');
+const { localizeImage } = require('./scripts/lib/localize.cjs'); // never hotlink rentv.com photos
const express = require('express');
const app = express();
app.use(express.json({ limit: '16kb' }));
@@ -197,6 +198,8 @@ async function fetchArticle(id) {
const imM = html.match(/["'](\/?(?:https?:\/\/[^"']*)?\/?images\/dynamic\/news_\d+_[^"']+\.(?:jpe?g|png))["']/i);
let image = imM ? imM[1] : '';
if (image && !/^https?:/i.test(image)) image = 'https://www.rentv.com/' + image.replace(/^\//, '');
+ // Copy the photo locally and serve it ourselves — the reader's browser never fetches rentv.com.
+ image = await localizeImage(image);
// Body: take the region from the headline to a trailing marker, split on <br><br> into real paragraphs.
const start = hM ? hM.index + hM[0].length : 0;
← 2149153 index: honor ?cat= so each hamburger section shows its own f
·
back to Rentv V1
·
auth: add Daniel viewer login b41691e →