Skip to content

Commit

Permalink
Add image metadata for URL previews (#224)
Browse files Browse the repository at this point in the history
 - Default to a nice `[matrix]` banner
    -  There is room for improvement here when the Matrix Public Archive gets it's own logo (#94) and maybe says "Matrix Public Archive" somewhere in the banner.
    - This is good enough for now (and certainly better than downstream previews using the first image on the page).
 - For rooms, it will use the room avatar

Part of #202

Image is sized to 1200x630 to match conventions of `og:image`.

Crafted the banner image by modifying the header on the room directory homepage and taking a node screenshot. Page zoom @ 175%
  • Loading branch information
MadLittleMods authored May 10, 2023
1 parent bf8040f commit 16323df
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 35 deletions.
Binary file added client/img/matrix-public-archive-opengraph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions client/js/entry-client-room-directory.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ import '../css/room-directory.css';
// over for all
import '../img/favicon.ico';
import '../img/favicon.svg';
import '../img/matrix-public-archive-opengraph.png';
34 changes: 30 additions & 4 deletions server/hydrogen-render/render-page-html.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
'use strict';

const assert = require('assert');
const urlJoin = require('url-join');

const { getSerializableSpans } = require('../tracing/tracing-middleware');
const sanitizeHtml = require('../lib/sanitize-html');
const safeJson = require('../lib/safe-json');
const getDependenciesForEntryPointName = require('../lib/get-dependencies-for-entry-point-name');
const getFaviconAssetUrls = require('../lib/get-favicon-asset-urls');
const getAssetUrl = require('../lib/get-asset-url');

const config = require('../lib/config');
const basePath = config.get('basePath');
assert(basePath);

let _assetUrls;
function getAssetUrls() {
// Probably not that much overhead but only calculate this once
if (_assetUrls) {
return _assetUrls;
}

_assetUrls = {
faviconIco: getAssetUrl('client/img/favicon.ico'),
faviconSvg: getAssetUrl('client/img/favicon.svg'),
opengraphImage: getAssetUrl('client/img/matrix-public-archive-opengraph.png'),
};
return _assetUrls;
}

function renderPageHtml({
pageOptions,
Expand Down Expand Up @@ -45,7 +65,12 @@ function renderPageHtml({
maybeAdultMeta = `<meta name="rating" content="adult">`;
}

const faviconMap = getFaviconAssetUrls();
const pageAssetUrls = getAssetUrls();
let metaImageUrl = urlJoin(basePath, pageAssetUrls.opengraphImage);
if (pageOptions.imageUrl) {
metaImageUrl = pageOptions.imageUrl;
}

const pageHtml = `
<!doctype html>
<html lang="en">
Expand All @@ -55,8 +80,9 @@ function renderPageHtml({
${maybeAdultMeta}
${sanitizeHtml(`<title>${pageOptions.title}</title>`)}
${sanitizeHtml(`<meta name="description" content="${pageOptions.description}">`)}
<link rel="icon" href="${faviconMap.ico}" sizes="any">
<link rel="icon" href="${faviconMap.svg}" type="image/svg+xml">
${sanitizeHtml(`<meta property="og:image" content="${metaImageUrl}">`)}
<link rel="icon" href="${pageAssetUrls.faviconIco}" sizes="any">
<link rel="icon" href="${pageAssetUrls.faviconSvg}" type="image/svg+xml">
${styles
.map(
(styleUrl) =>
Expand Down
24 changes: 24 additions & 0 deletions server/lib/get-asset-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const path = require('path').posix;

function getAssetUrl(inputAssetPath) {
// Lazy-load the manifest so we only require it on first call hopefully after the Vite
// client build completes. `require(...)` calls are cached so it should be fine to
// look this up over and over.
//
// We have to disable the `no-missing-require` because the file is built via the Vite client build.
// eslint-disable-next-line n/no-missing-require, n/no-unpublished-require
const manfiest = require('../../dist/manifest.json');

const assetEntry = manfiest[inputAssetPath];
if (!assetEntry) {
throw new Error(`Could not find asset with path "${inputAssetPath}" in \`dist/manifest.json\``);
}

const outputAssetPath = path.join('/', assetEntry.file);

return outputAssetPath;
}

module.exports = getAssetUrl;
30 changes: 0 additions & 30 deletions server/lib/get-favicon-asset-urls.js

This file was deleted.

8 changes: 8 additions & 0 deletions server/routes/room-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const renderHydrogenVmRenderScriptToPageHtml = require('../hydrogen-render/rende
const setHeadersToPreloadAssets = require('../lib/set-headers-to-preload-assets');
const setHeadersForDateTemporalContext = require('../lib/set-headers-for-date-temporal-context');
const MatrixPublicArchiveURLCreator = require('matrix-public-archive-shared/lib/url-creator');
const { mxcUrlToHttpThumbnail } = require('matrix-public-archive-shared/lib/mxc-url-to-http');
const checkTextForNsfw = require('matrix-public-archive-shared/lib/check-text-for-nsfw');
const {
MS_LOOKUP,
Expand Down Expand Up @@ -906,6 +907,13 @@ router.get(
const pageOptions = {
title: `${roomData.name} - Matrix Public Archive`,
description: `View the history of ${roomData.name} in the Matrix Public Archive`,
imageUrl:
roomData.avatarUrl &&
mxcUrlToHttpThumbnail({
mxcUrl: roomData.avatarUrl,
homeserverUrl: matrixServerUrl,
size: 256,
}),
blockedBySafeSearch: isNsfw,
entryPoint: 'client/js/entry-client-hydrogen.js',
locationHref: urlJoin(basePath, req.originalUrl),
Expand Down
8 changes: 7 additions & 1 deletion shared/lib/mxc-url-to-http.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,21 @@ function mxcUrlToHttp({ mxcUrl, homeserverUrl }) {
)}`;
}

function mxcUrlToHttpThumbnail({ mxcUrl, homeserverUrl, size }) {
const ALLOWED_RESIZE_METHODS = ['scale', 'crop'];
function mxcUrlToHttpThumbnail({ mxcUrl, homeserverUrl, size, resizeMethod = 'scale' }) {
assert(mxcUrl, '`mxcUrl` must be provided to `mxcUrlToHttp(...)`');
assert(homeserverUrl, '`homeserverUrl` must be provided to `mxcUrlToHttp(...)`');
assert(size, '`size` must be provided to `mxcUrlToHttp(...)`');
assert(
ALLOWED_RESIZE_METHODS.includes(resizeMethod),
`\`resizeMethod\` must be ${JSON.stringify(ALLOWED_RESIZE_METHODS)}`
);
const [serverName, mediaId] = mxcUrl.substr('mxc://'.length).split('/');

let qs = new URLSearchParams();
qs.append('width', Math.round(size));
qs.append('height', Math.round(size));
qs.append('method', resizeMethod);

const url = homeserverUrl.replace(/\/$/, '');
return `${url}/_matrix/media/r0/thumbnail/${encodeURIComponent(serverName)}/${encodeURIComponent(
Expand Down

0 comments on commit 16323df

Please sign in to comment.