|
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, |
|
}) |
|
} |
|
}, |
|
}) |