matrix-public-archive/shared/lib/url-creator.js

163 lines
5.0 KiB
JavaScript

'use strict';
const urlJoin = require('url-join');
const assert = require('matrix-public-archive-shared/lib/assert');
const { TIME_PRECISION_VALUES } = require('matrix-public-archive-shared/lib/reference-values');
function qsToUrlPiece(qs) {
if (qs.toString()) {
// We allow `$` to be unencoded in the query string because it's a valid character
// in a Matrix event ID
return `?${qs.toString().replace(/%24/g, '$')}`;
} else {
return '';
}
}
class URLCreator {
constructor(basePath) {
this._basePath = basePath;
}
permalinkForRoom(roomIdOrAlias) {
// We don't `encodeURIComponent(...)` because the URL looks nicer without encoded things
return `https://matrix.to/#/${roomIdOrAlias}`;
}
roomDirectoryUrl({ searchTerm, homeserver, paginationToken } = {}) {
let qs = new URLSearchParams();
if (searchTerm) {
qs.append('search', searchTerm);
}
if (homeserver) {
qs.append('homeserver', homeserver);
}
if (paginationToken) {
qs.append('page', paginationToken);
}
return `${this._basePath}${qsToUrlPiece(qs)}`;
}
_getArchiveUrlPathForRoomIdOrAlias(roomIdOrAlias) {
let urlPath;
// We don't `encodeURIComponent(...)` because the URL looks nicer without encoded things
if (roomIdOrAlias.startsWith('#')) {
urlPath = `/r/${roomIdOrAlias.replace(/^#/, '')}`;
} else if (roomIdOrAlias.startsWith('!')) {
urlPath = `/roomid/${roomIdOrAlias.replace(/^!/, '')}`;
} else {
throw new Error(
'URLCreator._getArchiveUrlPathForRoomIdOrAlias(...): roomIdOrAlias should start with # (alias) or ! (room ID)'
);
}
return urlPath;
}
archiveUrlForRoom(roomIdOrAlias, { viaServers = [] } = {}) {
assert(roomIdOrAlias);
assert(Array.isArray(viaServers));
let qs = new URLSearchParams();
[].concat(viaServers).forEach((viaServer) => {
qs.append('via', viaServer);
});
const urlPath = this._getArchiveUrlPathForRoomIdOrAlias(roomIdOrAlias);
return `${urlJoin(this._basePath, `${urlPath}`)}${qsToUrlPiece(qs)}`;
}
archiveUrlForDate(
roomIdOrAlias,
date,
{ preferredPrecision = null, viaServers = [], scrollStartEventId } = {}
) {
assert(roomIdOrAlias);
assert(date);
assert(Array.isArray(viaServers));
// `preferredPrecision` is optional but if they gave a value, make sure it's something expected
if (preferredPrecision) {
assert(
Object.values(TIME_PRECISION_VALUES).includes(preferredPrecision),
`TimeSelectorViewModel: options.preferredPrecision must be one of ${JSON.stringify(
Object.values(TIME_PRECISION_VALUES)
)}`
);
}
let qs = new URLSearchParams();
[].concat(viaServers).forEach((viaServer) => {
qs.append('via', viaServer);
});
if (scrollStartEventId) {
qs.append('at', scrollStartEventId);
}
const urlPath = this._getArchiveUrlPathForRoomIdOrAlias(roomIdOrAlias);
// Gives the date in YYYY/mm/dd format.
// date.toISOString() -> 2022-02-16T23:20:04.709Z
const [datePiece, timePiece] = date.toISOString().split('T');
// Get the `2022/02/16` part of it
const urlDate = datePiece.replaceAll('-', '/');
// Get the `23:20:04` part of it (TIME_PRECISION_VALUES.seconds)
let urlTime = timePiece.split('.')[0];
if (preferredPrecision === TIME_PRECISION_VALUES.minutes) {
// We only want to replace the seconds part of the URL if its superfluous. `23:59:00`
// does not convey more information than `23:59` so we can safely remove it if the
// desired precision is in minutes.
urlTime = urlTime.replace(/:00$/, '');
}
const shouldIncludeTimeInUrl = !!preferredPrecision;
return `${urlJoin(
this._basePath,
`${urlPath}/date/${urlDate}${shouldIncludeTimeInUrl ? `T${urlTime}` : ''}`
)}${qsToUrlPiece(qs)}`;
}
archiveJumpUrlForRoom(
roomIdOrAlias,
{
dir,
currentRangeStartTs,
currentRangeEndTs,
timelineStartEventId,
timelineEndEventId,
viaServers = [],
}
) {
assert(roomIdOrAlias);
assert(dir);
assert(typeof currentRangeStartTs === 'number');
assert(typeof currentRangeEndTs === 'number');
assert(Array.isArray(viaServers));
// `timelineStartEventId` and `timelineEndEventId` are optional because the
// timeline could be showing 0 events or we could be jumping with no knowledge of
// what was shown before.
let qs = new URLSearchParams();
qs.append('dir', dir);
qs.append('currentRangeStartTs', currentRangeStartTs);
qs.append('currentRangeEndTs', currentRangeEndTs);
if (timelineStartEventId) {
qs.append('timelineStartEventId', timelineStartEventId);
}
if (timelineEndEventId) {
qs.append('timelineEndEventId', timelineEndEventId);
}
[].concat(viaServers).forEach((viaServer) => {
qs.append('via', viaServer);
});
const urlPath = this._getArchiveUrlPathForRoomIdOrAlias(roomIdOrAlias);
return `${urlJoin(this._basePath, `${urlPath}/jump`)}${qsToUrlPiece(qs)}`;
}
}
module.exports = URLCreator;