Skip to content

Commit d1c5bf5

Browse files
committedJan 27, 2026
first commit
0 parents  commit d1c5bf5

File tree

9 files changed

+892
-0
lines changed

9 files changed

+892
-0
lines changed
 

‎.gitignore‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
venv/
8+
env/
9+
.env
10+
.env.local
11+
.env.*.local
12+
13+
# Node.js / pnpm
14+
node_modules/
15+
.pnpm/
16+
.pnpm-store/
17+
npm-debug.log*
18+
yarn-debug.log*
19+
yarn-error.log*
20+
pnpm-debug.log*
21+
lerna-debug.log*
22+
23+
# IDE
24+
.vscode/
25+
.idea/
26+
*.swp
27+
*.swo
28+
*.sublime-project
29+
*.sublime-workspace
30+
31+
# OS
32+
.DS_Store
33+
.DS_Store?
34+
._*
35+
.Spotlight-V100
36+
.Trashes
37+
ehthumbs.db
38+
Thumbs.db
39+
Desktop.ini
40+
41+
# Logs
42+
*.log
43+
logs/
44+
45+
# Temporary files
46+
*.tmp
47+
*.temp
48+
.cache/

‎README.md‎

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Events API
2+
3+
Notion データベースからイベント情報を取得し、JSON API として公開するプロジェクトです。
4+
5+
## 概要
6+
7+
- **定期実行**: GitHub Actions により5分おきに自動実行
8+
- **データソース**: Notion API 経由でイベントデータベースから取得
9+
- **フィルタリング**: 日本時間(JST)の現在時刻以降のイベントのみを抽出
10+
- **出力**: `data/events.json` として保存・公開
11+
12+
## API エンドポイント
13+
14+
JSON データは以下の URL から取得可能です:
15+
16+
### GitHub Raw URL(推奨)
17+
```
18+
https://raw.githubusercontent.com/{owner}/{repo}/main/events/data/events.json
19+
```
20+
21+
### GitHub Pages(設定した場合)
22+
```
23+
https://{owner}.github.io/{repo}/events/data/events.json
24+
```
25+
26+
## セットアップ
27+
28+
### 1. Notion Integration の作成
29+
30+
1. [Notion Developers](https://www.notion.so/my-integrations) にアクセス
31+
2. 「New integration」をクリック
32+
3. Integration の名前を設定(例: "Events API")
33+
4. 「Submit」をクリック
34+
5. 表示される「Internal Integration Token」をコピー
35+
36+
### 2. Notion データベースの準備
37+
38+
1. イベント用のデータベースを作成(または既存のものを使用)
39+
2. 以下のプロパティを設定(名前は調整可能):
40+
- `Name` または `Title`: タイトル(タイトル型)
41+
- `Date`: 日時(日付型)
42+
- `Location` または `場所`: 場所(テキスト型)
43+
- `Description` または `説明`: 説明(テキスト型)
44+
- `URL` または `Link`: リンク(URL型)
45+
- `Category` または `カテゴリ`: カテゴリ(セレクト型)
46+
47+
3. データベースに Integration を接続:
48+
- データベースページ右上の「...」をクリック
49+
- 「Connections」→「Add connections」
50+
- 作成した Integration を選択
51+
52+
4. データベース ID を取得:
53+
- データベースページの URL から ID を取得
54+
- `https://www.notion.so/{workspace}/{database_id}?v=...`
55+
56+
### 3. GitHub Secrets の設定
57+
58+
リポジトリの Settings → Secrets and variables → Actions で以下を設定:
59+
60+
| Secret 名 | 説明 |
61+
|-----------|------|
62+
| `NOTION_API_TOKEN` | Notion Integration Token |
63+
| `NOTION_DATABASE_ID` | Notion データベースの ID |
64+
65+
### 4. ワークフローの有効化
66+
67+
1. このディレクトリをリポジトリにプッシュ
68+
2. GitHub Actions タブでワークフローを有効化
69+
3. 手動実行でテスト: Actions → "Fetch Events from Notion" → "Run workflow"
70+
71+
## JSON 出力形式
72+
73+
```json
74+
{
75+
"generated_at": "2024-01-15T10:30:00+09:00",
76+
"timezone": "Asia/Tokyo",
77+
"total_count": 5,
78+
"events": [
79+
{
80+
"id": "page-id-xxx",
81+
"title": "イベント名",
82+
"date_start": "2024-01-20T14:00:00+09:00",
83+
"date_end": "2024-01-20T17:00:00+09:00",
84+
"location": "東京都渋谷区...",
85+
"description": "イベントの説明...",
86+
"url": "https://example.com/event",
87+
"category": "セミナー",
88+
"notion_url": "https://www.notion.so/...",
89+
"last_edited": "2024-01-10T12:00:00.000Z"
90+
}
91+
]
92+
}
93+
```
94+
95+
## フロントエンドでの利用例
96+
97+
### JavaScript (fetch)
98+
99+
```javascript
100+
async function fetchEvents() {
101+
const response = await fetch(
102+
'https://raw.githubusercontent.com/{owner}/{repo}/main/events/data/events.json'
103+
);
104+
const data = await response.json();
105+
return data.events;
106+
}
107+
```
108+
109+
### React/Next.js
110+
111+
```typescript
112+
const [events, setEvents] = useState([]);
113+
114+
useEffect(() => {
115+
fetch('https://raw.githubusercontent.com/{owner}/{repo}/main/events/data/events.json')
116+
.then(res => res.json())
117+
.then(data => setEvents(data.events));
118+
}, []);
119+
```
120+
121+
## カスタマイズ
122+
123+
### プロパティ名の変更
124+
125+
`fetch_events.py``parse_event()` 関数内で、実際の Notion データベースのプロパティ名に合わせて調整してください:
126+
127+
```python
128+
# 例: "イベント名" というプロパティ名を使う場合
129+
title_prop = properties.get("イベント名")
130+
```
131+
132+
### 追加のプロパティ取得
133+
134+
新しいフィールドを追加する場合は、`parse_event()` 関数に追加処理を記述してください。
135+
136+
## トラブルシューティング
137+
138+
### API Token エラー
139+
- Secrets が正しく設定されているか確認
140+
- Token に余分なスペースがないか確認
141+
142+
### データベースアクセスエラー
143+
- Integration がデータベースに接続されているか確認
144+
- データベース ID が正しいか確認
145+
146+
### 日付フィルタリングが効かない
147+
- データベースの日付プロパティ名が "Date" か確認
148+
- 異なる場合は `fetch_events.py` を修正
149+
150+
## ライセンス
151+
152+
MIT License

‎SCHEMA.md‎

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Events JSON API スキーマ
2+
3+
## エンドポイント
4+
5+
```
6+
https://raw.githubusercontent.com/{owner}/{repo}/main/events/data/events.json
7+
```
8+
9+
## レスポンス形式
10+
11+
### ルートオブジェクト
12+
13+
```json
14+
{
15+
"generated_at": "2024-01-15T10:30:00+09:00",
16+
"timezone": "Asia/Tokyo",
17+
"total_count": 5,
18+
"events": [...]
19+
}
20+
```
21+
22+
| フィールド || 説明 |
23+
|-----------|-----|------|
24+
| `generated_at` | string (ISO 8601) | JSON生成日時(JST) |
25+
| `timezone` | string | タイムゾーン(常に "Asia/Tokyo") |
26+
| `total_count` | number | イベントの総数 |
27+
| `events` | array | イベントオブジェクトの配列 |
28+
29+
### イベントオブジェクト
30+
31+
```json
32+
{
33+
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
34+
"name": "街頭演説",
35+
"date": {
36+
"start": "2026-01-29",
37+
"end": null
38+
},
39+
"location": "福岡",
40+
"time": "午後",
41+
"live_stream_url": "",
42+
"description": "古川 あおいアカウントよりご確認ください\nhttps://x.com/AoiFurukawa\nhttps://www.instagram.com/aoi.furukawa/",
43+
"notion_url": "https://www.notion.so/...",
44+
"created_time": "2024-01-10T12:00:00.000Z",
45+
"last_edited_time": "2024-01-15T10:00:00.000Z"
46+
}
47+
```
48+
49+
| フィールド || 必須 | 説明 |
50+
|-----------|-----|------|------|
51+
| `id` | string || NotionページID |
52+
| `name` | string || イベント名(Notionの「名前」プロパティ) |
53+
| `date` | object || 日付情報 |
54+
| `date.start` | string \| null || 開始日時(ISO 8601形式、または日付のみ "YYYY-MM-DD") |
55+
| `date.end` | string \| null || 終了日時(ISO 8601形式、または日付のみ "YYYY-MM-DD")。単日イベントの場合は `null` |
56+
| `location` | string || 場所(Notionの「場所」プロパティ)。空文字列の場合あり |
57+
| `time` | string || 時間帯(Notionの「時間」プロパティ)。空文字列の場合あり |
58+
| `live_stream_url` | string || Live配信URL(Notionの「Live配信」プロパティ)。空文字列の場合あり |
59+
| `description` | string || 詳細説明(Notionの「詳細」プロパティ)。改行文字 `\n` を含む場合あり |
60+
| `notion_url` | string || NotionページのURL |
61+
| `created_time` | string || 作成日時(ISO 8601 UTC) |
62+
| `last_edited_time` | string || 最終更新日時(ISO 8601 UTC) |
63+
64+
## 使用例
65+
66+
### JavaScript (fetch)
67+
68+
```javascript
69+
async function fetchEvents() {
70+
const response = await fetch(
71+
'https://raw.githubusercontent.com/{owner}/{repo}/main/events/data/events.json'
72+
);
73+
const data = await response.json();
74+
75+
console.log(`取得件数: ${data.total_count}`);
76+
console.log(`生成日時: ${data.generated_at}`);
77+
78+
data.events.forEach(event => {
79+
console.log(`${event.name} - ${event.date.start} @ ${event.location}`);
80+
});
81+
82+
return data.events;
83+
}
84+
```
85+
86+
### TypeScript 型定義
87+
88+
```typescript
89+
interface EventDate {
90+
start: string | null;
91+
end: string | null;
92+
}
93+
94+
interface Event {
95+
id: string;
96+
name: string;
97+
date: EventDate;
98+
location: string;
99+
time: string;
100+
live_stream_url: string;
101+
description: string;
102+
notion_url: string;
103+
created_time: string;
104+
last_edited_time: string;
105+
}
106+
107+
interface EventsResponse {
108+
generated_at: string;
109+
timezone: string;
110+
total_count: number;
111+
events: Event[];
112+
}
113+
```
114+
115+
### React コンポーネント例
116+
117+
```tsx
118+
import { useEffect, useState } from 'react';
119+
120+
function EventsList() {
121+
const [events, setEvents] = useState<Event[]>([]);
122+
const [loading, setLoading] = useState(true);
123+
124+
useEffect(() => {
125+
fetch('https://raw.githubusercontent.com/{owner}/{repo}/main/events/data/events.json')
126+
.then(res => res.json())
127+
.then(data => {
128+
setEvents(data.events);
129+
setLoading(false);
130+
});
131+
}, []);
132+
133+
if (loading) return <div>読み込み中...</div>;
134+
135+
return (
136+
<ul>
137+
{events.map(event => (
138+
<li key={event.id}>
139+
<h3>{event.name}</h3>
140+
<p>{event.date.start} @ {event.location}</p>
141+
{event.live_stream_url && (
142+
<a href={event.live_stream_url}>Live配信を見る</a>
143+
)}
144+
</li>
145+
))}
146+
</ul>
147+
);
148+
}
149+
```
150+
151+
## フィルタリング
152+
153+
- このAPIは**現在時刻(JST)以降のイベントのみ**を返します
154+
- 過去のイベントは自動的に除外されます
155+
- イベントは日付の昇順(古い順)でソートされています
156+
157+
## 更新頻度
158+
159+
- GitHub Actionsにより**5分おき**に自動更新されます
160+
- `generated_at` フィールドで最終更新時刻を確認できます

‎data/events.json‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"generated_at": null,
3+
"timezone": "Asia/Tokyo",
4+
"total_count": 0,
5+
"events": [],
6+
"_comment": "This file will be automatically updated by GitHub Actions"
7+
}

