mirror of
https://github.com/misskey-dev/summaly.git
synced 2025-06-08 12:48:05 +09:00
built
This commit is contained in:
parent
a5a8c4437d
commit
7b8a2b0913
2
built/general.d.ts
vendored
2
built/general.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import * as URL from 'node:url';
|
import * as URL from 'node:url';
|
||||||
import Summary from './summary.js';
|
import type { default as Summary } from './summary.js';
|
||||||
declare const _default: (url: URL.Url, lang?: string | null) => Promise<Summary | null>;
|
declare const _default: (url: URL.Url, lang?: string | null) => Promise<Summary | null>;
|
||||||
export default _default;
|
export default _default;
|
||||||
|
@ -2,7 +2,74 @@ import * as URL from 'node:url';
|
|||||||
import clip from './utils/clip.js';
|
import clip from './utils/clip.js';
|
||||||
import cleanupTitle from './utils/cleanup-title.js';
|
import cleanupTitle from './utils/cleanup-title.js';
|
||||||
import { decode as decodeHtml } from 'html-entities';
|
import { decode as decodeHtml } from 'html-entities';
|
||||||
import { head, scpaping } from './utils/got.js';
|
import { get, head, scpaping } from './utils/got.js';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
/**
|
||||||
|
* Contains only the html snippet for a sanitized iframe as the thumbnail is
|
||||||
|
* mostly covered in OpenGraph instead.
|
||||||
|
*
|
||||||
|
* Width should always be 100%.
|
||||||
|
*/
|
||||||
|
async function getOEmbedRich($, pageUrl) {
|
||||||
|
const href = $('link[type="application/json+oembed"]').attr('href');
|
||||||
|
if (!href) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// XXX: Use global URL object instead of the deprecated `node:url`
|
||||||
|
// Disallow relative URL as no one seems to use it
|
||||||
|
const oEmbed = await get(URL.resolve(pageUrl, href));
|
||||||
|
const body = (() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(oEmbed);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
})();
|
||||||
|
if (!body || body.version !== '1.0' || body.type !== 'rich') {
|
||||||
|
// Not a well formed rich oEmbed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!body.html.startsWith('<iframe ') || !body.html.endsWith('</iframe>')) {
|
||||||
|
// It includes something else than an iframe
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const oEmbedHtml = cheerio.load(body.html);
|
||||||
|
const iframe = oEmbedHtml("iframe");
|
||||||
|
if (iframe.length !== 1) {
|
||||||
|
// Somehow we either have multiple iframes or none
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (iframe.parents().length !== 2) {
|
||||||
|
// Should only have the body and html elements as the parents
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const src = iframe.attr('src');
|
||||||
|
if (!src) {
|
||||||
|
// No src?
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// XXX: Use global URL object instead of the deprecated `node:url`
|
||||||
|
const url = URL.parse(src);
|
||||||
|
if (url.protocol !== 'https:') {
|
||||||
|
// Allow only HTTPS for best security
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const height = Math.min(Number(iframe.attr('height') ?? body.height), 1024);
|
||||||
|
if (Number.isNaN(height)) {
|
||||||
|
// No proper size info
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const allowedFeatures = (iframe.attr('allow') ?? '').split(/\s+/g);
|
||||||
|
const safeList = ['', 'fullscreen', 'encrypted-media', 'picture-in-picture'];
|
||||||
|
if (allowedFeatures.some(allow => !safeList.includes(allow))) {
|
||||||
|
// This iframe is probably too powerful to be embedded
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
src,
|
||||||
|
height,
|
||||||
|
allow: allowedFeatures
|
||||||
|
};
|
||||||
|
}
|
||||||
export default async (url, lang = null) => {
|
export default async (url, lang = null) => {
|
||||||
if (lang && !lang.match(/^[\w-]+(\s*,\s*[\w-]+)*$/))
|
if (lang && !lang.match(/^[\w-]+(\s*,\s*[\w-]+)*$/))
|
||||||
lang = null;
|
lang = null;
|
||||||
@ -73,10 +140,17 @@ export default async (url, lang = null) => {
|
|||||||
// スラッシュを付けて返却
|
// スラッシュを付けて返却
|
||||||
return '/' + relativeURLString;
|
return '/' + relativeURLString;
|
||||||
};
|
};
|
||||||
const icon = await find(favicon) ||
|
const getIcon = async () => {
|
||||||
|
return await find(favicon) ||
|
||||||
// 相対指定を絶対指定に変換し再試行
|
// 相対指定を絶対指定に変換し再試行
|
||||||
await find(toAbsolute(favicon)) ||
|
await find(toAbsolute(favicon)) ||
|
||||||
null;
|
null;
|
||||||
|
};
|
||||||
|
const [icon, oEmbed] = await Promise.all([
|
||||||
|
getIcon(),
|
||||||
|
// playerあるならoEmbedは必要ない
|
||||||
|
!playerUrl ? getOEmbedRich($, url.href) : null,
|
||||||
|
]);
|
||||||
// Clean up the title
|
// Clean up the title
|
||||||
title = cleanupTitle(title, siteName);
|
title = cleanupTitle(title, siteName);
|
||||||
if (title === '') {
|
if (title === '') {
|
||||||
@ -94,5 +168,6 @@ export default async (url, lang = null) => {
|
|||||||
},
|
},
|
||||||
sitename: siteName || null,
|
sitename: siteName || null,
|
||||||
sensitive,
|
sensitive,
|
||||||
|
oEmbed,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -38,6 +38,7 @@ export async function summarize(url) {
|
|||||||
width: playerWidth ? parseInt(playerWidth) : null,
|
width: playerWidth ? parseInt(playerWidth) : null,
|
||||||
height: playerHeight ? parseInt(playerHeight) : null
|
height: playerHeight ? parseInt(playerHeight) : null
|
||||||
},
|
},
|
||||||
sitename: 'Amazon'
|
sitename: 'Amazon',
|
||||||
|
oEmbed: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ export async function summarize(url) {
|
|||||||
width: null,
|
width: null,
|
||||||
height: null
|
height: null
|
||||||
},
|
},
|
||||||
sitename: 'Wikipedia'
|
sitename: 'Wikipedia',
|
||||||
|
oEmbed: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
22
built/summary.d.ts
vendored
22
built/summary.d.ts
vendored
@ -27,6 +27,10 @@ declare type Summary = {
|
|||||||
* Possibly sensitive
|
* Possibly sensitive
|
||||||
*/
|
*/
|
||||||
sensitive?: boolean;
|
sensitive?: boolean;
|
||||||
|
/**
|
||||||
|
* The iframe information of oEmbed data from that web page
|
||||||
|
*/
|
||||||
|
oEmbed: OEmbedRichIframe | null;
|
||||||
};
|
};
|
||||||
export default Summary;
|
export default Summary;
|
||||||
export declare type Player = {
|
export declare type Player = {
|
||||||
@ -43,3 +47,21 @@ export declare type Player = {
|
|||||||
*/
|
*/
|
||||||
height: number | null;
|
height: number | null;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Extracted iframe information from OEmbed html field.
|
||||||
|
* `width` is omitted here as it should always be 100%.
|
||||||
|
*/
|
||||||
|
export declare type OEmbedRichIframe = {
|
||||||
|
/**
|
||||||
|
* The src of the iframe
|
||||||
|
*/
|
||||||
|
src: string;
|
||||||
|
/**
|
||||||
|
* The height of the iframe
|
||||||
|
*/
|
||||||
|
height: number;
|
||||||
|
/**
|
||||||
|
* The allowed feature list of the iframe
|
||||||
|
*/
|
||||||
|
allow: string[];
|
||||||
|
};
|
||||||
|
@ -84,14 +84,15 @@ async function getResponse(args) {
|
|||||||
limit: 0,
|
limit: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return await receiveResponce({ req, typeFilter: args.typeFilter });
|
return await receiveResponse({ req, typeFilter: args.typeFilter });
|
||||||
}
|
}
|
||||||
async function receiveResponce(args) {
|
async function receiveResponse(args) {
|
||||||
const req = args.req;
|
const req = args.req;
|
||||||
const maxSize = MAX_RESPONSE_SIZE;
|
const maxSize = MAX_RESPONSE_SIZE;
|
||||||
req.on('response', (res) => {
|
req.on('response', (res) => {
|
||||||
// Check html
|
// Check html
|
||||||
if (args.typeFilter && !res.headers['content-type']?.match(args.typeFilter)) {
|
if (args.typeFilter && !res.headers['content-type']?.match(args.typeFilter)) {
|
||||||
|
// console.warn(res.headers['content-type']);
|
||||||
req.cancel(`Rejected by type filter ${res.headers['content-type']}`);
|
req.cancel(`Rejected by type filter ${res.headers['content-type']}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user