Feat:アバターデコレーションを連合するように
Signed-off-by: mattyatea <mattyacocacora0@gmail.com>
This commit is contained in:
parent
4dd4a11cef
commit
bfd817ae10
|
@ -0,0 +1,13 @@
|
||||||
|
export class Avatardecoration31698323592283 {
|
||||||
|
name = 'Avatardecoration31698323592283'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "host" character varying(128)`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_3f8079d448095b8d867d318d12" ON "avatar_decoration" ("host") `);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_3f8079d448095b8d867d318d12"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "host"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,11 +26,14 @@ import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
||||||
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js';
|
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
import { isNotNull } from '@/misc/is-not-null.js';
|
import { isNotNull } from '@/misc/is-not-null.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { MiAvatarDecoration } from '@/models/_.js';
|
||||||
import { LdSignatureService } from './LdSignatureService.js';
|
import { LdSignatureService } from './LdSignatureService.js';
|
||||||
import { ApMfmService } from './ApMfmService.js';
|
import { ApMfmService } from './ApMfmService.js';
|
||||||
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
|
import type { IAccept, IActivity, IAdd, IAnnounce,
|
||||||
|
IApAvatarDecoration, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApRendererService {
|
export class ApRendererService {
|
||||||
|
@ -54,6 +57,7 @@ export class ApRendererService {
|
||||||
private pollsRepository: PollsRepository,
|
private pollsRepository: PollsRepository,
|
||||||
|
|
||||||
private customEmojiService: CustomEmojiService,
|
private customEmojiService: CustomEmojiService,
|
||||||
|
private avatarDecorationService: AvatarDecorationService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private driveFileEntityService: DriveFileEntityService,
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
private ldSignatureService: LdSignatureService,
|
private ldSignatureService: LdSignatureService,
|
||||||
|
@ -184,7 +188,19 @@ export class ApRendererService {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@bindThis
|
||||||
|
public renderAvatarDecoration(avatarDecoration: MiAvatarDecoration): IApAvatarDecoration {
|
||||||
|
return {
|
||||||
|
id: avatarDecoration.url,
|
||||||
|
type: 'AvatarDecoration',
|
||||||
|
name: avatarDecoration.name,
|
||||||
|
updated: avatarDecoration.updatedAt != null ? avatarDecoration.updatedAt.toISOString() : new Date().toISOString(),
|
||||||
|
icon: {
|
||||||
|
type: 'Image',
|
||||||
|
url: avatarDecoration.url,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
// to anonymise reporters, the reporting actor must be a system user
|
// to anonymise reporters, the reporting actor must be a system user
|
||||||
@bindThis
|
@bindThis
|
||||||
public renderFlag(user: MiLocalUser, object: IObject | string, content: string): IFlag {
|
public renderFlag(user: MiLocalUser, object: IObject | string, content: string): IFlag {
|
||||||
|
@ -472,11 +488,17 @@ export class ApRendererService {
|
||||||
const emojis = await this.getEmojis(user.emojis);
|
const emojis = await this.getEmojis(user.emojis);
|
||||||
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
|
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
|
||||||
|
|
||||||
|
const AvatarDecorations = await this.getAvatarDecorations(user.avatarDecorations);
|
||||||
|
const apAvatarDecorations = AvatarDecorations.map(decoration => this.renderAvatarDecoration(decoration));
|
||||||
|
|
||||||
const hashtagTags = user.tags.map(tag => this.renderHashtag(tag));
|
const hashtagTags = user.tags.map(tag => this.renderHashtag(tag));
|
||||||
|
|
||||||
|
AvatarDecorations.forEach((decoration, index) => user.avatarDecorations[index].id = decoration.name ); //デコレーションのidのところにnameを突っ込んでる(これ以外思いつかなかった)
|
||||||
|
|
||||||
const tag = [
|
const tag = [
|
||||||
...apemojis,
|
...apemojis,
|
||||||
...hashtagTags,
|
...hashtagTags,
|
||||||
|
...apAvatarDecorations,
|
||||||
];
|
];
|
||||||
|
|
||||||
const keypair = await this.userKeypairService.getUserKeypair(user.id);
|
const keypair = await this.userKeypairService.getUserKeypair(user.id);
|
||||||
|
@ -503,6 +525,7 @@ export class ApRendererService {
|
||||||
publicKey: this.renderKey(user, keypair, '#main-key'),
|
publicKey: this.renderKey(user, keypair, '#main-key'),
|
||||||
isCat: user.isCat,
|
isCat: user.isCat,
|
||||||
attachment: attachment.length ? attachment : undefined,
|
attachment: attachment.length ? attachment : undefined,
|
||||||
|
AvatarDecorations: user.avatarDecorations,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (user.movedToUri) {
|
if (user.movedToUri) {
|
||||||
|
@ -720,4 +743,12 @@ export class ApRendererService {
|
||||||
|
|
||||||
return emojis;
|
return emojis;
|
||||||
}
|
}
|
||||||
|
@bindThis
|
||||||
|
private async getAvatarDecorations(decorations: { id: string; angle: number; flipH: boolean }[]): Promise<MiAvatarDecoration[]> {
|
||||||
|
if (decorations.length === 0) return [];
|
||||||
|
const allAvatarDecorations = await this.avatarDecorationService.getAll();
|
||||||
|
//const matchingDecorations = allAvatarDecorations.find(item1 => decorations.some(item2 => item1.id === item2.id )).map;
|
||||||
|
const avatarDecorations = allAvatarDecorations.filter(item1 => decorations.some(item2 => item1.id === item2.id));
|
||||||
|
return avatarDecorations ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,13 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||||
import promiseLimit from 'promise-limit';
|
import promiseLimit from 'promise-limit';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
|
import type { PollsRepository, EmojisRepository, AvatarDecorationsRepository } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type { MiRemoteUser } from '@/models/User.js';
|
import type { MiRemoteUser } from '@/models/User.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
import type { MiNote } from '@/models/Note.js';
|
||||||
import { toArray, toSingle, unique } from '@/misc/prelude/array.js';
|
import { toArray, toSingle, unique } from '@/misc/prelude/array.js';
|
||||||
import type { MiEmoji } from '@/models/Emoji.js';
|
import type { MiEmoji } from '@/models/Emoji.js';
|
||||||
|
import type { MiAvatarDecoration } from '@/models/AvatarDecoration.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { AppLockService } from '@/core/AppLockService.js';
|
import { AppLockService } from '@/core/AppLockService.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
|
@ -24,7 +25,7 @@ import { StatusError } from '@/misc/status-error.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { checkHttps } from '@/misc/check-https.js';
|
import { checkHttps } from '@/misc/check-https.js';
|
||||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType, isAvatarDecoration } from '../type.js';
|
||||||
import { ApLoggerService } from '../ApLoggerService.js';
|
import { ApLoggerService } from '../ApLoggerService.js';
|
||||||
import { ApMfmService } from '../ApMfmService.js';
|
import { ApMfmService } from '../ApMfmService.js';
|
||||||
import { ApDbResolverService } from '../ApDbResolverService.js';
|
import { ApDbResolverService } from '../ApDbResolverService.js';
|
||||||
|
@ -37,7 +38,6 @@ import { ApQuestionService } from './ApQuestionService.js';
|
||||||
import { ApImageService } from './ApImageService.js';
|
import { ApImageService } from './ApImageService.js';
|
||||||
import type { Resolver } from '../ApResolverService.js';
|
import type { Resolver } from '../ApResolverService.js';
|
||||||
import type { IObject, IPost } from '../type.js';
|
import type { IObject, IPost } from '../type.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApNoteService {
|
export class ApNoteService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
@ -52,6 +52,9 @@ export class ApNoteService {
|
||||||
@Inject(DI.emojisRepository)
|
@Inject(DI.emojisRepository)
|
||||||
private emojisRepository: EmojisRepository,
|
private emojisRepository: EmojisRepository,
|
||||||
|
|
||||||
|
@Inject(DI.avatarDecorationsRepository)
|
||||||
|
private avatarDecorationRepository: AvatarDecorationsRepository,
|
||||||
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private apMfmService: ApMfmService,
|
private apMfmService: ApMfmService,
|
||||||
private apResolverService: ApResolverService,
|
private apResolverService: ApResolverService,
|
||||||
|
@ -397,4 +400,47 @@ export class ApNoteService {
|
||||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@bindThis
|
||||||
|
public async extractAvatarDecorations(AvatarDecorations: IObject | IObject[], host: string): Promise<MiAvatarDecoration[]> {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
host = this.utilityService.toPuny(host);
|
||||||
|
|
||||||
|
const AvatarDecorationTags = toArray(AvatarDecorations).filter(isAvatarDecoration);
|
||||||
|
const existingAvatars = await this.avatarDecorationRepository.findBy({
|
||||||
|
host,
|
||||||
|
name: In(AvatarDecorationTags.map((avatar) => avatar.name)),
|
||||||
|
});
|
||||||
|
return await Promise.all(AvatarDecorationTags.map(async tag => {
|
||||||
|
const exists = existingAvatars.find(x => x.name === tag.name);
|
||||||
|
if (exists) {
|
||||||
|
if ((exists.updatedAt == null)
|
||||||
|
|| (new Date(tag.updated) > exists.updatedAt)
|
||||||
|
|| (tag.icon.url !== exists.url)
|
||||||
|
) {
|
||||||
|
await this.avatarDecorationRepository.update({
|
||||||
|
host,
|
||||||
|
name: tag.name,
|
||||||
|
}, {
|
||||||
|
url: tag.id,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
});
|
||||||
|
const avatarDecoration = await this.avatarDecorationRepository.findOneBy({ host, name: tag.name });
|
||||||
|
if (avatarDecoration == null) throw new Error('avatardecoration update failed');
|
||||||
|
return avatarDecoration;
|
||||||
|
}
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
this.logger.info(`register avatarDecoration host=${host}, name=${tag.name}`);
|
||||||
|
|
||||||
|
return await this.avatarDecorationRepository.insert({
|
||||||
|
id: this.idService.gen(),
|
||||||
|
host,
|
||||||
|
name: tag.name,
|
||||||
|
url: tag.id,
|
||||||
|
description: '',
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: [],
|
||||||
|
updatedAt: new Date(),
|
||||||
|
}).then(x => this.avatarDecorationRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,6 +291,15 @@ export class ApPersonService implements OnModuleInit {
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region アバターデコレーション取得
|
||||||
|
const avatardecorations = await this.apNoteService.extractAvatarDecorations(person.tag ?? [], host)
|
||||||
|
.then(_decorations => _decorations.map(decorations => decorations.name))
|
||||||
|
.catch(err => {
|
||||||
|
this.logger.error('error occurred while fetching user avatar decorations', { stack: err });
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Start transaction
|
// Start transaction
|
||||||
await this.db.transaction(async transactionalEntityManager => {
|
await this.db.transaction(async transactionalEntityManager => {
|
||||||
|
@ -317,6 +326,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
isBot,
|
isBot,
|
||||||
isCat: (person as any).isCat === true,
|
isCat: (person as any).isCat === true,
|
||||||
emojis,
|
emojis,
|
||||||
|
avatarDecorations: avatardecorations,
|
||||||
})) as MiRemoteUser;
|
})) as MiRemoteUser;
|
||||||
|
|
||||||
await transactionalEntityManager.save(new MiUserProfile({
|
await transactionalEntityManager.save(new MiUserProfile({
|
||||||
|
@ -419,12 +429,26 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
this.logger.info(`Updating the Person: ${person.id}`);
|
this.logger.info(`Updating the Person: ${person.id}`);
|
||||||
|
|
||||||
|
const avatardecorations = await this.apNoteService.extractAvatarDecorations(person.tag ?? [], exist.host)
|
||||||
|
.catch(err => {
|
||||||
|
this.logger.error('error occurred while fetching user avatar decorations', { stack: err });
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
avatardecorations.forEach((value, index) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
avatardecorations[index].flipH = person.AvatarDecorations[index].flipH;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
avatardecorations[index].angle = person.AvatarDecorations[index].angle;
|
||||||
|
});
|
||||||
|
|
||||||
// カスタム絵文字取得
|
// カスタム絵文字取得
|
||||||
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => {
|
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => {
|
||||||
this.logger.info(`extractEmojis: ${e}`);
|
this.logger.info(`extractEmojis: ${e}`);
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const emojiNames = emojis.map(emoji => emoji.name);
|
const emojiNames = emojis.map(emoji => emoji.name);
|
||||||
|
|
||||||
const fields = this.analyzeAttachments(person.attachment ?? []);
|
const fields = this.analyzeAttachments(person.attachment ?? []);
|
||||||
|
@ -454,9 +478,9 @@ export class ApPersonService implements OnModuleInit {
|
||||||
movedToUri: person.movedTo ?? null,
|
movedToUri: person.movedTo ?? null,
|
||||||
alsoKnownAs: person.alsoKnownAs ?? null,
|
alsoKnownAs: person.alsoKnownAs ?? null,
|
||||||
isExplorable: person.discoverable,
|
isExplorable: person.discoverable,
|
||||||
|
avatarDecorations: avatardecorations,
|
||||||
...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))),
|
...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))),
|
||||||
} as Partial<MiRemoteUser> & Pick<MiRemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
|
} as Partial<MiRemoteUser> & Pick<MiRemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
|
||||||
|
|
||||||
const moving = ((): boolean => {
|
const moving = ((): boolean => {
|
||||||
// 移行先がない→ある
|
// 移行先がない→ある
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -178,6 +178,10 @@ export interface IActor extends IObject {
|
||||||
endpoints?: {
|
endpoints?: {
|
||||||
sharedInbox?: string;
|
sharedInbox?: string;
|
||||||
};
|
};
|
||||||
|
AvatarDecorations?:{
|
||||||
|
angle: string;
|
||||||
|
flipH: boolean;
|
||||||
|
}
|
||||||
'vcard:bday'?: string;
|
'vcard:bday'?: string;
|
||||||
'vcard:Address'?: string;
|
'vcard:Address'?: string;
|
||||||
}
|
}
|
||||||
|
@ -228,10 +232,18 @@ export interface IApEmoji extends IObject {
|
||||||
name: string;
|
name: string;
|
||||||
updated: string;
|
updated: string;
|
||||||
}
|
}
|
||||||
|
export interface IApAvatarDecoration extends IObject {
|
||||||
|
type: 'AvatarDecoration';
|
||||||
|
name: string;
|
||||||
|
updated: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const isEmoji = (object: IObject): object is IApEmoji =>
|
export const isEmoji = (object: IObject): object is IApEmoji =>
|
||||||
getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null;
|
getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null;
|
||||||
|
|
||||||
|
export const isAvatarDecoration = (object: IObject): object is IApEmoji =>
|
||||||
|
getApType(object) === 'AvatarDecoration' && !Array.isArray(object.icon) && object.icon.url != null;
|
||||||
|
|
||||||
export interface IKey extends IObject {
|
export interface IKey extends IObject {
|
||||||
type: 'Key';
|
type: 'Key';
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
|
@ -16,6 +16,10 @@ export class MiAvatarDecoration {
|
||||||
})
|
})
|
||||||
public updatedAt: Date | null;
|
public updatedAt: Date | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024, nullable: true,
|
||||||
|
})
|
||||||
|
public host: string;
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 1024,
|
length: 1024,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue