playerを使うように

This commit is contained in:
Kagami Sascha Rosylight 2023-03-11 20:48:25 +01:00
parent 84f96c3961
commit 4c45ed716e
4 changed files with 34 additions and 61 deletions

View File

@ -60,7 +60,7 @@ interface IPlugin {
A Promise of an Object that contains properties below: A Promise of an Object that contains properties below:
※ Almost all values are nullable. player shoud not be null. ※ Almost all values are nullable. player should not be null.
#### Root #### Root
@ -78,17 +78,10 @@ A Promise of an Object that contains properties below:
#### Player #### Player
| Property | Type | Description | | Property | Type | Description |
| :-------------- | :------- | :--------------------------------------- | | :-------------- | :--------- | :---------------------------------------------- |
| **url** | *string* | The url of the player | | **url** | *string* | The url of the player |
| **width** | *number* | The width of the player | | **width** | *number* | The width of the player |
| **height** | *number* | The height of the player | | **height** | *number* | The height of the player |
#### oEmbed
| Property | Type | Description |
| :-------------- | :--------- | :---------------------------------------------- |
| **src** | *string* | The source for the iframe |
| **height** | *number* | The height of the iframe |
| **allow** | *string[]* | The names of the allowed permissions for iframe | | **allow** | *string[]* | The names of the allowed permissions for iframe |
Currently the possible items in `allow` are: Currently the possible items in `allow` are:

View File

@ -5,7 +5,7 @@ import cleanupTitle from './utils/cleanup-title.js';
import { decode as decodeHtml } from 'html-entities'; import { decode as decodeHtml } from 'html-entities';
import { get, head, scpaping } from './utils/got.js'; import { get, head, scpaping } from './utils/got.js';
import type { default as Summary, OEmbedRichIframe } from './summary.js'; import type { default as Summary, Player } from './summary.js';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
/** /**
@ -14,7 +14,7 @@ import * as cheerio from 'cheerio';
* *
* Width should always be 100%. * Width should always be 100%.
*/ */
async function getOEmbedRich($: cheerio.CheerioAPI, pageUrl: string): Promise<OEmbedRichIframe | null> { async function getOEmbedPlayer($: cheerio.CheerioAPI, pageUrl: string): Promise<Player | null> {
const href = $('link[type="application/json+oembed"]').attr('href'); const href = $('link[type="application/json+oembed"]').attr('href');
if (!href) { if (!href) {
return null; return null;
@ -29,7 +29,7 @@ async function getOEmbedRich($: cheerio.CheerioAPI, pageUrl: string): Promise<OE
} catch {} } catch {}
})(); })();
if (!body || body.version !== '1.0' || body.type !== 'rich') { if (!body || body.version !== '1.0' || !['rich', 'video'].includes(body.type)) {
// Not a well formed rich oEmbed // Not a well formed rich oEmbed
return null; return null;
} }
@ -52,15 +52,14 @@ async function getOEmbedRich($: cheerio.CheerioAPI, pageUrl: string): Promise<OE
return null; return null;
} }
const src = iframe.attr('src'); const url = iframe.attr('src');
if (!src) { if (!url) {
// No src? // No src?
return null; return null;
} }
// XXX: Use global URL object instead of the deprecated `node:url` // XXX: Use global URL object instead of the deprecated `node:url`
const url = URL.parse(src); if (URL.parse(url).protocol !== 'https:') {
if (url.protocol !== 'https:') {
// Allow only HTTPS for best security // Allow only HTTPS for best security
return null; return null;
} }
@ -87,7 +86,8 @@ async function getOEmbedRich($: cheerio.CheerioAPI, pageUrl: string): Promise<OE
} }
return { return {
src, url,
width: null,
height, height,
allow: allowedFeatures allow: allowedFeatures
} }
@ -199,8 +199,7 @@ export default async (url: URL.Url, lang: string | null = null): Promise<Summary
const [icon, oEmbed] = await Promise.all([ const [icon, oEmbed] = await Promise.all([
getIcon(), getIcon(),
// playerあるならoEmbedは必要ない getOEmbedPlayer($, url.href),
!playerUrl ? getOEmbedRich($, url.href) : null,
]) ])
// Clean up the title // Clean up the title
@ -215,13 +214,13 @@ export default async (url: URL.Url, lang: string | null = null): Promise<Summary
icon: icon || null, icon: icon || null,
description: description || null, description: description || null,
thumbnail: image || null, thumbnail: image || null,
player: { player: oEmbed ?? {
url: playerUrl || null, url: playerUrl || null,
width: Number.isNaN(playerWidth) ? null : playerWidth, width: Number.isNaN(playerWidth) ? null : playerWidth,
height: Number.isNaN(playerHeight) ? null : playerHeight height: Number.isNaN(playerHeight) ? null : playerHeight,
allow: ['fullscreen', 'encrypted-media'],
}, },
sitename: siteName || null, sitename: siteName || null,
sensitive, sensitive,
oEmbed,
}; };
}; };