‎fetch_events.py‎

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Notion APIからイベント情報を取得し、現在時刻以降のイベントをJSONとして保存するスクリプト
4+
"""
5+
6+
import json
7+
import os
8+
from datetime import datetime, timezone, timedelta
9+
from notion_client import Client
10+
11+
# 日本時間のタイムゾーン
12+
JST = timezone(timedelta(hours=9))
13+
14+
15+
def get_notion_client():
16+
"""Notion APIクライアントを初期化"""
17+
token = os.environ.get("NOTION_API_TOKEN")
18+
if not token:
19+
raise ValueError("NOTION_API_TOKEN environment variable is not set")
20+
return Client(auth=token)
21+
22+
23+
def fetch_events_from_notion(notion: Client, database_id: str) -> list:
24+
"""
25+
NotionデータベースからJSTの現在時刻以降のイベントのみ取得
26+
"""
27+
now_jst = datetime.now(JST)
28+
29+
# 今日の日付(YYYY-MM-DD形式)を取得
30+
today_date = now_jst.date().isoformat()
31+
32+
# Notion APIでフィルタリング(日付プロパティ名: "日付")
33+
# 今日以降の日付のイベントを取得
34+
response = notion.databases.query(
35+
database_id=database_id,
36+
filter={
37+
"property": "日付",
38+
"date": {
39+
"on_or_after": today_date
40+
}
41+
},
42+
sorts=[
43+
{
44+
"property": "日付",
45+
"direction": "ascending"
46+
}
47+
]
48+
)
49+
50+
results = response.get("results", [])
51+
52+
# ページネーション対応(100件以上の場合)
53+
while response.get("has_more"):
54+
response = notion.databases.query(
55+
database_id=database_id,
56+
filter={
57+
"property": "日付",
58+
"date": {
59+
"on_or_after": today_date
60+
}
61+
},
62+
sorts=[
63+
{
64+
"property": "日付",
65+
"direction": "ascending"
66+
}
67+
],
68+
start_cursor=response.get("next_cursor")
69+
)
70+
results.extend(response.get("results", []))
71+
72+
return results
73+
74+
75+
def parse_event(page: dict) -> dict:
76+
"""
77+
Notionページデータをイベントオブジェクトに変換
78+
実際のプロパティ名に基づいて実装
79+
"""
80+
properties = page.get("properties", {})
81+
82+
# 名前(タイトル)の取得
83+
name = ""
84+
name_prop = properties.get("名前")
85+
if name_prop and name_prop.get("title"):
86+
name = "".join([t.get("plain_text", "") for t in name_prop["title"]])
87+
88+
# 日付の取得
89+
date_start = None
90+
date_end = None
91+
date_prop = properties.get("日付")
92+
if date_prop and date_prop.get("date"):
93+
date_data = date_prop["date"]
94+
date_start = date_data.get("start")
95+
date_end = date_data.get("end")
96+
97+
# 場所の取得
98+
location = ""
99+
location_prop = properties.get("場所")
100+
if location_prop and location_prop.get("rich_text"):
101+
location = "".join([t.get("plain_text", "") for t in location_prop["rich_text"]])
102+
103+
# 時間の取得
104+
time = ""
105+
time_prop = properties.get("時間")
106+
if time_prop and time_prop.get("rich_text"):
107+
time = "".join([t.get("plain_text", "") for t in time_prop["rich_text"]])
108+
109+
# Live配信(URL)の取得
110+
live_stream_url = ""
111+
live_prop = properties.get("Live配信")
112+
if live_prop:
113+
if live_prop.get("url"):
114+
live_stream_url = live_prop["url"]
115+
elif live_prop.get("rich_text"):
116+
live_stream_url = "".join([t.get("plain_text", "") for t in live_prop["rich_text"]])
117+
118+
# 詳細の取得
119+
description = ""
120+
detail_prop = properties.get("詳細")
121+
if detail_prop and detail_prop.get("rich_text"):
122+
description = "".join([t.get("plain_text", "") for t in detail_prop["rich_text"]])
123+
124+
return {
125+
"id": page.get("id", ""),
126+
"name": name,
127+
"date": {
128+
"start": date_start,
129+
"end": date_end
130+
},
131+
"location": location,
132+
"time": time,
133+
"live_stream_url": live_stream_url,
134+
"description": description,
135+
"notion_url": page.get("url", ""),
136+
"created_time": page.get("created_time", ""),
137+
"last_edited_time": page.get("last_edited_time", "")
138+
}
139+
140+
141+
def filter_future_events(events: list, now_jst: datetime) -> list:
142+
"""
143+
現在時刻以降のイベントのみをフィルタリング
144+
日付のみの場合は今日の日付なら全て含める(時刻情報がないため)
145+
"""
146+
filtered = []
147+
148+
for event in events:
149+
date_start = event.get("date", {}).get("start")
150+
if not date_start:
151+
continue
152+
153+
# 日付文字列をパース
154+
try:
155+
# ISO形式(時刻含む)の場合
156+
if "T" in date_start:
157+
event_datetime = datetime.fromisoformat(date_start.replace("Z", "+00:00"))
158+
# JSTに変換(UTCの場合は)
159+
if event_datetime.tzinfo is None:
160+
event_datetime = event_datetime.replace(tzinfo=JST)
161+
elif event_datetime.tzinfo.utcoffset(event_datetime).total_seconds() == 0:
162+
event_datetime = event_datetime.replace(tzinfo=timezone.utc).astimezone(JST)
163+
# 現在時刻以降かチェック
164+
if event_datetime >= now_jst:
165+
filtered.append(event)
166+
else:
167+
# 日付のみ(YYYY-MM-DD)の場合
168+
event_date = datetime.strptime(date_start, "%Y-%m-%d").date()
169+
today_date = now_jst.date()
170+
# 今日以降の日付なら含める
171+
if event_date >= today_date:
172+
filtered.append(event)
173+
except (ValueError, AttributeError) as e:
174+
print(f"Warning: Could not parse date '{date_start}': {e}")
175+
continue
176+
177+
return filtered
178+
179+
180+
def main():
181+
"""メイン処理"""
182+
# 環境変数からデータベースIDを取得
183+
database_id = os.environ.get("NOTION_DATABASE_ID")
184+
if not database_id:
185+
raise ValueError("NOTION_DATABASE_ID environment variable is not set")
186+
187+
# Notionクライアントを初期化
188+
notion = get_notion_client()
189+
190+
# 現在時刻(JST)を取得
191+
now_jst = datetime.now(JST)
192+
193+
# イベントを取得
194+
print(f"Fetching events from Notion database: {database_id}")
195+
print(f"Current time (JST): {now_jst.isoformat()}")
196+
raw_events = fetch_events_from_notion(notion, database_id)
197+
print(f"Found {len(raw_events)} events from today onwards")
198+
199+
# イベントをパース
200+
parsed_events = [parse_event(page) for page in raw_events]
201+
202+
# 現在時刻以降のイベントのみをフィルタリング
203+
events = filter_future_events(parsed_events, now_jst)
204+
print(f"Filtered to {len(events)} events after current time")
205+
206+
# 出力データを作成
207+
output = {
208+
"generated_at": now_jst.isoformat(),
209+
"timezone": "Asia/Tokyo",
210+
"total_count": len(events),
211+
"events": events
212+
}
213+
214+
# JSONファイルに保存
215+
output_path = "data/events.json"
216+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
217+
218+
with open(output_path, "w", encoding="utf-8") as f:
219+
json.dump(output, f, ensure_ascii=False, indent=2)
220+
221+
print(f"Events saved to {output_path}")
222+
print(json.dumps(output, ensure_ascii=False, indent=2))
223+
224+
225+
if __name__ == "__main__":
226+
main()

‎package.json‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "events-api",
3+
"version": "1.0.0",
4+
"description": "Notion からイベント情報を取得して JSON API として公開",
5+
"scripts": {
6+
"test": "node test_notion.js"
7+
},
8+
"dependencies": {
9+
"@notionhq/client": "^2.2.15"
10+
}
11+
}

‎pnpm-lock.yaml‎

Lines changed: 239 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎requirements.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
notion-client>=2.0.0

‎workflows/fetch_events.yml‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Fetch Events from Notion
2+
3+
on:
4+
schedule:
5+
# 5分おきに実行(UTC)
6+
- cron: '*/5 * * * *'
7+
workflow_dispatch: # 手動実行も可能
8+
9+
permissions:
10+
contents: write
11+
12+
jobs:
13+
fetch-events:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: '3.11'
24+
25+
- name: Install dependencies
26+
run: |
27+
python -m pip install --upgrade pip
28+
pip install -r requirements.txt
29+
30+
- name: Fetch events from Notion
31+
env:
32+
NOTION_API_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
33+
NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }}
34+
run: python fetch_events.py
35+
36+
- name: Check for changes
37+
id: check_changes
38+
run: |
39+
git diff --quiet data/events.json || echo "changed=true" >> $GITHUB_OUTPUT
40+
41+
- name: Commit and push changes
42+
if: steps.check_changes.outputs.changed == 'true'
43+
run: |
44+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
45+
git config --local user.name "github-actions[bot]"
46+
git add data/events.json
47+
git commit -m "Update events data [skip ci]"
48+
git push

0 commit comments

Comments
 (0)
Please sign in to comment.