mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-26 22:43:08 +02:00
merge: some fixes to NoteEdit and muting (!376)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/376 Approved-by: Amelia Yukii <amelia.yukii@shourai.de> Approved-by: Luna <her@mint.lgbt> Approved-by: Marie <marie@kaifa.ch>
This commit is contained in:
commit
75bce2228f
2 changed files with 92 additions and 38 deletions
|
@ -328,6 +328,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
||||||
}
|
}
|
||||||
data.text = data.text.trim();
|
data.text = data.text.trim();
|
||||||
|
if (data.text === '') {
|
||||||
|
data.text = null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
data.text = null;
|
data.text = null;
|
||||||
}
|
}
|
||||||
|
@ -807,7 +810,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
||||||
|
|
||||||
if (!isThreadMuted || !muted) {
|
if (!isThreadMuted && !muted) {
|
||||||
nm.push(data.reply.userId, 'reply');
|
nm.push(data.reply.userId, 'reply');
|
||||||
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
|
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
|
||||||
|
|
||||||
|
@ -842,7 +845,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
||||||
|
|
||||||
if (!isThreadMuted || !muted) {
|
if (!isThreadMuted && !muted) {
|
||||||
nm.push(data.renote.userId, type);
|
nm.push(data.renote.userId, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,11 @@ import { MetaService } from '@/core/MetaService.js';
|
||||||
import { SearchService } from '@/core/SearchService.js';
|
import { SearchService } from '@/core/SearchService.js';
|
||||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
import { isReply } from '@/misc/is-reply.js';
|
||||||
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
|
|
||||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||||
|
|
||||||
|
@ -206,6 +211,8 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
|
private userBlockingService: UserBlockingService,
|
||||||
|
private cacheService: CacheService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -255,16 +262,18 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
if (data.channel != null) data.localOnly = true;
|
if (data.channel != null) data.localOnly = true;
|
||||||
if (data.updatedAt == null) data.updatedAt = new Date();
|
if (data.updatedAt == null) data.updatedAt = new Date();
|
||||||
|
|
||||||
|
const meta = await this.metaService.fetch();
|
||||||
|
|
||||||
if (data.visibility === 'public' && data.channel == null) {
|
if (data.visibility === 'public' && data.channel == null) {
|
||||||
const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
|
const sensitiveWords = meta.sensitiveWords;
|
||||||
if (this.isSensitive(data, sensitiveWords)) {
|
if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const inSilencedInstance = this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, user.host);
|
const inSilencedInstance = this.utilityService.isSilencedHost((meta).silencedHosts, user.host);
|
||||||
|
|
||||||
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
|
@ -296,6 +305,18 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check blocking
|
||||||
|
if (data.renote && !this.isQuote(data)) {
|
||||||
|
if (data.renote.userHost === null) {
|
||||||
|
if (data.renote.userId !== user.id) {
|
||||||
|
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||||
|
if (blocked) {
|
||||||
|
throw new Error('blocked');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 返信対象がpublicではないならhomeにする
|
// 返信対象がpublicではないならhomeにする
|
||||||
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
|
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
|
@ -316,6 +337,9 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
||||||
}
|
}
|
||||||
data.text = data.text.trim();
|
data.text = data.text.trim();
|
||||||
|
if (data.text === '') {
|
||||||
|
data.text = null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
data.text = null;
|
data.text = null;
|
||||||
}
|
}
|
||||||
|
@ -361,6 +385,14 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.host && !data.cw) {
|
||||||
|
await this.federatedInstanceService.fetch(user.host).then(async i => {
|
||||||
|
if (i.isNSFW) {
|
||||||
|
data.cw = 'Instance is marked as NSFW';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const update: Partial<MiNote> = {};
|
const update: Partial<MiNote> = {};
|
||||||
if (data.text !== oldnote.text) {
|
if (data.text !== oldnote.text) {
|
||||||
update.text = data.text;
|
update.text = data.text;
|
||||||
|
@ -587,7 +619,15 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isThreadMuted) {
|
const [
|
||||||
|
userIdsWhoMeMuting,
|
||||||
|
] = data.reply.userId ? await Promise.all([
|
||||||
|
this.cacheService.userMutingsCache.fetch(data.reply.userId),
|
||||||
|
]) : [new Set<string>()];
|
||||||
|
|
||||||
|
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
||||||
|
|
||||||
|
if (!isThreadMuted && !muted) {
|
||||||
nm.push(data.reply.userId, 'reply');
|
nm.push(data.reply.userId, 'reply');
|
||||||
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
|
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
|
||||||
|
|
||||||
|
@ -603,12 +643,29 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// If it is renote
|
// If it is renote
|
||||||
if (data.renote) {
|
if (data.renote) {
|
||||||
const type = data.text ? 'quote' : 'renote';
|
const type = this.isQuote(data) ? 'quote' : 'renote';
|
||||||
|
|
||||||
// Notify
|
// Notify
|
||||||
if (data.renote.userHost === null) {
|
if (data.renote.userHost === null) {
|
||||||
|
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
||||||
|
where: {
|
||||||
|
userId: data.renote.userId,
|
||||||
|
threadId: data.renote.threadId ?? data.renote.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
userIdsWhoMeMuting,
|
||||||
|
] = data.renote.userId ? await Promise.all([
|
||||||
|
this.cacheService.userMutingsCache.fetch(data.renote.userId),
|
||||||
|
]) : [new Set<string>()];
|
||||||
|
|
||||||
|
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
||||||
|
|
||||||
|
if (!isThreadMuted && !muted) {
|
||||||
nm.push(data.renote.userId, type);
|
nm.push(data.renote.userId, type);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Publish event
|
// Publish event
|
||||||
if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
|
if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
|
||||||
|
@ -657,7 +714,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
this.relayService.deliverToRelays(user, noteActivity);
|
this.relayService.deliverToRelays(user, noteActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
dm.execute();
|
trackPromise(dm.execute());
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -686,28 +743,9 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
|
private isQuote(note: Option): note is Option & { renote: MiNote } {
|
||||||
if (sensitiveWord.length > 0) {
|
// sync with misc/is-quote.ts
|
||||||
const text = note.cw ?? note.text ?? '';
|
return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
|
||||||
if (text === '') return false;
|
|
||||||
const matched = sensitiveWord.some(filter => {
|
|
||||||
// represents RegExp
|
|
||||||
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
|
||||||
// This should never happen due to input sanitisation.
|
|
||||||
if (!regexp) {
|
|
||||||
const words = filter.split(' ');
|
|
||||||
return words.every(keyword => text.includes(keyword));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return new RE2(regexp[1], regexp[2]).test(text);
|
|
||||||
} catch (err) {
|
|
||||||
// This should never happen due to input sanitisation.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (matched) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -720,7 +758,15 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isThreadMuted) {
|
const [
|
||||||
|
userIdsWhoMeMuting,
|
||||||
|
] = u.id ? await Promise.all([
|
||||||
|
this.cacheService.userMutingsCache.fetch(u.id),
|
||||||
|
]) : [new Set<string>()];
|
||||||
|
|
||||||
|
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
||||||
|
|
||||||
|
if (isThreadMuted || muted) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -748,7 +794,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
const user = await this.usersRepository.findOneBy({ id: note.userId });
|
const user = await this.usersRepository.findOneBy({ id: note.userId });
|
||||||
if (user == null) throw new Error('user not found');
|
if (user == null) throw new Error('user not found');
|
||||||
|
|
||||||
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
|
const content = data.renote && !this.isQuote(data)
|
||||||
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
|
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
|
||||||
: this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, false), user);
|
: this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, false), user);
|
||||||
|
|
||||||
|
@ -782,6 +828,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
@bindThis
|
@bindThis
|
||||||
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
|
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
|
if (!meta.enableFanoutTimeline) return;
|
||||||
|
|
||||||
const r = this.redisForTimelines.pipeline();
|
const r = this.redisForTimelines.pipeline();
|
||||||
|
|
||||||
|
@ -825,7 +872,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
|
|
||||||
if (note.visibility === 'followers') {
|
if (note.visibility === 'followers') {
|
||||||
// TODO: 重そうだから何とかしたい Set 使う?
|
// TODO: 重そうだから何とかしたい Set 使う?
|
||||||
userListMemberships = userListMemberships.filter(x => followings.some(f => f.followerId === x.userListUserId));
|
userListMemberships = userListMemberships.filter(x => x.userListUserId === user.id || followings.some(f => f.followerId === x.userListUserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする
|
// TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする
|
||||||
|
@ -834,7 +881,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
|
if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
|
||||||
|
|
||||||
// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
|
// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
|
||||||
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) {
|
if (isReply(note, following.followerId)) {
|
||||||
if (!following.withReplies) continue;
|
if (!following.withReplies) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -848,11 +895,12 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
// ダイレクトのとき、そのリストが対象外のユーザーの場合
|
// ダイレクトのとき、そのリストが対象外のユーザーの場合
|
||||||
if (
|
if (
|
||||||
note.visibility === 'specified' &&
|
note.visibility === 'specified' &&
|
||||||
|
note.userId !== userListMembership.userListUserId &&
|
||||||
!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
|
!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
|
||||||
) continue;
|
) continue;
|
||||||
|
|
||||||
// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
|
// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
|
||||||
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) {
|
if (isReply(note, userListMembership.userListUserId)) {
|
||||||
if (!userListMembership.withReplies) continue;
|
if (!userListMembership.withReplies) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,11 +918,14 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自分自身以外への返信
|
// 自分自身以外への返信
|
||||||
if (note.replyId && note.replyUserId !== note.userId) {
|
if (isReply(note)) {
|
||||||
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||||
|
|
||||||
if (note.visibility === 'public' && note.userHost == null) {
|
if (note.visibility === 'public' && note.userHost == null) {
|
||||||
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
||||||
|
if (note.replyUserHost == null) {
|
||||||
|
this.fanoutTimelineService.push(`localTimelineWithReplyTo:${note.replyUserId}`, note.id, 300 / 10, r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||||
|
|
Loading…
Reference in a new issue