@@ -3,8 +3,12 @@ use std::num::NonZeroU16;
33use axum:: extract:: { OriginalUri , Path , Query , State } ;
44use axum:: http:: StatusCode ;
55use axum:: response:: { Html , Result as AxumResult } ;
6+ use axum_extra:: TypedHeader ;
7+ use headers_accept:: Accept ;
68use http:: header:: LOCATION ;
79use indexmap:: indexmap;
10+ use mediatype:: media_type;
11+ use mediatype:: names:: MARKDOWN ;
812use minijinja:: { context, value:: Value as MJValue } ;
913use str_macro:: str;
1014use tower_sessions:: Session ;
@@ -19,10 +23,14 @@ use crate::stores::blog::{get_detailed_post_by_slug, get_next_post, get_previous
1923use crate :: types:: { AppState , HtmlOrMd , Paginator } ;
2024use crate :: utils:: html:: render_with;
2125
26+ // If the client requests with `Accept: text/markdown` (indicating that it is an AI agent), we will redirect to the ".md" page,
27+ // which returns content in Markdown format. Otherwise, we serve HTML.
2228pub async fn show_post (
2329 auth_session : AuthSession ,
24- Path ( ( _y , _m , slug_ext) ) : Path < ( u16 , u16 , String ) > ,
30+ Path ( ( y , m , slug_ext) ) : Path < ( u16 , u16 , String ) > ,
2531 Query ( params) : Query < PostPageParams > ,
32+ // Value of `Accept` header
33+ TypedHeader ( accept) : TypedHeader < Accept > ,
2634 session : Session ,
2735 State ( state) : State < AppState > ,
2836) -> AxumResult < HtmlOrMd > {
@@ -31,6 +39,17 @@ pub async fn show_post(
3139 Some ( ( slug, ".md" ) ) => ( slug, true ) ,
3240 _ => ( slug_ext. as_str ( ) , false ) ,
3341 } ;
42+ if !is_md {
43+ let available = vec ! [ media_type!( TEXT / HTML ) , media_type!( TEXT / MARKDOWN ) ] ;
44+ let preferred_type = accept. negotiate ( & available) ;
45+ if preferred_type. map ( |t| t. subty ) == Some ( MARKDOWN ) {
46+ return Err ( (
47+ StatusCode :: TEMPORARY_REDIRECT ,
48+ format ! ( "/post/{y}/{m}/{slug}.md" ) ,
49+ )
50+ . into ( ) ) ;
51+ } ;
52+ } ;
3453 let post = get_detailed_post_by_slug ( slug, & db)
3554 . await
3655 . map_err ( PageError :: GelQueryError ) ?
0 commit comments