diff --git a/CHANGELOG_YOJO.md b/CHANGELOG_YOJO.md index 72c2e6f198..2e63c9ded7 100644 --- a/CHANGELOG_YOJO.md +++ b/CHANGELOG_YOJO.md @@ -12,9 +12,10 @@ Cherrypick 4.11.1 - Enhance: チャートの連合グラフで割合を表示 - Enhance: お気に入り登録クリップの一覧画面から登録解除できるように - Fix: リモートから添付されてきたクリップURLにホスト情報があると二重になる不具合を修正 [#460](https://github.com/yojo-art/cherrypick/pull/460) +- Fix: リモートクリップ説明文がローカル仕様になってる問題の修正 [#466](https://github.com/yojo-art/cherrypick/pull/466) ### Server -- +- Enhance: リモートユーザーの`/api/clips/show`と`/api/users/clips`の応答にemojisを追加 [#466](https://github.com/yojo-art/cherrypick/pull/466) ### Misc diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts index 0ed580f399..5f89575a81 100644 --- a/packages/backend/src/core/ClipService.ts +++ b/packages/backend/src/core/ClipService.ts @@ -20,6 +20,7 @@ import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; import { Packed } from '@/misc/json-schema.js'; +import { emojis } from '@/misc/remote-api-utils.js'; @Injectable() export class ClipService { @@ -201,6 +202,7 @@ export class ClipService { public async showRemote( clipId:string, host:string, + fetch_emoji = false, ) : Promise> { const cache_key = 'clip:show:' + clipId + '@' + host; const cache_value = await this.redisForRemoteApis.get(cache_key); @@ -263,6 +265,7 @@ export class ClipService { favoritedCount: remote_clip.favoritedCount, isFavorited: false, notesCount: remote_clip.notesCount, + emojis: (remote_clip.description && fetch_emoji) ? emojis(this.config, this.httpRequestService, this.redisForRemoteApis, host, remote_clip.description) : {}, }); } } diff --git a/packages/backend/src/misc/remote-api-utils.ts b/packages/backend/src/misc/remote-api-utils.ts index bd8ad1d82b..256994a42a 100644 --- a/packages/backend/src/misc/remote-api-utils.ts +++ b/packages/backend/src/misc/remote-api-utils.ts @@ -17,6 +17,81 @@ export type FetchRemoteApiOpts={ untilId?:string, }; +export async function emojis( + config: Config, + httpRequestService: HttpRequestService, + redisForRemoteApis: Redis.Redis, + host: string, + text:string, +):Promise<{[k: string]: string}> { + const emojis = new Map(); + const remote_emojis = await fetch_remote_emojis(config, httpRequestService, redisForRemoteApis, host); + for (const [key, value] of remote_emojis) { + const name = ':' + key + ':'; + if (text.indexOf(name) !== -1) { + emojis.set(key, value); + } + } + return Object.fromEntries(emojis); +} + +export async function fetch_remote_emojis( + config: Config, + httpRequestService: HttpRequestService, + redisForRemoteApis: Redis.Redis, + host: string, +):Promise> { + const cache_key = 'emojis:' + host; + const cache_value = await redisForRemoteApis.get(cache_key); + if (cache_value !== null) { + //ステータス格納 + if (cache_value.startsWith('__')) { + if (cache_value === '__SKIP_FETCH') return new Map(); + //未定義のステータス + return new Map(); + } + return new Map(Object.entries(JSON.parse(cache_value))); + } + const url = 'https://' + host + '/api/emojis'; + const timeout = 30 * 1000; + const operationTimeout = 60 * 1000; + const res = got.get(url, { + headers: { + 'User-Agent': config.userAgent, + }, + timeout: { + lookup: timeout, + connect: timeout, + secureConnect: timeout, + socket: timeout, // read timeout + response: timeout, + send: timeout, + request: operationTimeout, // whole operation timeout + }, + agent: { + http: httpRequestService.httpAgent, + https: httpRequestService.httpsAgent, + }, + http2: true, + retry: { + limit: 2, + }, + enableUnixSockets: false, + }); + const text = await res.text(); + const array = JSON.parse(text)?.emojis; + const parsed = new Map(); + if (Array.isArray(array)) { + for (const entry of array) { + parsed.set(entry.name, entry.url); + } + } + const redisPipeline = redisForRemoteApis.pipeline(); + redisPipeline.set(cache_key, JSON.stringify(Object.fromEntries(parsed))); + redisPipeline.expire(cache_key, 60 * 60); + await redisPipeline.exec(); + return parsed; +} export async function fetch_remote_api( config: Config, httpRequestService: HttpRequestService, host: string, endpoint: string, opts: FetchRemoteApiOpts, ) { diff --git a/packages/backend/src/models/json-schema/clip.ts b/packages/backend/src/models/json-schema/clip.ts index c4e7055cd8..7eebe51a04 100644 --- a/packages/backend/src/models/json-schema/clip.ts +++ b/packages/backend/src/models/json-schema/clip.ts @@ -56,5 +56,14 @@ export const packedClipSchema = { type: 'integer', optional: true, nullable: false, }, + emojis: { + type: 'object', + optional: true, nullable: false, + additionalProperties: { + anyOf: [{ + type: 'string', + }], + }, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index c26c368ddb..224b0aa9ce 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -67,7 +67,8 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { const parsed_id = ps.clipId.split('@'); if (parsed_id.length === 2 ) {//is remote - const clip = await clipService.showRemote(parsed_id[0], parsed_id[1]).catch(err => { + const clip = await clipService.showRemote(parsed_id[0], parsed_id[1], true).catch(err => { + console.error(err); throw new ApiError(meta.errors.failedToResolveRemoteUser); }); if (me) { diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 281a2ccbe4..d315d21850 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -14,7 +14,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { Config } from '@/config.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import { fetch_remote_api, fetch_remote_user_id } from '@/misc/remote-api-utils.js'; +import { emojis, fetch_remote_api, fetch_remote_user_id } from '@/misc/remote-api-utils.js'; import type { FindOptionsWhere } from 'typeorm'; export const meta = { @@ -127,6 +127,7 @@ async function remote( favoritedCount: remote_clip.favoritedCount, isFavorited: false, notesCount: remote_clip.notesCount, + emojis: remote_clip.description ? emojis(config, httpRequestService, redisForRemoteApis, user.host, remote_clip.description) : {}, }); clips.push(clip); } diff --git a/packages/cherrypick-js/src/autogen/types.ts b/packages/cherrypick-js/src/autogen/types.ts index 53bed6dda6..3a4efcf82b 100644 --- a/packages/cherrypick-js/src/autogen/types.ts +++ b/packages/cherrypick-js/src/autogen/types.ts @@ -4901,6 +4901,9 @@ export type components = { favoritedCount: number; isFavorited?: boolean; notesCount?: number; + emojis?: { + [key: string]: string; + }; }; FederationInstance: { /** Format: id */ diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index 54b637c96b..6fc47829ce 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
({{ i18n.ts.noDescription }})