mirror of
https://github.com/misskey-dev/summaly.git
synced 2025-05-08 15:17:20 +09:00
138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
import * as URL from 'url';
|
|
import * as request from 'request';
|
|
import nullOrEmpty from './utils/null-or-empty';
|
|
import clip from './utils/clip';
|
|
import cleanupTitle from './utils/cleanup-title';
|
|
|
|
import { AllHtmlEntities } from 'html-entities';
|
|
const entities = new AllHtmlEntities();
|
|
|
|
import * as client from 'cheerio-httpcli';
|
|
client.set('referer', false);
|
|
client.set('timeout', 10000);
|
|
client.set('maxDataSize', 1024 * 1024);
|
|
|
|
import Summary from './summary';
|
|
|
|
export default async (url: URL.Url): Promise<Summary> => {
|
|
const res = await client.fetch(url.href);
|
|
|
|
if (res.error) {
|
|
throw 'something happened';
|
|
}
|
|
|
|
const contentType: string = res.response.headers['content-type'];
|
|
|
|
// HTMLじゃなかった場合は中止
|
|
if (contentType.indexOf('text/html') === -1) {
|
|
return null;
|
|
}
|
|
|
|
const $ = res.$;
|
|
|
|
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(entities.decode(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 player =
|
|
$('meta[property="twitter:player"]').attr('content') ||
|
|
$('meta[name="twitter:player"]').attr('content');
|
|
|
|
const playerWidth = parseInt(
|
|
$('meta[property="twitter:player:width"]').attr('content') ||
|
|
$('meta[name="twitter:player:width"]').attr('content'));
|
|
|
|
const playerHeight = parseInt(
|
|
$('meta[property="twitter:player:height"]').attr('content') ||
|
|
$('meta[name="twitter:player: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(entities.decode(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 ? entities.decode(siteName) : null;
|
|
|
|
const favicon =
|
|
$('link[rel="shortcut icon"]').attr('href') ||
|
|
$('link[rel="icon"]').attr('href') ||
|
|
'/favicon.ico';
|
|
|
|
const find = (path: string) => new Promise<string>(done => {
|
|
const target = URL.resolve(url.href, path);
|
|
request.head(target, (err, res) => {
|
|
if (err) {
|
|
done(null);
|
|
} else if (res.statusCode == 200) {
|
|
done(target);
|
|
} else {
|
|
done(null);
|
|
}
|
|
});
|
|
});
|
|
|
|
// 相対的なURL (ex. test) を絶対的 (ex. /test) に変換
|
|
const toAbsolute = (relativeURLString: string): string => {
|
|
const relativeURL = URL.parse(relativeURLString);
|
|
const isAbsolute = relativeURL.slashes || 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: player || null,
|
|
sitename: siteName || null
|
|
};
|
|
};
|