View File

@ -33,11 +33,6 @@ 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;
@ -57,25 +52,9 @@ export type Player = {
* The height of the player * The height of the player
*/ */
height: number | null; height: number | null;
};
/**
/** * The allowed permissions of the iframe
* Extracted iframe information from OEmbed html field. */
* `width` is omitted here as it should always be 100%. allow: string[];
*/
export 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[],
}; };

View File

@ -213,6 +213,7 @@ describe('TwitterCard', () => {
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.player.url).toBe('https://example.com/embedurl'); expect(summary.player.url).toBe('https://example.com/embedurl');
expect(summary.player.allow).toStrictEqual(['fullscreen', 'encrypted-media']);
}); });
test('Player detection - Pleroma:video => video', async () => { test('Player detection - Pleroma:video => video', async () => {
@ -224,6 +225,7 @@ describe('TwitterCard', () => {
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.player.url).toBe('https://example.com/embedurl'); expect(summary.player.url).toBe('https://example.com/embedurl');
expect(summary.player.allow).toStrictEqual(['fullscreen', 'encrypted-media']);
}); });
test('Player detection - Pleroma:image => image', async () => { test('Player detection - Pleroma:image => image', async () => {
@ -256,44 +258,44 @@ describe("oEmbed", () => {
test(`Invalidity test: ${filename}`, async () => { test(`Invalidity test: ${filename}`, async () => {
await setUpFastify(`invalid/${filename}`); await setUpFastify(`invalid/${filename}`);
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed).toBe(null); expect(summary.player.url).toBe(null);
}); });
} }
test('src', async () => { test('src', async () => {
await setUpFastify('oembed.json'); await setUpFastify('oembed.json');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed?.src).toBe('https://example.com/'); expect(summary.player.url).toBe('https://example.com/');
}); });
test('max height', async () => { test('max height', async () => {
await setUpFastify('oembed-too-tall.json'); await setUpFastify('oembed-too-tall.json');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed?.height).toBe(1024); expect(summary.player.height).toBe(1024);
}); });
test('children are ignored', async () => { test('children are ignored', async () => {
await setUpFastify('oembed-iframe-child.json'); await setUpFastify('oembed-iframe-child.json');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed?.src).toBe('https://example.com/'); expect(summary.player.url).toBe('https://example.com/');
}); });
test('allows fullscreen', async () => { test('allows fullscreen', async () => {
await setUpFastify('oembed-allow-fullscreen.json'); await setUpFastify('oembed-allow-fullscreen.json');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed?.src).toBe('https://example.com/'); expect(summary.player.url).toBe('https://example.com/');
}); });
test('allows safelisted features', async () => { test('allows safelisted features', async () => {
await setUpFastify('oembed-allow-safelisted-features.json'); await setUpFastify('oembed-allow-safelisted-features.json');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed?.src).toBe('https://example.com/'); expect(summary.player.url).toBe('https://example.com/');
}); });
test('oEmbed with relative path', async () => { test('oEmbed with relative path', async () => {
await setUpFastify('oembed.json', 'htmls/oembed-relative.html'); await setUpFastify('oembed.json', 'htmls/oembed-relative.html');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed?.src).toBe('https://example.com/'); expect(summary.player.url).toBe('https://example.com/');
}); });
test('oEmbed with nonexistent path', async () => { test('oEmbed with nonexistent path', async () => {
@ -309,21 +311,21 @@ describe("oEmbed", () => {
test('oEmbed with OpenGraph', async () => { test('oEmbed with OpenGraph', async () => {
await setUpFastify('oembed.json', 'htmls/oembed-and-og.html'); await setUpFastify('oembed.json', 'htmls/oembed-and-og.html');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed?.src).toBe('https://example.com/'); expect(summary.player.url).toBe('https://example.com/');
expect(summary.description).toBe('blobcats rule the world'); expect(summary.description).toBe('blobcats rule the world');
}); });
test('Invalid oEmbed with valid OpenGraph', async () => { test('Invalid oEmbed with valid OpenGraph', async () => {
await setUpFastify('invalid/oembed-insecure.json', 'htmls/oembed-and-og.html'); await setUpFastify('invalid/oembed-insecure.json', 'htmls/oembed-and-og.html');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed).toBe(null); expect(summary.player.url).toBe(null);
expect(summary.description).toBe('blobcats rule the world'); expect(summary.description).toBe('blobcats rule the world');
}); });
test('oEmbed with og:video', async () => { test('oEmbed with og:video', async () => {
await setUpFastify('oembed.json', 'htmls/oembed-and-og-video.html'); await setUpFastify('oembed.json', 'htmls/oembed-and-og-video.html');
const summary = await summaly(host); const summary = await summaly(host);
expect(summary.oEmbed).toBe(null); expect(summary.player.url).toBe('https://example.com/');
expect(summary.player.url).toBe('https://example.com/embedurl'); expect(summary.player.allow).toStrictEqual([]);
}); });
}); });