Skip to content

Commit

Permalink
Support Remote Avatar Decoration view
Browse files Browse the repository at this point in the history
  • Loading branch information
caipira113 committed Nov 8, 2023
1 parent f143242 commit 7891331
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 4 deletions.
13 changes: 13 additions & 0 deletions packages/backend/migration/1699432324194-remoteAvaterDecoration.js
Original file line number Diff line number Diff line change
@@ -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"`);
}
}
105 changes: 102 additions & 3 deletions packages/backend/src/core/AvatarDecorationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,43 @@

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';
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);

Expand Down Expand Up @@ -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 });
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -100,6 +101,8 @@ export class ApPersonService implements OnModuleInit {

@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,

private avatarDecorationService: AvatarDecorationService,
) {
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/core/entities/UserEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions packages/backend/src/models/AvatarDecoration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit 7891331

Please sign in to comment.