@@ -17,7 +17,6 @@ interface ProjectData {
1717 } > ;
1818}
1919
20- // og:title を書き換える HTMLRewriter
2120class OGTitleRewriter {
2221 private title : string ;
2322
@@ -32,7 +31,6 @@ class OGTitleRewriter {
3231 }
3332}
3433
35- // og:description を書き換える HTMLRewriter
3634class OGDescriptionRewriter {
3735 private description : string ;
3836
@@ -47,7 +45,20 @@ class OGDescriptionRewriter {
4745 }
4846}
4947
50- // 日付を YYYY/MM/DD 形式にフォーマット(日本時間)
48+ class OGImageRewriter {
49+ private imageUrl : string ;
50+
51+ constructor ( imageUrl : string ) {
52+ this . imageUrl = imageUrl ;
53+ }
54+
55+ element ( element : Element ) {
56+ if ( element . getAttribute ( "property" ) === "og:image" ) {
57+ element . setAttribute ( "content" , this . imageUrl ) ;
58+ }
59+ }
60+ }
61+
5162function formatDate ( dateString : string ) : string {
5263 const date = new Date ( dateString ) ;
5364 return new Intl . DateTimeFormat ( "ja-JP" , {
@@ -74,14 +85,12 @@ async function fetchProjectData(eventId: string, apiEndpoint: string): Promise<P
7485 }
7586}
7687
77- // eventId が有効かチェック(21文字のnanoid)
78- function isValidEventId ( path : string ) : boolean {
88+ function isValidEventPath ( path : string ) : boolean {
7989 // 新パス /e/eventId または 旧パス /eventId の形式で、eventId が21文字の英数字・ハイフン・アンダースコア
8090 const match = path . match ( / ^ \/ (?: e \/ ) ? ( [ A - Z a - z 0 - 9 _ - ] { 21 } ) $ / ) ;
8191 return ! ! match ;
8292}
8393
84- // パスから eventId を抽出
8594function extractEventId ( path : string ) : string | null {
8695 // 新パス /e/eventId または 旧パス /eventId から eventId を抽出
8796 const match = path . match ( / ^ \/ (?: e \/ ) ? ( [ A - Z a - z 0 - 9 _ - ] { 21 } ) $ / ) ;
@@ -104,8 +113,7 @@ export async function onRequest(context: EventContext<Env, any, any>): Promise<R
104113 return await next ( ) ;
105114 }
106115
107- // eventId パターンをチェック
108- if ( ! isValidEventId ( path ) ) {
116+ if ( ! isValidEventPath ( path ) ) {
109117 return await next ( ) ;
110118 }
111119
@@ -117,33 +125,36 @@ export async function onRequest(context: EventContext<Env, any, any>): Promise<R
117125 // 元のHTMLレスポンスを取得
118126 const response = await next ( ) ;
119127
120- // HTMLでない場合はそのまま返す
121128 const contentType = response . headers . get ( "content-type" ) ;
122129
123- // 304 Not Modified の場合やHTMLでない場合はスキップ
130+ // 304 Not Modified や HTML でない場合はスキップ
124131 if ( response . status === 304 || ( ! contentType ?. includes ( "text/html" ) && contentType !== null ) ) {
125132 return response ;
126133 }
127134
128- // プロジェクト情報を取得
129135 const projectData = await fetchProjectData ( eventId , env . API_ENDPOINT ) ;
130136
137+ const defaultOgImageUrl = `${ url . origin } /og-image.jpg` ;
138+
131139 if ( ! projectData ) {
132- return response ;
140+ // プロジェクトが見つからない場合はデフォルト画像を設定
141+ return new HTMLRewriter ( )
142+ . on ( 'meta[property="og:image"]' , new OGImageRewriter ( defaultOgImageUrl ) )
143+ . transform ( response ) ;
133144 }
134145
135- // og:title を書き換え
136146 const ogTitle = `${ projectData . name } - イツヒマ` ;
137147
138- // og:description を作成
139148 const startDate = formatDate ( projectData . startDate ) ;
140149 const endDate = formatDate ( projectData . endDate ) ;
141- const dateRange = startDate === endDate ? startDate : `${ startDate } - ${ endDate } ` ;
142-
150+ const dateRange = startDate === endDate ? startDate : `${ startDate } 〜 ${ endDate } ` ;
143151 const ogDescription = `日程範囲: ${ dateRange } ` ;
144152
153+ const ogImageUrl = `${ url . origin } /og/${ eventId } ` ;
154+
145155 return new HTMLRewriter ( )
146156 . on ( 'meta[property="og:title"]' , new OGTitleRewriter ( ogTitle ) )
147157 . on ( 'meta[property="og:description"]' , new OGDescriptionRewriter ( ogDescription ) )
158+ . on ( 'meta[property="og:image"]' , new OGImageRewriter ( ogImageUrl ) )
148159 . transform ( response ) ;
149160}
0 commit comments