japan-seasons-mcp 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +67 -0
- package/.github/workflows/scrape-fruit-farms.yml +62 -0
- package/README.md +220 -0
- package/dist/api.d.ts +2 -0
- package/dist/api.js +148 -0
- package/dist/api.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +471 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/areas.d.ts +11 -0
- package/dist/lib/areas.js +146 -0
- package/dist/lib/areas.js.map +1 -0
- package/dist/lib/cache.d.ts +19 -0
- package/dist/lib/cache.js +89 -0
- package/dist/lib/cache.js.map +1 -0
- package/dist/lib/fetch.d.ts +1 -0
- package/dist/lib/fetch.js +106 -0
- package/dist/lib/fetch.js.map +1 -0
- package/dist/lib/koyo.d.ts +54 -0
- package/dist/lib/koyo.js +175 -0
- package/dist/lib/koyo.js.map +1 -0
- package/dist/lib/logger.d.ts +5 -0
- package/dist/lib/logger.js +7 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/romaji.d.ts +6 -0
- package/dist/lib/romaji.js +130 -0
- package/dist/lib/romaji.js.map +1 -0
- package/dist/lib/sakura-forecast.d.ts +96 -0
- package/dist/lib/sakura-forecast.js +431 -0
- package/dist/lib/sakura-forecast.js.map +1 -0
- package/dist/lib/weather.d.ts +43 -0
- package/dist/lib/weather.js +68 -0
- package/dist/lib/weather.js.map +1 -0
- package/package.json +58 -0
- package/playwright-verify.cjs +291 -0
- package/playwright-verify.py +334 -0
- package/public/flowers.json +398 -0
- package/public/fruit-farms.json +4210 -0
- package/public/index.html +1864 -0
- package/scrape-fruit-farms.py +225 -0
- package/scrape-jalan.py +156 -0
- package/scrape-navitime-explore.py +85 -0
- package/scrape-pagination-explore.py +56 -0
- package/test-audit.py +201 -0
- package/test-popups.js +99 -0
- package/test-popups.py +112 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
schedule:
|
|
9
|
+
# Run daily at 9 AM JST (midnight UTC) to detect upstream API breakage
|
|
10
|
+
- cron: "0 0 * * *"
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 22
|
|
20
|
+
- run: npm ci
|
|
21
|
+
- run: npm run build
|
|
22
|
+
|
|
23
|
+
api-health:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
steps:
|
|
26
|
+
- name: Check n-kishou sakura API
|
|
27
|
+
run: |
|
|
28
|
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://other-api-prod.n-kishou.co.jp/get-sakura-hw")
|
|
29
|
+
echo "Sakura API: $STATUS"
|
|
30
|
+
[ "$STATUS" = "200" ] || exit 1
|
|
31
|
+
|
|
32
|
+
- name: Check n-kishou spots API
|
|
33
|
+
run: |
|
|
34
|
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://other-api-prod.n-kishou.co.jp/list-jr-points?type=sakura&filter_mode=forecast&area_mode=pref&area_code=13&sort_code=0")
|
|
35
|
+
echo "Spots API: $STATUS"
|
|
36
|
+
[ "$STATUS" = "200" ] || exit 1
|
|
37
|
+
|
|
38
|
+
- name: Check koyo forecast list
|
|
39
|
+
run: |
|
|
40
|
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://tennavi-data-prod.n-kishou.co.jp/koyo/koyo_jma_forecast_list.json")
|
|
41
|
+
echo "Koyo forecast: $STATUS"
|
|
42
|
+
[ "$STATUS" = "200" ] || exit 1
|
|
43
|
+
|
|
44
|
+
- name: Check Kawazu cherry data
|
|
45
|
+
run: |
|
|
46
|
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://tennavi-data-prod.n-kishou.co.jp/sakura/sakura_forecast_kawazu_list.json")
|
|
47
|
+
echo "Kawazu data: $STATUS"
|
|
48
|
+
[ "$STATUS" = "200" ] || exit 1
|
|
49
|
+
|
|
50
|
+
- name: Check weather API
|
|
51
|
+
run: |
|
|
52
|
+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://weather.tsukumijima.net/api/forecast/city/130010")
|
|
53
|
+
echo "Weather API: $STATUS"
|
|
54
|
+
[ "$STATUS" = "200" ] || exit 1
|
|
55
|
+
|
|
56
|
+
- name: Verify sakura API response structure
|
|
57
|
+
run: |
|
|
58
|
+
curl -s "https://other-api-prod.n-kishou.co.jp/get-sakura-hw" | python3 -c "
|
|
59
|
+
import json, sys
|
|
60
|
+
data = json.load(sys.stdin)
|
|
61
|
+
regions = data['result_list']['region']
|
|
62
|
+
assert len(regions) > 0, 'No regions returned'
|
|
63
|
+
sample = regions[0]['sample'][0]
|
|
64
|
+
assert 'bloom' in sample, 'Missing bloom data'
|
|
65
|
+
assert 'full' in sample, 'Missing full bloom data'
|
|
66
|
+
print(f'OK: {len(regions)} regions, structure valid')
|
|
67
|
+
"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Scrape Fruit Farm Spots
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
# Every Monday at 2am JST (Sunday 17:00 UTC)
|
|
6
|
+
- cron: '0 17 * * 0'
|
|
7
|
+
workflow_dispatch: # allow manual trigger
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
scrape:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: '3.12'
|
|
19
|
+
|
|
20
|
+
- name: Install Playwright
|
|
21
|
+
run: |
|
|
22
|
+
pip install playwright requests
|
|
23
|
+
playwright install chromium
|
|
24
|
+
|
|
25
|
+
- name: Scrape Navitime (base data)
|
|
26
|
+
run: python3 scrape-fruit-farms.py
|
|
27
|
+
timeout-minutes: 15
|
|
28
|
+
|
|
29
|
+
- name: Scrape Jalan (merge with existing)
|
|
30
|
+
run: python3 scrape-jalan.py
|
|
31
|
+
timeout-minutes: 30
|
|
32
|
+
|
|
33
|
+
- name: Validate output (fail fast if scrape broke)
|
|
34
|
+
run: |
|
|
35
|
+
python3 -c "
|
|
36
|
+
import json, sys
|
|
37
|
+
d = json.load(open('public/fruit-farms.json'))
|
|
38
|
+
total = d.get('total', 0)
|
|
39
|
+
coords = d.get('with_coords', 0)
|
|
40
|
+
print(f'Total spots: {total}, with coords: {coords}')
|
|
41
|
+
if total < 50:
|
|
42
|
+
print('ERROR: Too few spots — scraper likely broke or was blocked')
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
if coords < 30:
|
|
45
|
+
print('ERROR: Too few coordinates — coordinate extraction broke')
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
print('Validation passed')
|
|
48
|
+
"
|
|
49
|
+
|
|
50
|
+
- name: Check if data changed
|
|
51
|
+
id: check
|
|
52
|
+
run: |
|
|
53
|
+
git diff --quiet public/fruit-farms.json && echo "changed=false" >> $GITHUB_OUTPUT || echo "changed=true" >> $GITHUB_OUTPUT
|
|
54
|
+
|
|
55
|
+
- name: Commit updated farm data
|
|
56
|
+
if: steps.check.outputs.changed == 'true'
|
|
57
|
+
run: |
|
|
58
|
+
git config user.name "github-actions[bot]"
|
|
59
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
60
|
+
git add public/fruit-farms.json
|
|
61
|
+
git commit -m "chore: update fruit farm spots (weekly scrape)"
|
|
62
|
+
git push
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">japan-sakura-koyo-mcp</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
Real-time cherry blossom & autumn leaves forecast for Japan.<br>
|
|
5
|
+
1,700+ spots. Live bloom data. Built for AI assistants.
|
|
6
|
+
</p>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/japan-sakura-koyo-mcp"><img src="https://img.shields.io/npm/v/japan-sakura-koyo-mcp" alt="npm"></a>
|
|
11
|
+
<a href="https://github.com/haomingkoo/japan-sakura-koyo-mcp/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="license"></a>
|
|
12
|
+
<a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-compatible-brightgreen" alt="MCP"></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
**Try it live:** [sakura.kooexperience.com](https://sakura.kooexperience.com) — interactive map with all 1,012 spots
|
|
18
|
+
|
|
19
|
+
**The problem:** You ask ChatGPT, Gemini, or Claude *"When should I visit Kyoto for cherry blossoms?"* and get a generic answer based on training data. The actual bloom date shifts by weeks every year depending on temperature.
|
|
20
|
+
|
|
21
|
+
**The fix:** This MCP server gives AI assistants access to **live forecast data** from the Japan Meteorological Corporation — the same source behind Japan's #1 cherry blossom app (SAKURA NAVI). Real dates. Real bloom percentages. Updated daily.
|
|
22
|
+
|
|
23
|
+
## What You Get
|
|
24
|
+
|
|
25
|
+
| Data | Coverage | Updated |
|
|
26
|
+
|------|----------|---------|
|
|
27
|
+
| **Sakura forecast** | 48 JMA observation cities — forecast vs actual bloom dates, historical averages | Daily |
|
|
28
|
+
| **Sakura spots** | **1,012** parks, temples, gardens — bloom %, full bloom %, GPS coordinates | Daily 9AM JST |
|
|
29
|
+
| **Kawazu cherry** | 9 early-bloom spots in Izu Peninsula (Jan-Feb, deep pink variety) | Seasonal |
|
|
30
|
+
| **Koyo forecast** | 50+ cities — maple & ginkgo color change dates vs historical normal | Seasonal |
|
|
31
|
+
| **Koyo spots** | **687** autumn viewing spots — peak window (start/peak/end), popularity rating, GPS | Seasonal |
|
|
32
|
+
| **Weather** | 51 cities — 3-day forecast, temperature, rain probability | Hourly |
|
|
33
|
+
|
|
34
|
+
Every data point comes from a live API call. Nothing is hardcoded or hallucinated.
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Use with Claude Desktop / Claude Code
|
|
39
|
+
|
|
40
|
+
Add to your MCP config:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"japan-sakura-koyo": {
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["japan-sakura-koyo-mcp"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then ask Claude: *"I'm going to Japan April 10-15. Where should I see cherry blossoms?"*
|
|
54
|
+
|
|
55
|
+
### Use with any MCP client (hosted)
|
|
56
|
+
|
|
57
|
+
Deploy and connect via URL:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
PORT=3000 npx japan-sakura-koyo-mcp --http
|
|
61
|
+
# MCP endpoint: http://localhost:3000/mcp
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Deploy to Railway, Fly.io, or Render — any MCP client connects with just a URL.
|
|
65
|
+
|
|
66
|
+
Or use our hosted instance: `https://sakura.kooexperience.com/mcp`
|
|
67
|
+
|
|
68
|
+
## Tools
|
|
69
|
+
|
|
70
|
+
### Cherry Blossom (Sakura)
|
|
71
|
+
|
|
72
|
+
**`get_sakura_forecast`** — Big picture across all of Japan
|
|
73
|
+
```
|
|
74
|
+
"I want to see cherry blossoms in Japan"
|
|
75
|
+
→ Returns 48 cities with bloom status, forecast dates, actual observation dates, historical averages
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**`get_sakura_spots`** — Drill into specific viewing spots
|
|
79
|
+
```
|
|
80
|
+
"Show me cherry blossom spots in Kyoto"
|
|
81
|
+
→ Returns 51 spots: Kiyomizu-dera, Arashiyama, Philosopher's Path... with bloom %, GPS
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**`get_sakura_best_dates`** — Match your travel dates
|
|
85
|
+
```
|
|
86
|
+
"I'm traveling April 10-15"
|
|
87
|
+
→ Returns cities where bloom overlaps your dates, ranked by timing
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**`get_kawazu_cherry`** — Early-season deep pink variety
|
|
91
|
+
```
|
|
92
|
+
"I want to see cherry blossoms in February"
|
|
93
|
+
→ Returns 9 Kawazu cherry spots in Izu Peninsula (blooms Jan-Feb, months before standard sakura)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Autumn Leaves (Koyo)
|
|
97
|
+
|
|
98
|
+
**`get_koyo_forecast`** — Maple & ginkgo timing by city
|
|
99
|
+
```
|
|
100
|
+
"When do autumn leaves peak in Japan?"
|
|
101
|
+
→ Returns 50+ cities with maple/ginkgo dates and comparison to historical normal
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**`get_koyo_spots`** — Famous viewing spots
|
|
105
|
+
```
|
|
106
|
+
"Best autumn leaves spots in Kyoto"
|
|
107
|
+
→ Returns 52 spots: Arashiyama, Eikando, Tofukuji... with peak dates, popularity rating, GPS
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Weather
|
|
111
|
+
|
|
112
|
+
**`get_weather_forecast`** — 3-day forecast for trip planning
|
|
113
|
+
```
|
|
114
|
+
"What's the weather in Osaka?"
|
|
115
|
+
→ Temperature, rain probability (6-hour windows), wind, conditions
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Bloom Scale
|
|
119
|
+
|
|
120
|
+
The official Japan Meteorological Corporation scale, used for all 1,012 sakura spots:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
BLOOM RATE (progress toward first bloom)
|
|
124
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
125
|
+
0% 60% 85% 100%
|
|
126
|
+
| Bud stage | Swelling | Opening | Bloom!
|
|
127
|
+
花芽〜つぼみ 膨らみ始め 開き始め 開花
|
|
128
|
+
|
|
129
|
+
FULL BLOOM RATE (progress toward mankai/満開)
|
|
130
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
131
|
+
0% 20% 40% 70% 90% 100%
|
|
132
|
+
| Open | 30% | 50% | 70% | Full bloom!
|
|
133
|
+
開花 三分咲き 五分咲き 七分咲き 満開
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## How It Works
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
┌──────────────────────────────────────────────┐
|
|
140
|
+
│ Japan Meteorological Corporation APIs │
|
|
141
|
+
│ (n-kishou.co.jp) │
|
|
142
|
+
│ │
|
|
143
|
+
│ get-sakura-hw → 48 city forecasts │
|
|
144
|
+
│ list-jr-points → 1,012 sakura spots │
|
|
145
|
+
│ list-jr-points → 687 koyo spots │
|
|
146
|
+
│ kawazu_info.json → Kawazu cherry data │
|
|
147
|
+
│ koyo_*.json → Autumn leaves forecasts │
|
|
148
|
+
└──────────────┬───────────────────────────────┘
|
|
149
|
+
│ live API calls (cached 1-6 hours)
|
|
150
|
+
│
|
|
151
|
+
┌──────────────▼───────────────────────────────┐
|
|
152
|
+
│ japan-sakura-koyo-mcp │
|
|
153
|
+
│ │
|
|
154
|
+
│ stdio mode: npx japan-sakura-koyo-mcp │
|
|
155
|
+
│ HTTP mode: PORT=3000 ... --http │
|
|
156
|
+
│ │
|
|
157
|
+
│ 7 tools, 1 prompt template │
|
|
158
|
+
└──────────────┬───────────────────────────────┘
|
|
159
|
+
│ MCP protocol
|
|
160
|
+
│
|
|
161
|
+
┌─────────┼─────────┐
|
|
162
|
+
│ │ │
|
|
163
|
+
Claude ChatGPT Gemini
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Season Guide
|
|
167
|
+
|
|
168
|
+
| When | What to See | Tool to Use |
|
|
169
|
+
|------|-------------|-------------|
|
|
170
|
+
| Jan-Feb | Kawazu cherry (deep pink, Izu Peninsula) | `get_kawazu_cherry` |
|
|
171
|
+
| Late Mar | Sakura begins in Kyushu, Shikoku, Kansai | `get_sakura_forecast` |
|
|
172
|
+
| Early Apr | Peak sakura in Tokyo, Osaka, Kyoto | `get_sakura_spots` |
|
|
173
|
+
| Mid Apr | Sakura moves to Tohoku | `get_sakura_best_dates` |
|
|
174
|
+
| Late Apr-May | Sakura reaches Hokkaido | `get_sakura_spots` |
|
|
175
|
+
| Oct-Nov | Autumn leaves begin in Hokkaido, mountains | `get_koyo_forecast` |
|
|
176
|
+
| Nov-Dec | Peak koyo in Kyoto, Tokyo, Kansai | `get_koyo_spots` |
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
git clone https://github.com/haomingkoo/japan-sakura-koyo-mcp.git
|
|
182
|
+
cd japan-sakura-koyo-mcp
|
|
183
|
+
npm install
|
|
184
|
+
npm run build
|
|
185
|
+
npm start # stdio mode
|
|
186
|
+
npm run start:http # HTTP mode on port 3000
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Data Sources
|
|
190
|
+
|
|
191
|
+
- **[Japan Meteorological Corporation](https://n-kishou.com)** (日本気象株式会社) — Sakura & koyo forecasts, bloom rates, 1,700+ viewing spots
|
|
192
|
+
- **[Japan Meteorological Agency](https://www.jma.go.jp)** (気象庁) — Weather forecasts via [tsukumijima API](https://weather.tsukumijima.net)
|
|
193
|
+
|
|
194
|
+
## Web App
|
|
195
|
+
|
|
196
|
+
Visit [sakura.kooexperience.com](https://sakura.kooexperience.com) for the interactive frontend:
|
|
197
|
+
|
|
198
|
+
- **Interactive map** with all 1,012 sakura spots + marker clustering
|
|
199
|
+
- **Lifecycle colors** — orange (bud) → pink (bloom) → green (ended)
|
|
200
|
+
- **Plan My Trip** — type cities you're visiting, find nearby spots with distance
|
|
201
|
+
- **Near Me** — uses your location to find spots within 30km
|
|
202
|
+
- **Kawazu cherry ★ markers** on the map (early-bloom Jan-Feb variety)
|
|
203
|
+
- **Fuzzy search** — find spots even with typos
|
|
204
|
+
- **Romaji names** — all 1,700 spots romanized for international users
|
|
205
|
+
- **Google Maps + Navitime** route links on every spot
|
|
206
|
+
- **Weather forecast** card when viewing prefecture spots
|
|
207
|
+
|
|
208
|
+
## Contributing
|
|
209
|
+
|
|
210
|
+
PRs welcome. Key areas:
|
|
211
|
+
|
|
212
|
+
- **Okinawa sakura** — Uses hikan-zakura (different species), currently not covered by the forecast API.
|
|
213
|
+
- **More data sources** — Plum blossom (ume), wisteria (fuji), sunflower (himawari) seasons.
|
|
214
|
+
- **Historical data** — JMA has bloom records back to 1953.
|
|
215
|
+
- **Multi-select prefectures** — View spots across multiple regions at once.
|
|
216
|
+
- **Nearby attractions** — Overlay famous landmarks near sakura spots.
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT
|
package/dist/api.d.ts
ADDED
package/dist/api.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { getSakuraForecast, getSakuraSpots, getKawazuForecast, findCities, findBestRegions, findPrefCode, } from "./lib/sakura-forecast.js";
|
|
4
|
+
import { getKoyoForecast, getKoyoSpots } from "./lib/koyo.js";
|
|
5
|
+
import { getWeatherForecast } from "./lib/weather.js";
|
|
6
|
+
function json(res, data, status = 200) {
|
|
7
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
8
|
+
res.end(JSON.stringify(data));
|
|
9
|
+
}
|
|
10
|
+
function error(res, msg, status = 400) {
|
|
11
|
+
json(res, { error: msg }, status);
|
|
12
|
+
}
|
|
13
|
+
export async function handleApiRequest(req, res, pathname, params) {
|
|
14
|
+
try {
|
|
15
|
+
// GET /api/sakura/forecast?city=Tokyo
|
|
16
|
+
if (pathname === "/api/sakura/forecast") {
|
|
17
|
+
const forecast = await getSakuraForecast();
|
|
18
|
+
const city = params.get("city");
|
|
19
|
+
if (city) {
|
|
20
|
+
const cities = findCities(forecast, city);
|
|
21
|
+
json(res, { cities });
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
json(res, forecast);
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
// GET /api/sakura/spots?pref=Tokyo (or pref=13)
|
|
29
|
+
if (pathname === "/api/sakura/spots") {
|
|
30
|
+
const pref = params.get("pref");
|
|
31
|
+
if (!pref) {
|
|
32
|
+
error(res, "Missing ?pref= parameter");
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const prefCode = findPrefCode(pref);
|
|
36
|
+
if (!prefCode) {
|
|
37
|
+
error(res, `Prefecture "${pref}" not found`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
const spots = await getSakuraSpots(prefCode);
|
|
41
|
+
json(res, spots);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
// GET /api/sakura/best?start=2026-04-10&end=2026-04-15
|
|
45
|
+
if (pathname === "/api/sakura/best") {
|
|
46
|
+
const start = params.get("start");
|
|
47
|
+
const end = params.get("end");
|
|
48
|
+
if (!start || !end) {
|
|
49
|
+
error(res, "Missing ?start= and ?end= parameters");
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
const startDate = new Date(start);
|
|
53
|
+
const endDate = new Date(end);
|
|
54
|
+
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
55
|
+
error(res, "Invalid date format. Use YYYY-MM-DD.");
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const forecast = await getSakuraForecast();
|
|
59
|
+
const matches = findBestRegions(forecast, startDate, endDate);
|
|
60
|
+
json(res, { start, end, matches });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// GET /api/sakura/all-spots — load all 1,012 spots across Japan
|
|
64
|
+
if (pathname === "/api/sakura/all-spots") {
|
|
65
|
+
const allSpots = [];
|
|
66
|
+
const prefCodes = Array.from({ length: 47 }, (_, i) => String(i + 1).padStart(2, "0"));
|
|
67
|
+
const results = await Promise.allSettled(prefCodes.map(code => getSakuraSpots(code)));
|
|
68
|
+
for (const r of results) {
|
|
69
|
+
if (r.status === "fulfilled" && r.value.spots) {
|
|
70
|
+
allSpots.push(...r.value.spots);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
json(res, { totalSpots: allSpots.length, spots: allSpots });
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
// GET /api/kawazu
|
|
77
|
+
if (pathname === "/api/kawazu") {
|
|
78
|
+
const data = await getKawazuForecast();
|
|
79
|
+
json(res, data);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
// GET /api/koyo/forecast
|
|
83
|
+
if (pathname === "/api/koyo/forecast") {
|
|
84
|
+
const data = await getKoyoForecast();
|
|
85
|
+
json(res, data);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
// GET /api/koyo/spots?pref=Kyoto
|
|
89
|
+
if (pathname === "/api/koyo/spots") {
|
|
90
|
+
const pref = params.get("pref");
|
|
91
|
+
if (!pref) {
|
|
92
|
+
error(res, "Missing ?pref= parameter");
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
const prefCode = findPrefCode(pref);
|
|
96
|
+
if (!prefCode) {
|
|
97
|
+
error(res, `Prefecture "${pref}" not found`);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
const spots = await getKoyoSpots(prefCode);
|
|
101
|
+
json(res, spots);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
// GET /api/fruit/farms — serve cached Navitime farm data
|
|
105
|
+
if (pathname === "/api/fruit/farms") {
|
|
106
|
+
try {
|
|
107
|
+
const farmsPath = resolve(process.cwd(), "public/fruit-farms.json");
|
|
108
|
+
const raw = readFileSync(farmsPath, "utf-8");
|
|
109
|
+
const data = JSON.parse(raw);
|
|
110
|
+
json(res, data);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
json(res, { spots: [], scraped_at: null, total: 0, error: "Farm data not yet available. Run scrape-fruit-farms.py to populate." });
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
// GET /api/flowers — serve curated seasonal flower spots (wisteria, hydrangea, etc.)
|
|
118
|
+
if (pathname === "/api/flowers") {
|
|
119
|
+
try {
|
|
120
|
+
const flowersPath = resolve(process.cwd(), "public/flowers.json");
|
|
121
|
+
const raw = readFileSync(flowersPath, "utf-8");
|
|
122
|
+
const data = JSON.parse(raw);
|
|
123
|
+
json(res, data);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
json(res, { spots: [], total: 0, error: "Flowers data not available." });
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// GET /api/weather?city=Tokyo
|
|
131
|
+
if (pathname === "/api/weather") {
|
|
132
|
+
const city = params.get("city");
|
|
133
|
+
if (!city) {
|
|
134
|
+
error(res, "Missing ?city= parameter");
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
const data = await getWeatherForecast(city);
|
|
138
|
+
json(res, data);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false; // not an API route
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
error(res, e.message, 500);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED