perf(server): improvement of external mediaProxy (#9787)
* perf(server): improvement of external mediaProxy * add a comment * ✌️ * /filesでsharpの処理を行わずリダイレクトする * fix * thumbnail => static * Fix #9788 * add avatar mode * add url * fix * static.webp * remove encodeURIComponent from media proxy path * remove existance check
This commit is contained in:
parent
0c12e80106
commit
2dfed75402
|
@ -130,6 +130,7 @@ proxyBypassHosts:
|
||||||
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
|
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
|
||||||
|
|
||||||
# Media Proxy
|
# Media Proxy
|
||||||
|
# Reference Implementation: https://github.com/misskey-dev/media-proxy
|
||||||
#mediaProxy: https://example.com/proxy
|
#mediaProxy: https://example.com/proxy
|
||||||
|
|
||||||
# Proxy remote files (default: false)
|
# Proxy remote files (default: false)
|
||||||
|
|
|
@ -24,6 +24,9 @@ You should also include the user name that made the change.
|
||||||
- syslogのサポートが削除されました
|
- syslogのサポートが削除されました
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
- 外部メディアプロキシへの対応を強化しました
|
||||||
|
外部メディアプロキシのFastify実装を作りました
|
||||||
|
https://github.com/misskey-dev/media-proxy
|
||||||
- ロールで広告の非表示が有効になっている場合は最初から広告を非表示にするように
|
- ロールで広告の非表示が有効になっている場合は最初から広告を非表示にするように
|
||||||
|
|
||||||
## 13.2.6 (2023/02/01)
|
## 13.2.6 (2023/02/01)
|
||||||
|
|
|
@ -87,6 +87,8 @@ export type Mixin = {
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
clientEntry: string;
|
clientEntry: string;
|
||||||
clientManifestExists: boolean;
|
clientManifestExists: boolean;
|
||||||
|
mediaProxy: string;
|
||||||
|
externalMediaProxyEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Config = Source & Mixin;
|
export type Config = Source & Mixin;
|
||||||
|
@ -135,6 +137,13 @@ export function loadConfig() {
|
||||||
mixin.clientEntry = clientManifest['src/init.ts'];
|
mixin.clientEntry = clientManifest['src/init.ts'];
|
||||||
mixin.clientManifestExists = clientManifestExists;
|
mixin.clientManifestExists = clientManifestExists;
|
||||||
|
|
||||||
|
const externalMediaProxy = config.mediaProxy ?
|
||||||
|
config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy
|
||||||
|
: null;
|
||||||
|
const internalMediaProxy = `${mixin.scheme}://${mixin.host}/proxy`;
|
||||||
|
mixin.mediaProxy = externalMediaProxy ?? internalMediaProxy;
|
||||||
|
mixin.externalMediaProxyEnabled = externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy;
|
||||||
|
|
||||||
if (!config.redis.prefix) config.redis.prefix = mixin.host;
|
if (!config.redis.prefix) config.redis.prefix = mixin.host;
|
||||||
|
|
||||||
return Object.assign(config, mixin);
|
return Object.assign(config, mixin);
|
||||||
|
|
|
@ -120,7 +120,7 @@ export class CustomEmojiService {
|
||||||
const url = isLocal
|
const url = isLocal
|
||||||
? emojiUrl
|
? emojiUrl
|
||||||
: this.config.proxyRemoteFiles
|
: this.config.proxyRemoteFiles
|
||||||
? `${this.config.url}/proxy/${encodeURIComponent((new URL(emojiUrl)).pathname)}?${query({ url: emojiUrl })}`
|
? `${this.config.mediaProxy}/emoji.webp?${query({ url: emojiUrl })}`
|
||||||
: emojiUrl;
|
: emojiUrl;
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
|
|
|
@ -54,7 +54,7 @@ export class ChannelEntityService {
|
||||||
name: channel.name,
|
name: channel.name,
|
||||||
description: channel.description,
|
description: channel.description,
|
||||||
userId: channel.userId,
|
userId: channel.userId,
|
||||||
bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner, false) : null,
|
bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
|
||||||
usersCount: channel.usersCount,
|
usersCount: channel.usersCount,
|
||||||
notesCount: channel.notesCount,
|
notesCount: channel.notesCount,
|
||||||
|
|
||||||
|
|
|
@ -71,27 +71,41 @@ export class DriveFileEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getPublicUrl(file: DriveFile, thumbnail = false): string | null {
|
public getPublicUrl(file: DriveFile, mode? : 'static' | 'avatar'): string | null { // static = thumbnail
|
||||||
|
const proxiedUrl = (url: string) => appendQuery(
|
||||||
|
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
|
||||||
|
query({
|
||||||
|
url,
|
||||||
|
...(mode ? { [mode]: '1' } : {}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// リモートかつメディアプロキシ
|
// リモートかつメディアプロキシ
|
||||||
if (file.uri != null && file.userHost != null && this.config.mediaProxy != null) {
|
if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) {
|
||||||
return appendQuery(this.config.mediaProxy, query({
|
return proxiedUrl(file.uri);
|
||||||
url: file.uri,
|
|
||||||
thumbnail: thumbnail ? '1' : undefined,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// リモートかつ期限切れはローカルプロキシを試みる
|
// リモートかつ期限切れはローカルプロキシを試みる
|
||||||
if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) {
|
if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) {
|
||||||
const key = thumbnail ? file.thumbnailAccessKey : file.webpublicAccessKey;
|
const key = mode === 'static' ? file.thumbnailAccessKey : file.webpublicAccessKey;
|
||||||
|
|
||||||
if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外
|
if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外
|
||||||
return `${this.config.url}/files/${key}`;
|
const url = `${this.config.url}/files/${key}`;
|
||||||
|
if (mode === 'avatar') return proxiedUrl(url);
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/avif', 'image/svg+xml'].includes(file.type);
|
const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/avif', 'image/svg+xml'].includes(file.type);
|
||||||
|
|
||||||
return thumbnail ? (file.thumbnailUrl ?? (isImage ? (file.webpublicUrl ?? file.url) : null)) : (file.webpublicUrl ?? file.url);
|
if (mode === 'static') {
|
||||||
|
return file.thumbnailUrl ?? (isImage ? (file.webpublicUrl ?? file.url) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = file.webpublicUrl ?? file.url;
|
||||||
|
|
||||||
|
if (mode === 'avatar') return proxiedUrl(url);
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -166,8 +180,8 @@ export class DriveFileEntityService {
|
||||||
isSensitive: file.isSensitive,
|
isSensitive: file.isSensitive,
|
||||||
blurhash: file.blurhash,
|
blurhash: file.blurhash,
|
||||||
properties: opts.self ? file.properties : this.getPublicProperties(file),
|
properties: opts.self ? file.properties : this.getPublicProperties(file),
|
||||||
url: opts.self ? file.url : this.getPublicUrl(file, false),
|
url: opts.self ? file.url : this.getPublicUrl(file),
|
||||||
thumbnailUrl: this.getPublicUrl(file, true),
|
thumbnailUrl: this.getPublicUrl(file, 'static'),
|
||||||
comment: file.comment,
|
comment: file.comment,
|
||||||
folderId: file.folderId,
|
folderId: file.folderId,
|
||||||
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
|
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
|
||||||
|
@ -201,8 +215,8 @@ export class DriveFileEntityService {
|
||||||
isSensitive: file.isSensitive,
|
isSensitive: file.isSensitive,
|
||||||
blurhash: file.blurhash,
|
blurhash: file.blurhash,
|
||||||
properties: opts.self ? file.properties : this.getPublicProperties(file),
|
properties: opts.self ? file.properties : this.getPublicProperties(file),
|
||||||
url: opts.self ? file.url : this.getPublicUrl(file, false),
|
url: opts.self ? file.url : this.getPublicUrl(file),
|
||||||
thumbnailUrl: this.getPublicUrl(file, true),
|
thumbnailUrl: this.getPublicUrl(file, 'static'),
|
||||||
comment: file.comment,
|
comment: file.comment,
|
||||||
folderId: file.folderId,
|
folderId: file.folderId,
|
||||||
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
|
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
|
||||||
|
|
|
@ -314,10 +314,10 @@ export class UserEntityService implements OnModuleInit {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getAvatarUrl(user: User): Promise<string> {
|
public async getAvatarUrl(user: User): Promise<string> {
|
||||||
if (user.avatar) {
|
if (user.avatar) {
|
||||||
return this.driveFileEntityService.getPublicUrl(user.avatar, true) ?? this.getIdenticonUrl(user.id);
|
return this.driveFileEntityService.getPublicUrl(user.avatar, 'avatar') ?? this.getIdenticonUrl(user.id);
|
||||||
} else if (user.avatarId) {
|
} else if (user.avatarId) {
|
||||||
const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId });
|
const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId });
|
||||||
return this.driveFileEntityService.getPublicUrl(avatar, true) ?? this.getIdenticonUrl(user.id);
|
return this.driveFileEntityService.getPublicUrl(avatar, 'avatar') ?? this.getIdenticonUrl(user.id);
|
||||||
} else {
|
} else {
|
||||||
return this.getIdenticonUrl(user.id);
|
return this.getIdenticonUrl(user.id);
|
||||||
}
|
}
|
||||||
|
@ -326,7 +326,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
@bindThis
|
@bindThis
|
||||||
public getAvatarUrlSync(user: User): string {
|
public getAvatarUrlSync(user: User): string {
|
||||||
if (user.avatar) {
|
if (user.avatar) {
|
||||||
return this.driveFileEntityService.getPublicUrl(user.avatar, true) ?? this.getIdenticonUrl(user.id);
|
return this.driveFileEntityService.getPublicUrl(user.avatar, 'avatar') ?? this.getIdenticonUrl(user.id);
|
||||||
} else {
|
} else {
|
||||||
return this.getIdenticonUrl(user.id);
|
return this.getIdenticonUrl(user.id);
|
||||||
}
|
}
|
||||||
|
@ -422,7 +422,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
createdAt: user.createdAt.toISOString(),
|
createdAt: user.createdAt.toISOString(),
|
||||||
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
|
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
|
||||||
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
|
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
|
||||||
bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner, false) : null,
|
bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner) : null,
|
||||||
bannerBlurhash: user.banner?.blurhash ?? null,
|
bannerBlurhash: user.banner?.blurhash ?? null,
|
||||||
isLocked: user.isLocked,
|
isLocked: user.isLocked,
|
||||||
isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
|
isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
|
||||||
|
|
|
@ -137,38 +137,38 @@ export class FileServerService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (file.state === 'remote') {
|
if (file.state === 'remote') {
|
||||||
const convertFile = async () => {
|
let image: IImageStreamable | null = null;
|
||||||
if (file.fileRole === 'thumbnail') {
|
|
||||||
if (['image/jpeg', 'image/webp', 'image/avif', 'image/png', 'image/svg+xml'].includes(file.mime)) {
|
|
||||||
return this.imageProcessingService.convertToWebpStream(
|
|
||||||
file.path,
|
|
||||||
498,
|
|
||||||
280
|
|
||||||
);
|
|
||||||
} else if (file.mime.startsWith('video/')) {
|
|
||||||
return await this.videoProcessingService.generateVideoThumbnail(file.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.fileRole === 'webpublic') {
|
if (file.fileRole === 'thumbnail') {
|
||||||
if (['image/svg+xml'].includes(file.mime)) {
|
if (isMimeImage(file.mime, 'sharp-convertible-image')) {
|
||||||
return this.imageProcessingService.convertToWebpStream(
|
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
||||||
file.path,
|
|
||||||
2048,
|
|
||||||
2048,
|
|
||||||
{ ...webpDefault, lossless: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
const url = new URL(`${this.config.mediaProxy}/static.webp`);
|
||||||
|
url.searchParams.set('url', file.url);
|
||||||
|
url.searchParams.set('static', '1');
|
||||||
|
return await reply.redirect(301, url.toString());
|
||||||
|
} else if (file.mime.startsWith('video/')) {
|
||||||
|
image = await this.videoProcessingService.generateVideoThumbnail(file.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.fileRole === 'webpublic') {
|
||||||
|
if (['image/svg+xml'].includes(file.mime)) {
|
||||||
|
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
||||||
|
|
||||||
|
const url = new URL(`${this.config.mediaProxy}/svg.webp`);
|
||||||
|
url.searchParams.set('url', file.url);
|
||||||
|
return await reply.redirect(301, url.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image) {
|
||||||
|
image = {
|
||||||
data: fs.createReadStream(file.path),
|
data: fs.createReadStream(file.path),
|
||||||
ext: file.ext,
|
ext: file.ext,
|
||||||
type: file.mime,
|
type: file.mime,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const image = await convertFile();
|
|
||||||
|
|
||||||
if ('pipe' in image.data && typeof image.data.pipe === 'function') {
|
if ('pipe' in image.data && typeof image.data.pipe === 'function') {
|
||||||
// image.dataがstreamなら、stream終了後にcleanup
|
// image.dataがstreamなら、stream終了後にcleanup
|
||||||
|
@ -180,7 +180,6 @@ export class FileServerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
|
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
|
||||||
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
|
||||||
return image.data;
|
return image.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,6 +216,23 @@ export class FileServerService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.config.externalMediaProxyEnabled) {
|
||||||
|
// 外部のメディアプロキシが有効なら、そちらにリダイレクト
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'public, max-age=259200'); // 3 days
|
||||||
|
|
||||||
|
const url = new URL(`${this.config.mediaProxy}/${request.params.url || ''}`);
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(request.query)) {
|
||||||
|
url.searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await reply.redirect(
|
||||||
|
301,
|
||||||
|
url.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const file = await this.getStreamAndTypeFromUrl(url);
|
const file = await this.getStreamAndTypeFromUrl(url);
|
||||||
if (file === '404') {
|
if (file === '404') {
|
||||||
|
@ -236,7 +252,7 @@ export class FileServerService {
|
||||||
const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image');
|
const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image');
|
||||||
|
|
||||||
let image: IImageStreamable | null = null;
|
let image: IImageStreamable | null = null;
|
||||||
if ('emoji' in request.query && isConvertibleImage) {
|
if (('emoji' in request.query || 'avatar' in request.query) && isConvertibleImage) {
|
||||||
if (!isAnimationConvertibleImage && !('static' in request.query)) {
|
if (!isAnimationConvertibleImage && !('static' in request.query)) {
|
||||||
image = {
|
image = {
|
||||||
data: fs.createReadStream(file.path),
|
data: fs.createReadStream(file.path),
|
||||||
|
@ -246,7 +262,7 @@ export class FileServerService {
|
||||||
} else {
|
} else {
|
||||||
const data = sharp(file.path, { animated: !('static' in request.query) })
|
const data = sharp(file.path, { animated: !('static' in request.query) })
|
||||||
.resize({
|
.resize({
|
||||||
height: 128,
|
height: 'emoji' in request.query ? 128 : 320,
|
||||||
withoutEnlargement: true,
|
withoutEnlargement: true,
|
||||||
})
|
})
|
||||||
.webp(webpDefault);
|
.webp(webpDefault);
|
||||||
|
@ -370,7 +386,7 @@ export class FileServerService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async getFileFromKey(key: string): Promise<
|
private async getFileFromKey(key: string): Promise<
|
||||||
{ state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: DriveFile; mime: string; ext: string | null; path: string; cleanup: () => void; }
|
{ state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: DriveFile; url: string; mime: string; ext: string | null; path: string; cleanup: () => void; }
|
||||||
| { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: DriveFile; mime: string; ext: string | null; path: string; }
|
| { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: DriveFile; mime: string; ext: string | null; path: string; }
|
||||||
| '404'
|
| '404'
|
||||||
| '204'
|
| '204'
|
||||||
|
@ -392,6 +408,7 @@ export class FileServerService {
|
||||||
const result = await this.downloadAndDetectTypeFromUrl(file.uri);
|
const result = await this.downloadAndDetectTypeFromUrl(file.uri);
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
|
url: file.uri,
|
||||||
fileRole: isThumbnail ? 'thumbnail' : isWebpublic ? 'webpublic' : 'original',
|
fileRole: isThumbnail ? 'thumbnail' : isWebpublic ? 'webpublic' : 'original',
|
||||||
file,
|
file,
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ export class ServerService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL('/proxy/emoji.webp', this.config.url);
|
const url = new URL(`${this.config.mediaProxy}/emoji.webp`);
|
||||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||||
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
|
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
|
||||||
url.searchParams.set('emoji', '1');
|
url.searchParams.set('emoji', '1');
|
||||||
|
|
|
@ -181,6 +181,10 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
mediaProxy: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
features: {
|
features: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: true, nullable: false,
|
optional: true, nullable: false,
|
||||||
|
@ -307,6 +311,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
|
||||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||||
|
|
||||||
|
mediaProxy: this.config.mediaProxy,
|
||||||
|
|
||||||
...(ps.detail ? {
|
...(ps.detail ? {
|
||||||
pinnedPages: instance.pinnedPages,
|
pinnedPages: instance.pinnedPages,
|
||||||
pinnedClipId: instance.pinnedClipId,
|
pinnedClipId: instance.pinnedClipId,
|
||||||
|
|
|
@ -33,7 +33,7 @@ export class UrlPreviewService {
|
||||||
private wrap(url?: string): string | null {
|
private wrap(url?: string): string | null {
|
||||||
return url != null
|
return url != null
|
||||||
? url.match(/^https?:\/\//)
|
? url.match(/^https?:\/\//)
|
||||||
? `${this.config.url}/proxy/preview.webp?${query({
|
? `${this.config.mediaProxy}/preview.webp?${query({
|
||||||
url,
|
url,
|
||||||
preview: '1',
|
preview: '1',
|
||||||
})}`
|
})}`
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { query, appendQuery } from '@/scripts/url';
|
import { query, appendQuery } from '@/scripts/url';
|
||||||
import { url } from '@/config';
|
import { url } from '@/config';
|
||||||
|
import { instance } from '@/instance';
|
||||||
|
|
||||||
export function getProxiedImageUrl(imageUrl: string, type?: 'preview'): string {
|
export function getProxiedImageUrl(imageUrl: string, type?: 'preview'): string {
|
||||||
if (imageUrl.startsWith(`${url}/proxy/`) || imageUrl.startsWith('/proxy/')) {
|
if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/')) {
|
||||||
// もう既にproxyっぽそうだったらsearchParams付けるだけ
|
// もう既にproxyっぽそうだったらsearchParams付けるだけ
|
||||||
return appendQuery(imageUrl, query({
|
return appendQuery(imageUrl, query({
|
||||||
fallback: '1',
|
fallback: '1',
|
||||||
|
@ -10,7 +11,7 @@ export function getProxiedImageUrl(imageUrl: string, type?: 'preview'): string {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${url}/proxy/image.webp?${query({
|
return `${instance.mediaProxy}/image.webp?${query({
|
||||||
url: imageUrl,
|
url: imageUrl,
|
||||||
fallback: '1',
|
fallback: '1',
|
||||||
...(type ? { [type]: '1' } : {}),
|
...(type ? { [type]: '1' } : {}),
|
||||||
|
@ -25,22 +26,19 @@ export function getProxiedImageUrlNullable(imageUrl: string | null | undefined,
|
||||||
export function getStaticImageUrl(baseUrl: string): string {
|
export function getStaticImageUrl(baseUrl: string): string {
|
||||||
const u = baseUrl.startsWith('http') ? new URL(baseUrl) : new URL(baseUrl, url);
|
const u = baseUrl.startsWith('http') ? new URL(baseUrl) : new URL(baseUrl, url);
|
||||||
|
|
||||||
if (u.href.startsWith(`${url}/proxy/`)) {
|
|
||||||
// もう既にproxyっぽそうだったらsearchParams付けるだけ
|
|
||||||
u.searchParams.set('static', '1');
|
|
||||||
return u.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u.href.startsWith(`${url}/emoji/`)) {
|
if (u.href.startsWith(`${url}/emoji/`)) {
|
||||||
// もう既にemojiっぽそうだったらsearchParams付けるだけ
|
// もう既にemojiっぽそうだったらsearchParams付けるだけ
|
||||||
u.searchParams.set('static', '1');
|
u.searchParams.set('static', '1');
|
||||||
return u.href;
|
return u.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拡張子がないとキャッシュしてくれないCDNがあるのでダミーの名前を指定する
|
if (u.href.startsWith(instance.mediaProxy + '/')) {
|
||||||
const dummy = `${encodeURIComponent(`${u.host}${u.pathname}`)}.webp`;
|
// もう既にproxyっぽそうだったらsearchParams付けるだけ
|
||||||
|
u.searchParams.set('static', '1');
|
||||||
|
return u.href;
|
||||||
|
}
|
||||||
|
|
||||||
return `${url}/proxy/${dummy}?${query({
|
return `${instance.mediaProxy}/static.webp?${query({
|
||||||
url: u.href,
|
url: u.href,
|
||||||
static: '1',
|
static: '1',
|
||||||
})}`;
|
})}`;
|
||||||
|
|
Loading…
Reference in a new issue