Skip to content

Instantly share code, notes, and snippets.

@ci7lus
Last active August 9, 2020 14:14
Show Gist options
  • Save ci7lus/1345c318a4d98a6e9f6051d926930949 to your computer and use it in GitHub Desktop.
Save ci7lus/1345c318a4d98a6e9f6051d926930949 to your computer and use it in GitHub Desktop.
import { defineComponent } from "ironpipe"
import { ArgEventHttp } from "ironpipe/lib/component/pipedream"
import axios from "axios"
import domino from "domino"
import { Feed } from "feed"
import moment from "moment"
import "moment-timezone"
import { renderToStaticMarkup } from "react-dom/server"
import { ComicMeteorFeedIndex } from "./comic-meteor-feed-index"
/**
* comic-meteor-feed.ts
* MIT License (c) 2020 ci7lus
* This component requires rollup and esbuild to compile.
*/
module.exports = defineComponent({
name: "comic-meteor-feed",
version: "0.0.1",
props: {
http: "$.interface.http",
db: "$.service.db",
},
async run(event: ArgEventHttp) {
const path = event.path
if (path === "/") {
this.http.respond({
status: 200,
headers: {
"Cache-Control": "public, max-age=3600",
},
body: renderToStaticMarkup(
ComicMeteorFeedIndex({ hostname: this.http.endpoint })
),
})
return
}
const m = path.match(/([a-z0-9]+)\.(rss2|json1|atom1)$/)
if (!m) {
this.http.respond({
status: 404,
})
return
}
const mime = m[2] as "rss2" | "json1" | "atom1"
const mimes = {
rss2: "application/atom+xml; charset=utf-8",
json1: "application/json; charset=utf-8",
atom1: "application/atom+xml; charset=utf-8",
}
const titleId = m[1]
const titleUrl = `https://comic-meteor.jp/${titleId}/`
const r = await axios.get(titleUrl, {
headers: {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36",
},
responseType: "text",
validateStatus: () => true,
timeout: 5000,
})
if (r.status !== 200) {
this.http.respond({
status: r.status,
})
return
}
try {
const document = domino.createDocument(r.data)
const author =
document.querySelector(
"#contents > div.work_episode > div:nth-child(3)"
)?.textContent || "©COMICメテオ"
const description =
document
.querySelector('meta[name="description"]')
?.getAttribute("content") || ""
const image = (document.querySelector(
"div.latest_info_img > img"
) as HTMLImageElement | null)?.src
const authorImage = (document.querySelector(
"div.work_author_intro_img > img"
) as HTMLImageElement | null)?.src
const titleFeed = new Feed({
title:
document.querySelector("div.h2ttl_other")?.textContent?.trim() ||
document.title,
description: description,
link: titleUrl,
id: titleUrl,
generator: "comic-meteor-feed.ts",
copyright: author,
language: "ja",
feed: new URL(`${this.http.endpoint}/${titleId}.rss2`).href,
image,
author: {
name: author,
link: authorImage,
},
})
type updatedMapType = { [key: string]: { updated_at: string } }
const updatedMap = this.db.get<updatedMapType>(titleId) || {}
const now = moment().tz("Asia/Tokyo").startOf("hours")
const nowFmt = now.format()
const episodeBox = document.querySelector(".work_episode_box")
if (episodeBox) {
Array.from(episodeBox.querySelectorAll(".work_episode_table")).map(
(episodeTable) => {
const link = episodeTable
.querySelector('a[target="_blank"]')
?.getAttribute("href")
const m = link?.match(/ptdata\/(\w+)\/([a-zA-Z0-9]+)/)
if (!link || !m) return
const episodeId = m[2]
const titleParts = episodeTable
.querySelector(".work_episode_txt")
?.textContent?.split("\n")
.map((splitted) => splitted.trim().replace(" ", " "))
if (!titleParts || titleParts.length < 3) return
const episodeTitle = titleParts[1]
const updatedAt =
episodeId in updatedMap
? moment(updatedMap[episodeId].updated_at).tz("Asia/Tokyo")
: (() => {
updatedMap[episodeId] = { updated_at: nowFmt }
return now
})()
titleFeed.addItem({
title: episodeTitle,
id: link,
link: link,
date: updatedAt.toDate(),
})
}
)
}
this.http.respond({
status: 200,
headers: {
"Content-Type": mimes[mime],
"Cache-Control": "public, max-age=600",
},
body: titleFeed[mime](),
})
if (0 < Object.keys(updatedMap).length) this.db.set(titleId, updatedMap)
} catch (error) {
console.error(error)
this.http.respond({
status: 500,
})
}
},
})
import React from "react"
export const ComicMeteorFeedIndex = ({ hostname }: { hostname: string }) => {
const url = new URL(hostname)
url.pathname = "/jyashin.rss2"
const jyashin = url.href
const subscribeUrl = jyashin.replace(
"/jyashin.rss2",
"/{title_id}.{rss2|atom1|json1}"
)
const exampleUrl = jyashin
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>comic-meteor-feed.ts</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css"
/>
</head>
<body>
<section className="section">
<div className="container">
<h1 className="title">comic-meteor-feed.ts</h1>
<p className="subtitle">
<a href="https://comic-meteor.jp">comic-meteor</a> rss feed
generator hosted with&nbsp;
<a href="https://pipedream.com">pipedream</a>.
</p>
<p className="mb-4">
Please use this URL to subscribe:
<input
id="subscribeUrl"
className="input"
type="text"
readOnly={true}
value={subscribeUrl}
/>
Example:
<input
id="exampleUrl"
className="input"
type="text"
readOnly={true}
value={exampleUrl}
/>
</p>
<p>
<a href="https://gist.github.com/ci7lus/1345c318a4d98a6e9f6051d926930949">
sourcecode (gist)
</a>
</p>
</div>
</section>
</body>
</html>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment