Add basic response (except images)
The basic idea here is to fetch the post with the medium API, parse the JSON into types, and then re-display the content. We also have to fetch each media object as a REST call to get things like embeded iframes.
This commit is contained in:
parent
fcf3eb14d0
commit
9e96f29852
5 changed files with 227 additions and 0 deletions
|
@ -17,4 +17,8 @@ module Lucky::Env
|
|||
def task?
|
||||
ENV["LUCKY_TASK"]? == "true"
|
||||
end
|
||||
|
||||
def use_local?
|
||||
ENV.fetch("USE_LOCAL", "false") == "true"
|
||||
end
|
||||
end
|
||||
|
|
75
src/actions/articles/show.cr
Normal file
75
src/actions/articles/show.cr
Normal file
|
@ -0,0 +1,75 @@
|
|||
require "json"
|
||||
|
||||
class Articles::Show < BrowserAction
|
||||
get "/post/:post_id" do
|
||||
if Lucky::Env.use_local?
|
||||
response = LocalClient.post_data(post_id)
|
||||
else
|
||||
response = MediumClient.post_data(post_id)
|
||||
end
|
||||
html ShowPage, medium_response_body: PostResponse::Root.from_json(response.body)
|
||||
end
|
||||
end
|
||||
|
||||
class PostResponse
|
||||
class Base
|
||||
include JSON::Serializable
|
||||
end
|
||||
|
||||
class Root < Base
|
||||
property data : Data
|
||||
end
|
||||
|
||||
class Data < Base
|
||||
property post : Post
|
||||
end
|
||||
|
||||
class Post < Base
|
||||
property title : String
|
||||
property creator : Creator
|
||||
property content : Content
|
||||
end
|
||||
|
||||
class Creator < Base
|
||||
property name : String
|
||||
property id : String
|
||||
end
|
||||
|
||||
class Content < Base
|
||||
property bodyModel : BodyModel
|
||||
end
|
||||
|
||||
class BodyModel < Base
|
||||
property paragraphs : Array(Paragraph)
|
||||
end
|
||||
|
||||
class Paragraph < Base
|
||||
property text : String
|
||||
property type : ParagraphType
|
||||
property iframe : IFrame?
|
||||
property layout : String?
|
||||
end
|
||||
|
||||
enum ParagraphType
|
||||
H3
|
||||
H4
|
||||
P
|
||||
PRE
|
||||
BQ
|
||||
ULI
|
||||
OLI
|
||||
IFRAME
|
||||
IMG
|
||||
end
|
||||
|
||||
class IFrame < Base
|
||||
property mediaResource : MediaResource
|
||||
end
|
||||
|
||||
class MediaResource < Base
|
||||
property id : String
|
||||
end
|
||||
|
||||
class Metadata < Base
|
||||
end
|
||||
end
|
20
src/actions/clients/local_client.cr
Normal file
20
src/actions/clients/local_client.cr
Normal file
|
@ -0,0 +1,20 @@
|
|||
require "./medium_client"
|
||||
|
||||
# This allows you to read posts responses from a local file instead of hitting # the API all the time. You can get an api response by inserting the post id
|
||||
# in this curl(1) command:
|
||||
|
||||
# curl -X "POST" "https://medium.com/_/graphql" \
|
||||
# -H 'Content-Type: application/json; charset=utf-8' \
|
||||
# -d $'{
|
||||
# "query": "query{post(id:\\"[post id here]\\"){title creator{name id}content{bodyModel{paragraphs{text type markups{name title type href start end rel anchorType}href iframe{mediaResource{id}}layout metadata{__typename id originalWidth originalHeight}}}}}}"
|
||||
# }' > [post id here].json
|
||||
|
||||
# Then place it in the /tmp/posts directory. The post id will come in as a
|
||||
# query param and go look for a file with a matching filename.
|
||||
|
||||
class LocalClient < MediumClient
|
||||
def self.post_data(post_id : String) : HTTP::Client::Response
|
||||
body = File.read("tmp/posts/#{post_id}.json")
|
||||
HTTP::Client::Response.new(HTTP::Status::OK, body: body)
|
||||
end
|
||||
end
|
90
src/actions/clients/medium_client.cr
Normal file
90
src/actions/clients/medium_client.cr
Normal file
|
@ -0,0 +1,90 @@
|
|||
require "json"
|
||||
|
||||
class MediumClient
|
||||
# https://stackoverflow.com/questions/2669690/
|
||||
JSON_HIJACK_STRING = "])}while(1);</x>"
|
||||
|
||||
def self.post_data(post_id : String) : HTTP::Client::Response
|
||||
client = HTTP::Client.new("medium.com", tls: true)
|
||||
client.post("/_/graphql", headers: headers, body: body(post_id))
|
||||
end
|
||||
|
||||
def self.embed_data(media_id : String) : MediaResponse::Root
|
||||
client = HTTP::Client.new("medium.com", tls: true)
|
||||
response = client.get("/media/#{media_id}", headers: headers)
|
||||
body = response.body.sub(JSON_HIJACK_STRING, nil)
|
||||
MediaResponse::Root.from_json(body)
|
||||
end
|
||||
|
||||
private def self.headers : HTTP::Headers
|
||||
HTTP::Headers{
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json; charset=utf-8",
|
||||
}
|
||||
end
|
||||
|
||||
private def self.body(post_id : String) : String
|
||||
query = <<-GRAPHQL
|
||||
query {
|
||||
post(id: "#{post_id}") {
|
||||
title
|
||||
creator {
|
||||
id
|
||||
name
|
||||
}
|
||||
content {
|
||||
bodyModel {
|
||||
paragraphs {
|
||||
text
|
||||
type
|
||||
markups {
|
||||
name
|
||||
type
|
||||
start
|
||||
end
|
||||
}
|
||||
href
|
||||
iframe {
|
||||
mediaResource {
|
||||
id
|
||||
}
|
||||
}
|
||||
metadata {
|
||||
__typename
|
||||
id
|
||||
originalWidth
|
||||
originalHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GRAPHQL
|
||||
JSON.build do |json|
|
||||
json.object do
|
||||
json.field "query", query
|
||||
json.field "variables", {} of String => String
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MediaResponse
|
||||
class Base
|
||||
include JSON::Serializable
|
||||
end
|
||||
|
||||
class Root < Base
|
||||
property payload : Payload
|
||||
end
|
||||
|
||||
class Payload < Base
|
||||
property value : Value
|
||||
end
|
||||
|
||||
class Value < Base
|
||||
property href : String
|
||||
property iframeSrc : String
|
||||
end
|
||||
end
|
38
src/pages/articles/show_page.cr
Normal file
38
src/pages/articles/show_page.cr
Normal file
|
@ -0,0 +1,38 @@
|
|||
class Articles::ShowPage < MainLayout
|
||||
needs medium_response_body : PostResponse::Root
|
||||
|
||||
def content
|
||||
paragraphs = medium_response_body.data.post.content.bodyModel.paragraphs
|
||||
paragraphs.each do |paragraph|
|
||||
case paragraph.type
|
||||
when PostResponse::ParagraphType::H3
|
||||
h3 paragraph.text
|
||||
when PostResponse::ParagraphType::H4
|
||||
h4 paragraph.text
|
||||
when PostResponse::ParagraphType::P
|
||||
para paragraph.text
|
||||
when PostResponse::ParagraphType::PRE
|
||||
pre paragraph.text
|
||||
when PostResponse::ParagraphType::BQ
|
||||
blockquote paragraph.text
|
||||
when PostResponse::ParagraphType::OLI
|
||||
li paragraph.text
|
||||
when PostResponse::ParagraphType::ULI
|
||||
li paragraph.text
|
||||
when PostResponse::ParagraphType::IFRAME
|
||||
embed = paragraph.iframe
|
||||
if embed
|
||||
embed_data = LocalClient.embed_data(embed.mediaResource.id)
|
||||
embed_value = embed_data.payload.value
|
||||
if embed_value.iframeSrc.blank?
|
||||
iframe src: embed_data.payload.value.href
|
||||
else
|
||||
iframe src: embed_data.payload.value.iframeSrc
|
||||
end
|
||||
end
|
||||
else
|
||||
para "#{paragraph.type} not yet implimented"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue