Skip to content

Commit 93eccf0

Browse files
jbblilyyunochicaipira113
authored
2024.8.0+monster1 (#10)
* ํฐํŠธ ๋ณ€๊ฒฝ(suite, zen maru gothic) * Relay GTL patch * ๋ฒ„์ „๋ช…๋ช…(2024.8.0+monster) * Support Remote Avatar Decoration view + fix 7891331 + fix !avatarDecorations * Remote Avatar ๋ฐ์ฝ”๋ ˆ์ด์…˜์šฉ ์บ์‹œ * Avatar decoration ์—ฐํ•ฉ์— offsetX, offsetY ์ถ”๊ฐ€ * ๋ฆฌ๋ชจํŠธ ์œ ์ €์˜ ์—ฌ๋Ÿฌ ์•„๋ฐ”ํƒ€ ์žฅ์‹ ์—ฐํ•ฉ ์ง€์› * ๋ฒ„์ „๋ช…๋ช…(2024.8.0+monster1) --------- Co-authored-by: Yunochi <yuno@yunochi.com> Co-authored-by: caipira113 <caipira@libnare.net>
1 parent d316fc8 commit 93eccf0

File tree

6 files changed

+149
-5
lines changed

6 files changed

+149
-5
lines changed

โ€Žpackage.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "misskey",
3-
"version": "2024.8.0+monster",
3+
"version": "2024.8.0+monster1",
44
"codename": "nasubi",
55
"repository": {
66
"type": "git",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export class RemoteAvaterDecoration1699432324194 {
2+
name = 'RemoteAvaterDecoration1699432324194'
3+
4+
async up(queryRunner) {
5+
queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "remoteId" varchar(32)`);
6+
queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "host" varchar(128)`);
7+
}
8+
9+
async down(queryRunner) {
10+
queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "host"`);
11+
queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "remoteId"`);
12+
}
13+
}

โ€Žpackages/backend/src/core/AvatarDecorationService.ts

+117-3
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,47 @@
55

66
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
77
import * as Redis from 'ioredis';
8-
import type { AvatarDecorationsRepository, MiAvatarDecoration, MiUser } from '@/models/_.js';
8+
import type { AvatarDecorationsRepository, InstancesRepository, UsersRepository, MiAvatarDecoration, MiUser } from '@/models/_.js';
99
import { IdService } from '@/core/IdService.js';
1010
import { GlobalEventService } from '@/core/GlobalEventService.js';
1111
import { DI } from '@/di-symbols.js';
1212
import { bindThis } from '@/decorators.js';
1313
import { MemorySingleCache } from '@/misc/cache.js';
1414
import type { GlobalEvents } from '@/core/GlobalEventService.js';
1515
import { ModerationLogService } from '@/core/ModerationLogService.js';
16+
import { HttpRequestService } from "@/core/HttpRequestService.js";
17+
import { appendQuery, query } from '@/misc/prelude/url.js';
18+
import type { Config } from '@/config.js';
19+
import {IsNull} from "typeorm";
1620

1721
@Injectable()
1822
export class AvatarDecorationService implements OnApplicationShutdown {
1923
public cache: MemorySingleCache<MiAvatarDecoration[]>;
24+
public cacheWithRemote: MemorySingleCache<MiAvatarDecoration[]>;
2025

2126
constructor(
27+
@Inject(DI.config)
28+
private config: Config,
29+
2230
@Inject(DI.redisForSub)
2331
private redisForSub: Redis.Redis,
2432

2533
@Inject(DI.avatarDecorationsRepository)
2634
private avatarDecorationsRepository: AvatarDecorationsRepository,
2735

36+
@Inject(DI.instancesRepository)
37+
private instancesRepository: InstancesRepository,
38+
39+
@Inject(DI.usersRepository)
40+
private usersRepository: UsersRepository,
41+
2842
private idService: IdService,
2943
private moderationLogService: ModerationLogService,
3044
private globalEventService: GlobalEventService,
45+
private httpRequestService: HttpRequestService,
3146
) {
3247
this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30); // 30s
48+
this.cacheWithRemote = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30);
3349

3450
this.redisForSub.on('message', this.onMessage);
3551
}
@@ -94,6 +110,99 @@ export class AvatarDecorationService implements OnApplicationShutdown {
94110
}
95111
}
96112

113+
@bindThis
114+
private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string {
115+
return appendQuery(
116+
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
117+
query({
118+
url,
119+
...(mode ? { [mode]: '1' } : {}),
120+
}),
121+
);
122+
}
123+
124+
@bindThis
125+
public async remoteUserUpdate(user: MiUser) {
126+
const userHost = user.host ?? '';
127+
const instance = await this.instancesRepository.findOneBy({ host: userHost });
128+
const userHostUrl = `https://${user.host}`;
129+
const showUserApiUrl = `${userHostUrl}/api/users/show`;
130+
131+
if (instance?.softwareName !== 'misskey' && instance?.softwareName !== 'cherrypick') {
132+
return;
133+
}
134+
135+
const res = await this.httpRequestService.send(showUserApiUrl, {
136+
method: 'POST',
137+
headers: { "Content-Type": "application/json" },
138+
body: JSON.stringify({ "username": user.username }),
139+
});
140+
141+
const userData: any = await res.json();
142+
const userAvatarDecorations = userData.avatarDecorations ?? undefined;
143+
144+
if (!userAvatarDecorations || userAvatarDecorations.length === 0) {
145+
const updates = {} as Partial<MiUser>;
146+
updates.avatarDecorations = [];
147+
await this.usersRepository.update({id: user.id}, updates);
148+
return;
149+
}
150+
151+
const instanceHost = instance?.host;
152+
const decorationApiUrl = `https://${instanceHost}/api/get-avatar-decorations`;
153+
const allRes = await this.httpRequestService.send(decorationApiUrl, {
154+
method: 'POST',
155+
headers: {"Content-Type": "application/json"},
156+
body: JSON.stringify({}),
157+
});
158+
const allDecorations: any = await allRes.json();
159+
const updates = {} as Partial<MiUser>;
160+
updates.avatarDecorations = [];
161+
for (const avatarDecoration of userAvatarDecorations) {
162+
let name;
163+
let description;
164+
const avatarDecorationId = avatarDecoration.id
165+
for (const decoration of allDecorations) {
166+
if (decoration.id == avatarDecorationId) {
167+
name = decoration.name;
168+
description = decoration.description;
169+
break;
170+
}
171+
}
172+
const existingDecoration = await this.avatarDecorationsRepository.findOneBy({
173+
host: userHost,
174+
remoteId: avatarDecorationId
175+
});
176+
const decorationData = {
177+
name: name,
178+
description: description,
179+
url: this.getProxiedUrl(avatarDecoration.url, 'static'),
180+
remoteId: avatarDecorationId,
181+
host: userHost,
182+
};
183+
if (existingDecoration == null) {
184+
await this.create(decorationData);
185+
this.cacheWithRemote.delete();
186+
} else {
187+
await this.update(existingDecoration.id, decorationData);
188+
this.cacheWithRemote.delete();
189+
}
190+
const findDecoration = await this.avatarDecorationsRepository.findOneBy({
191+
host: userHost,
192+
remoteId: avatarDecorationId
193+
});
194+
195+
updates.avatarDecorations.push({
196+
id: findDecoration?.id ?? '',
197+
angle: avatarDecoration.angle ?? 0,
198+
flipH: avatarDecoration.flipH ?? false,
199+
offsetX: avatarDecoration.offsetX ?? 0,
200+
offsetY: avatarDecoration.offsetY ?? 0,
201+
});
202+
}
203+
await this.usersRepository.update({id: user.id}, updates);
204+
}
205+
97206
@bindThis
98207
public async delete(id: MiAvatarDecoration['id'], moderator?: MiUser): Promise<void> {
99208
const avatarDecoration = await this.avatarDecorationsRepository.findOneByOrFail({ id });
@@ -110,11 +219,16 @@ export class AvatarDecorationService implements OnApplicationShutdown {
110219
}
111220

112221
@bindThis
113-
public async getAll(noCache = false): Promise<MiAvatarDecoration[]> {
222+
public async getAll(noCache = false, withRemote = false): Promise<MiAvatarDecoration[]> {
114223
if (noCache) {
115224
this.cache.delete();
225+
this.cacheWithRemote.delete();
226+
}
227+
if (!withRemote) {
228+
return this.cache.fetch(() => this.avatarDecorationsRepository.find({ where: { host: IsNull() } }));
229+
} else {
230+
return this.cacheWithRemote.fetch(() => this.avatarDecorationsRepository.find());
116231
}
117-
return this.cache.fetch(() => this.avatarDecorationsRepository.find());
118232
}
119233

120234
@bindThis

โ€Žpackages/backend/src/core/activitypub/models/ApPersonService.ts

+7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import type { ApLoggerService } from '../ApLoggerService.js';
4949
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
5050
import type { ApImageService } from './ApImageService.js';
5151
import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js';
52+
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
5253

5354
const nameLength = 128;
5455
const summaryLength = 2048;
@@ -103,6 +104,8 @@ export class ApPersonService implements OnModuleInit {
103104
private followingsRepository: FollowingsRepository,
104105

105106
private roleService: RoleService,
107+
108+
private avatarDecorationService: AvatarDecorationService,
106109
) {
107110
}
108111

@@ -420,6 +423,8 @@ export class ApPersonService implements OnModuleInit {
420423
// ใƒใƒƒใ‚ทใƒฅใ‚ฟใ‚ฐๆ›ดๆ–ฐ
421424
this.hashtagService.updateUsertags(user, tags);
422425

426+
this.avatarDecorationService.remoteUserUpdate(user);
427+
423428
//#region ใ‚ขใƒใ‚ฟใƒผใจใƒ˜ใƒƒใƒ€ใƒผ็”ปๅƒใ‚’ใƒ•ใ‚งใƒƒใƒ
424429
try {
425430
const updates = await this.resolveAvatarAndBanner(user, person.icon, person.image);
@@ -545,6 +550,8 @@ export class ApPersonService implements OnModuleInit {
545550
if (moving) updates.movedAt = new Date();
546551

547552
// Update user
553+
const user = await this.usersRepository.findOneByOrFail({ id: exist.id });
554+
await this.avatarDecorationService.remoteUserUpdate(user);
548555
await this.usersRepository.update(exist.id, updates);
549556

550557
if (person.publicKey) {

โ€Žpackages/backend/src/core/entities/UserEntityService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ export class UserEntityService implements OnModuleInit {
480480
host: user.host,
481481
avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user),
482482
avatarBlurhash: user.avatarBlurhash,
483-
avatarDecorations: user.avatarDecorations.length > 0 ? this.avatarDecorationService.getAll().then(decorations => user.avatarDecorations.filter(ud => decorations.some(d => d.id === ud.id)).map(ud => ({
483+
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 => ({
484484
id: ud.id,
485485
angle: ud.angle || undefined,
486486
flipH: ud.flipH || undefined,

โ€Žpackages/backend/src/models/AvatarDecoration.ts

+10
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,14 @@ export class MiAvatarDecoration {
3636
array: true, length: 128, default: '{}',
3737
})
3838
public roleIdsThatCanBeUsedThisDecoration: string[];
39+
40+
@Column('varchar', {
41+
length: 32,
42+
})
43+
public remoteId: string;
44+
45+
@Column('varchar', {
46+
length: 128, nullable: true
47+
})
48+
public host: string | null;
3949
}

0 commit comments

Comments
ย (0)