@@ -2,23 +2,36 @@ import { load } from 'cheerio';
22import { raw } from 'hono/html' ;
33import { renderToString } from 'hono/jsx/dom/server' ;
44
5- import { config } from '@/config' ;
65import cache from '@/utils/cache' ;
76import ofetch from '@/utils/ofetch' ;
87import { parseDate } from '@/utils/parse-date' ;
98
10- export const getBuildId = ( ) =>
11- cache . tryGet (
12- 'aeon:buildId' ,
13- async ( ) => {
14- const response = await ofetch ( 'https://aeon.co' ) ;
15- const $ = load ( response ) ;
16- const nextData = JSON . parse ( $ ( 'script#__NEXT_DATA__' ) . text ( ) ) ;
17- return nextData . buildId ;
18- } ,
19- config . cache . routeExpire ,
20- false
21- ) ;
9+ const ENDPOINT = 'https://api.aeonmedia.co/graphql' ;
10+
11+ const ESSAY = /* GraphQL */ `
12+ query getAeonEssay($slug: String!) {
13+ essay(slug: $slug) {
14+ publishedAt
15+ updatedAt
16+ authors { name authorBio }
17+ audioUrl
18+ image { url alt caption }
19+ body
20+ }
21+ }` ;
22+
23+ const VIDEO = /* GraphQL */ `
24+ query getAeonVideo($slug: String!, $site: SiteEnum!) {
25+ video(slug: $slug, site: $site) {
26+ publishedAt
27+ updatedAt
28+ authors { name authorBio }
29+ hoster
30+ hosterId
31+ credits
32+ description
33+ }
34+ }` ;
2235
2336const renderVideoDescription = ( article ) => {
2437 let video = article . hosterId ;
@@ -44,62 +57,63 @@ const renderEssayDescription = ({ banner, authorsBio, content }) =>
4457 { banner ?. url ? (
4558 < figure >
4659 < img src = { banner . url } alt = { banner . alt } />
47- { banner . caption ? < figcaption > { banner . caption } </ figcaption > : null }
60+ { banner . caption ? < figcaption > { raw ( banner . caption ) } </ figcaption > : null }
4861 </ figure >
4962 ) : null }
5063 { authorsBio ? raw ( authorsBio ) : null }
5164 { content ? raw ( content ) : null }
5265 </ >
5366 ) ;
5467
55- const getData = async ( list ) => {
68+ const getJSON = ( slug , site ) => {
69+ const query = site ? VIDEO : ESSAY ;
70+ const variables = site ? { slug, site } : { slug } ;
71+ const operationName = site ? 'getAeonVideo' : 'getAeonEssay' ;
72+ return ofetch ( ENDPOINT , {
73+ method : 'POST' ,
74+ body : {
75+ operationName,
76+ query,
77+ variables,
78+ } ,
79+ } ) ;
80+ } ;
81+
82+ export const getData = async ( list ) => {
5683 const items = await Promise . all (
5784 list . map ( ( item ) =>
5885 cache . tryGet ( item . link , async ( ) => {
59- const buildId = await getBuildId ( ) ;
60- const response = await ofetch ( `https://aeon.co/_next/data/${ buildId } /${ item . type } s/${ item . slug } .json?id=${ item . slug } ` ) ;
61-
62- const data = response . pageProps . article ;
63- const type = data . type . toLowerCase ( ) ;
86+ const res = await getJSON ( item . slug , item . type === 'video' ? 'aeon' : null ) ;
6487
88+ const data = res . data [ item . type ] ;
6589 item . pubDate = parseDate ( data . publishedAt ) ;
6690
67- if ( type === 'video' ) {
91+ if ( item . type === 'video' ) {
6892 item . description = renderVideoDescription ( data ) ;
6993 } else {
70- if ( data . audio ?. id ) {
71- const response = await ofetch ( 'https://api.aeonmedia.co/graphql' , {
72- method : 'POST' ,
73- body : {
74- query : `query getAudio($audioId: ID!) {
75- audio(id: $audioId) {
76- id
77- streamUrl
78- }
79- }` ,
80- variables : {
81- audioId : data . audio . id ,
82- } ,
83- operationName : 'getAudio' ,
84- } ,
85- } ) ;
86-
94+ if ( data . audioUrl ) {
8795 delete item . image ;
88- item . enclosure_url = response . data . audio . streamUrl ;
96+ item . enclosure_url = data . audioUrl ;
8997 item . enclosure_type = 'audio/mpeg' ;
9098 }
9199
92- // Besides, it seems that the method based on __NEXT_DATA__
93- // does not include the information of the two-column
94- // images in the article body,
95- // e.g. https://aeon.co/essays/how-to-mourn-a-forest-a-lesson-from-west-papua .
96- // But that's very rare.
97-
98100 const capture = load ( data . body , null , false ) ;
99101 const banner = data . image ;
100102 capture ( 'p.pullquote' ) . remove ( ) ;
101103
102- const authorsBio = data . authors . map ( ( author ) => '<p>' + author . name + author . authorBio . replaceAll ( / ^ < p > / g, ' ' ) ) . join ( '' ) ;
104+ const authorsBio = renderToString (
105+ < >
106+ < hr />
107+ { data . authors . map ( ( author ) => (
108+ < p >
109+ { author . name }
110+ { raw ( author . authorBio . replaceAll ( / ^ < p > / g, ' ' ) ) }
111+ </ p >
112+ ) ) }
113+ < hr />
114+ < br />
115+ </ >
116+ ) ;
103117
104118 item . description = renderEssayDescription ( { banner, authorsBio, content : capture . html ( ) } ) ;
105119 }
@@ -111,5 +125,3 @@ const getData = async (list) => {
111125
112126 return items ;
113127} ;
114-
115- export { getData } ;
0 commit comments