1
1
mirror of https://github.com/kokonect-link/cherrypick synced 2024-11-27 14:28:53 +09:00

Support Remote Avatar Decoration view

This commit is contained in:
caipira113 2023-11-08 22:40:07 +09:00 committed by NoriDev
parent d96dc193fe
commit 4b991190cf
5 changed files with 133 additions and 4 deletions

View File

@ -0,0 +1,13 @@
export class RemoteAvaterDecoration1699432324194 {
name = 'RemoteAvaterDecoration1699432324194'
async up(queryRunner) {
queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "remoteId" varchar(32)`);
queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "host" varchar(128)`);
}
async down(queryRunner) {
queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "host"`);
queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "remoteId"`);
}
}

View File

@ -5,7 +5,7 @@
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { AvatarDecorationsRepository, MiAvatarDecoration, MiUser } from '@/models/_.js';
import type { AvatarDecorationsRepository, InstancesRepository, UsersRepository, MiAvatarDecoration, MiUser } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
@ -13,21 +13,35 @@ import { bindThis } from '@/decorators.js';
import { MemorySingleCache } from '@/misc/cache.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { HttpRequestService } from "@/core/HttpRequestService.js";
import { appendQuery, query } from '@/misc/prelude/url.js';
import type { Config } from '@/config.js';
import {IsNull} from "typeorm";
@Injectable()
export class AvatarDecorationService implements OnApplicationShutdown {
public cache: MemorySingleCache<MiAvatarDecoration[]>;
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
@Inject(DI.avatarDecorationsRepository)
private avatarDecorationsRepository: AvatarDecorationsRepository,
@Inject(DI.instancesRepository)
private instancesRepository: InstancesRepository,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private idService: IdService,
private moderationLogService: ModerationLogService,
private globalEventService: GlobalEventService,
private httpRequestService: HttpRequestService,
) {
this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30);
@ -94,6 +108,87 @@ export class AvatarDecorationService implements OnApplicationShutdown {
}
}
@bindThis
private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string {
return appendQuery(
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
query({
url,
...(mode ? { [mode]: '1' } : {}),
}),
);
}
@bindThis
public async remoteUserUpdate(user: MiUser) {
const userHost = user.host ?? '';
const instance = await this.instancesRepository.findOneBy({ host: userHost });
const userHostUrl = `https://${user.host}`;
const showUserApiUrl = `${userHostUrl}/api/users/show`;
if (instance?.softwareName !== 'misskey' && instance?.softwareName !== 'cherrypick') {
return;
}
const res = await this.httpRequestService.send(showUserApiUrl, {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ "username": user.username }),
});
const userData: any = await res.json();
const avatarDecorations = userData.avatarDecorations[0];
if (avatarDecorations != null) {
const avatarDecorationId = avatarDecorations.id;
const instanceHost = instance?.host;
const decorationApiUrl = `https://${instanceHost}/api/get-avatar-decorations`;
const allRes = await this.httpRequestService.send(decorationApiUrl, {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
});
const allDecorations: any = await allRes.json();
let name;
let description;
for (const decoration of allDecorations) {
if (decoration.id == avatarDecorationId) {
name = decoration.name;
description = decoration.description;
break;
}
}
const existingDecoration = await this.avatarDecorationsRepository.findOneBy({ host: userHost, remoteId: avatarDecorationId });
const decorationData = {
name: name,
description: description,
url: this.getProxiedUrl(avatarDecorations.url, 'static'),
remoteId: avatarDecorationId,
host: userHost,
};
if (existingDecoration == null) {
await this.create(decorationData);
} else {
await this.update(existingDecoration.id, decorationData);
}
const findDecoration = await this.avatarDecorationsRepository.findOneBy({ host: userHost, remoteId: avatarDecorationId });
const updates = {} as Partial<MiUser>;
updates.avatarDecorations = [{
id: findDecoration?.id ?? '',
angle: avatarDecorations.angle ?? 0,
flipH: avatarDecorations.flipH ?? false,
}];
await this.usersRepository.update({ id: user.id }, updates);
}
}
@bindThis
public async delete(id: MiAvatarDecoration['id'], moderator?: MiUser): Promise<void> {
const avatarDecoration = await this.avatarDecorationsRepository.findOneByOrFail({ id });
@ -110,11 +205,15 @@ export class AvatarDecorationService implements OnApplicationShutdown {
}
@bindThis
public async getAll(noCache = false): Promise<MiAvatarDecoration[]> {
public async getAll(noCache = false, withRemote = false): Promise<MiAvatarDecoration[]> {
if (noCache) {
this.cache.delete();
}
return this.cache.fetch(() => this.avatarDecorationsRepository.find());
if (!withRemote) {
return this.cache.fetch(() => this.avatarDecorationsRepository.find({ where: { host: IsNull() } }));
} else {
return this.cache.fetch(() => this.avatarDecorationsRepository.find());
}
}
@bindThis

View File

@ -48,6 +48,7 @@ import type { ApLoggerService } from '../ApLoggerService.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type { ApImageService } from './ApImageService.js';
import type { IActor, IObject } from '../type.js';
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
const nameLength = 128;
const summaryLength = 2048;
@ -100,6 +101,8 @@ export class ApPersonService implements OnModuleInit {
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
private avatarDecorationService: AvatarDecorationService,
) {
}
@ -462,6 +465,8 @@ export class ApPersonService implements OnModuleInit {
// ハッシュタグ更新
this.hashtagService.updateUsertags(user, tags);
this.avatarDecorationService.remoteUserUpdate(user);
//#region アバターとヘッダー画像をフェッチ
try {
const updates = await this.resolveAvatarAndBanner(user, person.icon, person.image);
@ -639,6 +644,8 @@ export class ApPersonService implements OnModuleInit {
if (moving) updates.movedAt = new Date();
// Update user
const user = await this.usersRepository.findOneByOrFail({ id: exist.id });
await this.avatarDecorationService.remoteUserUpdate(user);
await this.usersRepository.update(exist.id, updates);
if (person.publicKey) {

View File

@ -394,7 +394,7 @@ export class UserEntityService implements OnModuleInit {
host: user.host,
avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user),
avatarBlurhash: user.avatarBlurhash,
avatarDecorations: user.avatarDecorations.length > 0 ? this.avatarDecorationService.getAll().then(decorations => user.avatarDecorations.filter(ud => decorations.some(d => d.id === ud.id)).map(ud => ({
avatarDecorations: user.avatarDecorations.length > 0 ? this.avatarDecorationService.getAll(false, true).then(decorations => user.avatarDecorations.filter(ud => decorations.some(d => d.id === ud.id)).map(ud => ({
id: ud.id,
angle: ud.angle || undefined,
flipH: ud.flipH || undefined,

View File

@ -36,4 +36,14 @@ export class MiAvatarDecoration {
array: true, length: 128, default: '{}',
})
public roleIdsThatCanBeUsedThisDecoration: string[];
@Column('varchar', {
length: 32,
})
public remoteId: string;
@Column('varchar', {
length: 128, nullable: true
})
public host: string | null;
}