summaly/built/general.js
2023-02-12 12:20:19 +00:00

99 lines
4.2 KiB
JavaScript

import * as URL from 'node:url';
import clip from './utils/clip.js';
import cleanupTitle from './utils/cleanup-title.js';
import { decode as decodeHtml } from 'html-entities';
import { head, scpaping } from './utils/got.js';
export default async (url, lang = null) => {
if (lang && !lang.match(/^[\w-]+(\s*,\s*[\w-]+)*$/))
lang = null;
const res = await scpaping(url.href, { lang: lang || undefined });
const $ = res.$;
const twitterCard = $('meta[property="twitter:card"]').attr('content');
let title = $('meta[property="og:title"]').attr('content') ||
$('meta[property="twitter:title"]').attr('content') ||
$('title').text();
if (title === undefined || title === null) {
return null;
}
title = clip(decodeHtml(title), 100);
let image = $('meta[property="og:image"]').attr('content') ||
$('meta[property="twitter:image"]').attr('content') ||
$('link[rel="image_src"]').attr('href') ||
$('link[rel="apple-touch-icon"]').attr('href') ||
$('link[rel="apple-touch-icon image_src"]').attr('href');
image = image ? URL.resolve(url.href, image) : null;
const playerUrl = (twitterCard !== 'summary_large_image' && $('meta[property="twitter:player"]').attr('content')) ||
(twitterCard !== 'summary_large_image' && $('meta[name="twitter:player"]').attr('content')) ||
$('meta[property="og:video"]').attr('content') ||
$('meta[property="og:video:secure_url"]').attr('content') ||
$('meta[property="og:video:url"]').attr('content');
const playerWidth = parseInt($('meta[property="twitter:player:width"]').attr('content') ||
$('meta[name="twitter:player:width"]').attr('content') ||
$('meta[property="og:video:width"]').attr('content') ||
'');
const playerHeight = parseInt($('meta[property="twitter:player:height"]').attr('content') ||
$('meta[name="twitter:player:height"]').attr('content') ||
$('meta[property="og:video:height"]').attr('content') ||
'');
let description = $('meta[property="og:description"]').attr('content') ||
$('meta[property="twitter:description"]').attr('content') ||
$('meta[name="description"]').attr('content');
description = description
? clip(decodeHtml(description), 300)
: null;
if (title === description) {
description = null;
}
let siteName = $('meta[property="og:site_name"]').attr('content') ||
$('meta[name="application-name"]').attr('content') ||
url.hostname;
siteName = siteName ? decodeHtml(siteName) : null;
const favicon = $('link[rel="shortcut icon"]').attr('href') ||
$('link[rel="icon"]').attr('href') ||
'/favicon.ico';
const sensitive = $('.tweet').attr('data-possibly-sensitive') === 'true';
const find = async (path) => {
const target = URL.resolve(url.href, path);
try {
await head(target);
return target;
}
catch (e) {
return null;
}
};
// 相対的なURL (ex. test) を絶対的 (ex. /test) に変換
const toAbsolute = (relativeURLString) => {
const relativeURL = URL.parse(relativeURLString);
const isAbsolute = relativeURL.slashes || relativeURL.path !== null && relativeURL.path[0] === '/';
// 既に絶対的なら、即座に値を返却
if (isAbsolute) {
return relativeURLString;
}
// スラッシュを付けて返却
return '/' + relativeURLString;
};
const icon = await find(favicon) ||
// 相対指定を絶対指定に変換し再試行
await find(toAbsolute(favicon)) ||
null;
// Clean up the title
title = cleanupTitle(title, siteName);
if (title === '') {
title = siteName;
}
return {
title: title || null,
icon: icon || null,
description: description || null,
thumbnail: image || null,
player: {
url: playerUrl || null,
width: Number.isNaN(playerWidth) ? null : playerWidth,
height: Number.isNaN(playerHeight) ? null : playerHeight
},
sitename: siteName || null,
sensitive,
};
};