Merge remote-tracking branch 'misskey-original/develop' into develop
# Conflicts: # package.json # packages/backend/src/server/api/endpoints/notes/create.ts # packages/frontend/src/components/MkEmojiPicker.vue # packages/frontend/src/components/MkTimeline.vue # packages/frontend/src/pages/settings/theme.vue
This commit is contained in:
commit
dfd491ff85
119 changed files with 1030 additions and 478 deletions
|
|
@ -460,7 +460,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
*/
|
||||
@bindThis
|
||||
public checkDuplicate(name: string): Promise<boolean> {
|
||||
return this.emojisRepository.exist({ where: { name, host: IsNull() } });
|
||||
return this.emojisRepository.exists({ where: { name, host: IsNull() } });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export class HashtagService {
|
|||
const instance = await this.metaService.fetch();
|
||||
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
|
||||
if (hiddenTags.includes(hashtag)) return;
|
||||
if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
|
||||
if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
|
||||
|
||||
// YYYYMMDDHHmm (10分間隔)
|
||||
const now = new Date();
|
||||
|
|
|
|||
|
|
@ -419,6 +419,10 @@ export class MfmService {
|
|||
},
|
||||
|
||||
text: (node) => {
|
||||
if (!node.props.text.match(/[\r\n]/)) {
|
||||
return doc.createTextNode(node.props.text);
|
||||
}
|
||||
|
||||
const el = doc.createElement('span');
|
||||
const nodes = node.props.text.split(/\r\n|\r|\n/).map(x => doc.createTextNode(x));
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ class NotificationManager {
|
|||
export class NoteCreateService implements OnApplicationShutdown {
|
||||
#shutdownController = new AbortController();
|
||||
|
||||
public static ContainsProhibitedWordsError = class extends Error {};
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
|
@ -223,13 +225,19 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
if (data.visibility === 'public' && data.channel == null) {
|
||||
const sensitiveWords = meta.sensitiveWords;
|
||||
if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
||||
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
||||
data.visibility = 'home';
|
||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||
data.visibility = 'home';
|
||||
}
|
||||
}
|
||||
|
||||
if (!user.host) {
|
||||
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
|
||||
throw new NoteCreateService.ContainsProhibitedWordsError();
|
||||
}
|
||||
}
|
||||
|
||||
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
|
||||
|
||||
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
||||
|
|
@ -571,7 +579,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
if (data.reply) {
|
||||
// 通知
|
||||
if (data.reply.userHost === null) {
|
||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
||||
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||
where: {
|
||||
userId: data.reply.userId,
|
||||
threadId: data.reply.threadId ?? data.reply.id,
|
||||
|
|
@ -709,7 +717,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
@bindThis
|
||||
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
|
||||
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
|
||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
||||
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||
where: {
|
||||
userId: u.id,
|
||||
threadId: note.threadId ?? note.id,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export class NoteReadService implements OnApplicationShutdown {
|
|||
//#endregion
|
||||
|
||||
// スレッドミュート
|
||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
||||
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||
where: {
|
||||
userId: userId,
|
||||
threadId: note.threadId ?? note.id,
|
||||
|
|
@ -70,7 +70,7 @@ export class NoteReadService implements OnApplicationShutdown {
|
|||
|
||||
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
|
||||
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
|
||||
const exist = await this.noteUnreadsRepository.exist({ where: { id: unread.id } });
|
||||
const exist = await this.noteUnreadsRepository.exists({ where: { id: unread.id } });
|
||||
|
||||
if (!exist) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -74,12 +74,12 @@ export class SignupService {
|
|||
const secret = generateUserToken();
|
||||
|
||||
// Check username duplication
|
||||
if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
|
||||
if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
|
||||
throw new Error('DUPLICATED_USERNAME');
|
||||
}
|
||||
|
||||
// Check deleted username duplication
|
||||
if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
|
||||
if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
|
||||
throw new Error('USED_USERNAME');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
let autoAccept = false;
|
||||
|
||||
// 鍵アカウントであっても、既にフォローされていた場合はスルー
|
||||
const isFollowing = await this.followingsRepository.exist({
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
|
|
@ -156,7 +156,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
|
||||
// フォローしているユーザーは自動承認オプション
|
||||
if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
|
||||
const isFollowed = await this.followingsRepository.exist({
|
||||
const isFollowed = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followerId: followee.id,
|
||||
followeeId: follower.id,
|
||||
|
|
@ -170,7 +170,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
if (followee.isLocked && !autoAccept) {
|
||||
autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
|
||||
follower,
|
||||
(oldSrc, newSrc) => this.followingsRepository.exist({
|
||||
(oldSrc, newSrc) => this.followingsRepository.exists({
|
||||
where: {
|
||||
followeeId: followee.id,
|
||||
followerId: newSrc.id,
|
||||
|
|
@ -233,7 +233,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
|
||||
this.cacheService.userFollowingsCache.refresh(follower.id);
|
||||
|
||||
const requestExist = await this.followRequestsRepository.exist({
|
||||
const requestExist = await this.followRequestsRepository.exists({
|
||||
where: {
|
||||
followeeId: followee.id,
|
||||
followerId: follower.id,
|
||||
|
|
@ -531,7 +531,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
}
|
||||
}
|
||||
|
||||
const requestExist = await this.followRequestsRepository.exist({
|
||||
const requestExist = await this.followRequestsRepository.exists({
|
||||
where: {
|
||||
followeeId: followee.id,
|
||||
followerId: follower.id,
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ export class UtilityService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
|
||||
if (sensitiveWords.length === 0) return false;
|
||||
public isKeyWordIncluded(text: string, keyWords: string[]): boolean {
|
||||
if (keyWords.length === 0) return false;
|
||||
if (text === '') return false;
|
||||
|
||||
const regexpregexp = /^\/(.+)\/(.*)$/;
|
||||
|
||||
const matched = sensitiveWords.some(filter => {
|
||||
const matched = keyWords.some(filter => {
|
||||
// represents RegExp
|
||||
const regexp = filter.match(regexpregexp);
|
||||
// This should never happen due to input sanitisation.
|
||||
|
|
|
|||
|
|
@ -629,7 +629,7 @@ export class ApInboxService {
|
|||
return 'skip: follower not found';
|
||||
}
|
||||
|
||||
const isFollowing = await this.followingsRepository.exist({
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followerId: follower.id,
|
||||
followeeId: actor.id,
|
||||
|
|
@ -686,14 +686,14 @@ export class ApInboxService {
|
|||
return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません';
|
||||
}
|
||||
|
||||
const requestExist = await this.followRequestsRepository.exist({
|
||||
const requestExist = await this.followRequestsRepository.exists({
|
||||
where: {
|
||||
followerId: actor.id,
|
||||
followeeId: followee.id,
|
||||
},
|
||||
});
|
||||
|
||||
const isFollowing = await this.followingsRepository.exist({
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followerId: actor.id,
|
||||
followeeId: followee.id,
|
||||
|
|
|
|||
|
|
@ -25,8 +25,21 @@ export class ApMfmService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public getNoteHtml(note: MiNote): string | null {
|
||||
if (!note.text) return '';
|
||||
return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers));
|
||||
public getNoteHtml(note: MiNote, apAppend?: string) {
|
||||
let noMisskeyContent = false;
|
||||
const srcMfm = (note.text ?? '') + (apAppend ?? '');
|
||||
|
||||
const parsed = mfm.parse(srcMfm);
|
||||
|
||||
if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
|
||||
noMisskeyContent = true;
|
||||
}
|
||||
|
||||
const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers));
|
||||
|
||||
return {
|
||||
content,
|
||||
noMisskeyContent,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ export class ApRendererService {
|
|||
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
|
||||
|
||||
if (inReplyToNote != null) {
|
||||
const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } });
|
||||
const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } });
|
||||
|
||||
if (inReplyToUserExist) {
|
||||
if (inReplyToNote.uri) {
|
||||
|
|
@ -389,17 +389,15 @@ export class ApRendererService {
|
|||
poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
||||
}
|
||||
|
||||
let apText = text;
|
||||
let apAppend = '';
|
||||
|
||||
if (quote) {
|
||||
apText += `\n\nRE: ${quote}`;
|
||||
apAppend += `\n\nRE: ${quote}`;
|
||||
}
|
||||
|
||||
const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
|
||||
|
||||
const content = this.apMfmService.getNoteHtml(Object.assign({}, note, {
|
||||
text: apText,
|
||||
}));
|
||||
const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend);
|
||||
|
||||
const emojis = await this.getEmojis(note.emojis);
|
||||
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
|
||||
|
|
@ -412,9 +410,6 @@ export class ApRendererService {
|
|||
|
||||
const asPoll = poll ? {
|
||||
type: 'Question',
|
||||
content: this.apMfmService.getNoteHtml(Object.assign({}, note, {
|
||||
text: text,
|
||||
})),
|
||||
[poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt,
|
||||
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
|
||||
type: 'Note',
|
||||
|
|
@ -432,11 +427,13 @@ export class ApRendererService {
|
|||
attributedTo,
|
||||
summary: summary ?? undefined,
|
||||
content: content ?? undefined,
|
||||
_misskey_content: text,
|
||||
source: {
|
||||
content: text,
|
||||
mediaType: 'text/x.misskeymarkdown',
|
||||
},
|
||||
...(noMisskeyContent ? {} : {
|
||||
_misskey_content: text,
|
||||
source: {
|
||||
content: text,
|
||||
mediaType: 'text/x.misskeymarkdown',
|
||||
},
|
||||
}),
|
||||
_misskey_quote: quote,
|
||||
quoteUrl: quote,
|
||||
published: this.idService.parse(note.id).date.toISOString(),
|
||||
|
|
@ -625,6 +622,7 @@ export class ApRendererService {
|
|||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
{
|
||||
Key: 'sec:Key',
|
||||
// as non-standards
|
||||
manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
|
||||
sensitive: 'as:sensitive',
|
||||
|
|
|
|||
|
|
@ -51,14 +51,14 @@ export class ChannelEntityService {
|
|||
|
||||
const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
|
||||
|
||||
const isFollowing = meId ? await this.channelFollowingsRepository.exist({
|
||||
const isFollowing = meId ? await this.channelFollowingsRepository.exists({
|
||||
where: {
|
||||
followerId: meId,
|
||||
followeeId: channel.id,
|
||||
},
|
||||
}) : false;
|
||||
|
||||
const isFavorited = meId ? await this.channelFavoritesRepository.exist({
|
||||
const isFavorited = meId ? await this.channelFavoritesRepository.exists({
|
||||
where: {
|
||||
userId: meId,
|
||||
channelId: channel.id,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export class ClipEntityService {
|
|||
description: clip.description,
|
||||
isPublic: clip.isPublic,
|
||||
favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
|
||||
isFavorited: meId ? await this.clipFavoritesRepository.exist({ where: { clipId: clip.id, userId: meId } }) : undefined,
|
||||
isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export class EmojiEntityService {
|
|||
category: emoji.category,
|
||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||
url: emoji.publicUrl || emoji.originalUrl,
|
||||
localOnly: emoji.localOnly ? true : undefined,
|
||||
isSensitive: emoji.isSensitive ? true : undefined,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
|
||||
draft: emoji.draft,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export class FlashEntityService {
|
|||
summary: flash.summary,
|
||||
script: flash.script,
|
||||
likedCount: flash.likedCount,
|
||||
isLiked: meId ? await this.flashLikesRepository.exist({ where: { flashId: flash.id, userId: meId } }) : undefined,
|
||||
isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export class GalleryPostEntityService {
|
|||
tags: post.tags.length > 0 ? post.tags : undefined,
|
||||
isSensitive: post.isSensitive,
|
||||
likedCount: post.likedCount,
|
||||
isLiked: meId ? await this.galleryLikesRepository.exist({ where: { postId: post.id, userId: meId } }) : undefined,
|
||||
isLiked: meId ? await this.galleryLikesRepository.exists({ where: { postId: post.id, userId: meId } }) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
hide = false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
const isFollowing = await this.followingsRepository.exist({
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followeeId: packedNote.userId,
|
||||
followerId: meId,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export class PageEntityService {
|
|||
eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null,
|
||||
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is MiDriveFile => x != null)),
|
||||
likedCount: page.likedCount,
|
||||
isLiked: meId ? await this.pageLikesRepository.exist({ where: { pageId: page.id, userId: meId } }) : undefined,
|
||||
isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,43 +152,43 @@ export class UserEntityService implements OnModuleInit {
|
|||
followerId: me,
|
||||
followeeId: target,
|
||||
}),
|
||||
this.followingsRepository.exist({
|
||||
this.followingsRepository.exists({
|
||||
where: {
|
||||
followerId: target,
|
||||
followeeId: me,
|
||||
},
|
||||
}),
|
||||
this.followRequestsRepository.exist({
|
||||
this.followRequestsRepository.exists({
|
||||
where: {
|
||||
followerId: me,
|
||||
followeeId: target,
|
||||
},
|
||||
}),
|
||||
this.followRequestsRepository.exist({
|
||||
this.followRequestsRepository.exists({
|
||||
where: {
|
||||
followerId: target,
|
||||
followeeId: me,
|
||||
},
|
||||
}),
|
||||
this.blockingsRepository.exist({
|
||||
this.blockingsRepository.exists({
|
||||
where: {
|
||||
blockerId: me,
|
||||
blockeeId: target,
|
||||
},
|
||||
}),
|
||||
this.blockingsRepository.exist({
|
||||
this.blockingsRepository.exists({
|
||||
where: {
|
||||
blockerId: target,
|
||||
blockeeId: me,
|
||||
},
|
||||
}),
|
||||
this.mutingsRepository.exist({
|
||||
this.mutingsRepository.exists({
|
||||
where: {
|
||||
muterId: me,
|
||||
muteeId: target,
|
||||
},
|
||||
}),
|
||||
this.renoteMutingsRepository.exist({
|
||||
this.renoteMutingsRepository.exists({
|
||||
where: {
|
||||
muterId: me,
|
||||
muteeId: target,
|
||||
|
|
@ -215,7 +215,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
/*
|
||||
const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
|
||||
|
||||
const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exist({
|
||||
const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exists({
|
||||
where: {
|
||||
antennaId: In(myAntennas.map(x => x.id)),
|
||||
read: false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue