Merge pull request #3 from paricafe/pari-dev

Pari dev
This commit is contained in:
FLY_MC 2024-11-21 00:01:40 +08:00 committed by GitHub
commit 9d8f9353e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 142 additions and 43 deletions

View file

@ -136,13 +136,17 @@ export class AntennaService implements OnApplicationShutdown {
const { username, host } = Acct.parse(x);
return this.utilityService.getFullApAccount(username, host).toLowerCase();
});
if (!accts.includes(this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false;
const matchUser = this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase();
const matchWildcard = this.utilityService.getFullApAccount('*', noteUser.host).toLowerCase();
if (!accts.includes(matchUser) && !accts.includes(matchWildcard)) return false;
} else if (antenna.src === 'users_blacklist') {
const accts = antenna.users.map(x => {
const { username, host } = Acct.parse(x);
return this.utilityService.getFullApAccount(username, host).toLowerCase();
});
if (accts.includes(this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false;
const matchUser = this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase();
const matchWildcard = this.utilityService.getFullApAccount('*', noteUser.host).toLowerCase();
if (accts.includes(matchUser) || accts.includes(matchWildcard)) return false;
}
const keywords = antenna.keywords

View file

@ -227,25 +227,33 @@ export class DriveService {
const thumbnailAccessKey = 'thumbnail-' + randomUUID();
const webpublicAccessKey = 'webpublic-' + randomUUID();
const url = this.internalStorageService.saveFromPath(accessKey, path);
let thumbnailUrl: string | null = null;
let webpublicUrl: string | null = null;
// Ugly type is just to help TS figure out that 2nd / 3rd promises are optional.
const promises: [Promise<string>, ...(Promise<string> | undefined)[]] = [
this.internalStorageService.saveFromPath(accessKey, path),
];
if (alts.thumbnail) {
thumbnailUrl = this.internalStorageService.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data);
this.registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`);
promises.push(this.internalStorageService.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data));
}
if (alts.webpublic) {
webpublicUrl = this.internalStorageService.saveFromBuffer(webpublicAccessKey, alts.webpublic.data);
promises.push(this.internalStorageService.saveFromBuffer(webpublicAccessKey, alts.webpublic.data));
}
const [url, thumbnailUrl, webpublicUrl] = await Promise.all(promises);
if (thumbnailUrl) {
this.registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`);
}
if (webpublicUrl) {
this.registerLogger.info(`web stored: ${webpublicAccessKey}`);
}
file.storedInternal = true;
file.url = url;
file.thumbnailUrl = thumbnailUrl;
file.webpublicUrl = webpublicUrl;
file.thumbnailUrl = thumbnailUrl ?? null;
file.webpublicUrl = webpublicUrl ?? null;
file.accessKey = accessKey;
file.thumbnailAccessKey = thumbnailAccessKey;
file.webpublicAccessKey = webpublicAccessKey;
@ -741,19 +749,19 @@ export class DriveService {
@bindThis
public async deleteFileSync(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
const promises = [];
if (file.storedInternal) {
this.internalStorageService.del(file.accessKey!);
promises.push(this.internalStorageService.del(file.accessKey!));
if (file.thumbnailUrl) {
this.internalStorageService.del(file.thumbnailAccessKey!);
promises.push(this.internalStorageService.del(file.thumbnailAccessKey!));
}
if (file.webpublicUrl) {
this.internalStorageService.del(file.webpublicAccessKey!);
promises.push(this.internalStorageService.del(file.webpublicAccessKey!));
}
} else if (!file.isLink) {
const promises = [];
promises.push(this.deleteObjectStorageFile(file.accessKey!));
if (file.thumbnailUrl) {
@ -763,9 +771,9 @@ export class DriveService {
if (file.webpublicUrl) {
promises.push(this.deleteObjectStorageFile(file.webpublicAccessKey!));
}
}
await Promise.all(promises);
}
this.deletePostProcess(file, isExpired, deleter);
}

View file

@ -138,6 +138,10 @@ export interface NoteEventTypes {
reaction: string;
userId: MiUser['id'];
};
replied: {
id: MiNote['id'];
userId: MiUser['id'];
};
}
type NoteStreamEventTypes = {
[key in keyof NoteEventTypes]: {

View file

@ -52,8 +52,10 @@ import { FeaturedService } from '@/core/FeaturedService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.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';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { CollapsedQueue } from '@/misc/collapsed-queue.js';
@ -217,6 +219,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private instanceChart: InstanceChart,
private utilityService: UtilityService,
private userBlockingService: UserBlockingService,
private cacheService: CacheService,
) {
this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount);
}
@ -450,6 +453,14 @@ export class NoteCreateService implements OnApplicationShutdown {
userHost: user.host,
});
// should really not happen, but better safe than sorry
if (data.reply?.id === insert.id) {
throw new Error("A note can't reply to itself");
}
if (data.renote?.id === insert.id) {
throw new Error("A note can't renote itself");
}
if (data.uri != null) insert.uri = data.uri;
if (data.url != null) insert.url = data.url;
@ -630,6 +641,10 @@ export class NoteCreateService implements OnApplicationShutdown {
// If has in reply to note
if (data.reply) {
this.globalEventService.publishNoteStream(data.reply.id, 'replied', {
id: note.id,
userId: user.id,
});
// 通知
if (data.reply.userHost === null) {
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
@ -639,7 +654,15 @@ export class NoteCreateService 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');
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
@ -659,8 +682,25 @@ export class NoteCreateService implements OnApplicationShutdown {
// Notify
if (data.renote.userHost === null) {
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
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);
}
}
// Publish event
if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
@ -788,7 +828,15 @@ export class NoteCreateService 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;
}

View file

@ -68,6 +68,13 @@ export class NoteDeleteService {
await this.notesRepository.decrement({ id: note.replyId }, 'repliesCount', 1);
}
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
await this.notesRepository.findOneBy({ id: note.renoteId }).then(async (renote) => {
if (!renote) return;
if (renote.userId !== user.id) await this.notesRepository.decrement({ id: renote.id }, 'renoteCount', 1);
});
}
if (!quiet) {
this.globalEventService.publishNoteStream(note.id, 'deleted', {
deletedAt: deletedAt,
@ -106,17 +113,27 @@ export class NoteDeleteService {
this.perUserNotesChart.update(user, note, false);
}
if (this.meta.enableStatsForFederatedInstances) {
if (note.renoteId && note.text) {
// Decrement notes count (user)
this.decNotesCountOfUser(user);
} else if (!note.renoteId) {
// Decrement notes count (user)
this.decNotesCountOfUser(user);
}
if (this.userEntityService.isRemoteUser(user)) {
this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
this.federatedInstanceService.fetch(user.host).then(async i => {
if (note.renoteId && note.text) {
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
} else if (!note.renoteId) {
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
}
if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, false);
}
});
}
}
}
for (const cascadingNote of cascadingNotes) {
this.searchService.unindexNote(cascadingNote);

View file

@ -66,7 +66,11 @@ export class NoteReadService implements OnApplicationShutdown {
noteUserId: note.userId,
};
await this.noteUnreadsRepository.insert(unread);
/* we may be called from NoteEditService, for a note that's
already present in the `note_unread` table: `upsert` makes sure
we don't throw a "duplicate key" error, while still updating
the other columns if they've changed */
await this.noteUnreadsRepository.upsert(unread, ['userId', 'noteId']);
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {

View file

@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, MiMeta } from '@/models/_.js';
import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, NoteThreadMutingsRepository, MiMeta } from '@/models/_.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import type { MiRemoteUser, MiUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
@ -64,8 +64,8 @@ type DecodedReaction = {
host?: string | null;
};
const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/;
const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
const isCustomEmojiRegexp = /^:([\p{Letter}\p{Number}\p{Mark}_+-]+)(?:@\.)?:$/u;
const decodeCustomEmojiRegexp = /^:([\p{Letter}\p{Number}\p{Mark}_+-]+)(?:@([\w.-]+))?:$/u;
@Injectable()
export class ReactionService {
@ -82,6 +82,9 @@ export class ReactionService {
@Inject(DI.noteReactionsRepository)
private noteReactionsRepository: NoteReactionsRepository,
@Inject(DI.noteThreadMutingsRepository)
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
@ -256,11 +259,20 @@ export class ReactionService {
// リアクションされたユーザーがローカルユーザーなら通知を作成
if (note.userHost === null) {
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: note.userId,
threadId: note.threadId ?? note.id,
},
});
if (!isThreadMuted) {
this.notificationService.createNotification(note.userId, 'reaction', {
noteId: note.id,
reaction: reaction,
}, user.id);
}
}
//#region 配信
if (this.userEntityService.isLocalUser(user) && !note.localOnly) {

View file

@ -38,6 +38,7 @@ export class UserSuspendService {
@bindThis
public async suspend(user: MiUser, moderator: MiUser): Promise<void> {
await this.usersRepository.update(user.id, {
isSuspended: true,
});

View file

@ -35,7 +35,7 @@ export class ApMfmService {
noMisskeyContent = true;
}
const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers));
const content = this.mfmService.toHtml(parsed, note.mentionedRemoteUsers ? JSON.parse(note.mentionedRemoteUsers) : []);
return {
content,

View file

@ -16,12 +16,12 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { fromTuple } from '@/misc/from-tuple.js';
import { isCollectionOrOrderedCollection } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
import type { IObject, ICollection, IOrderedCollection } from './type.js';
import { fromTuple } from '@/misc/from-tuple.js';
export class Resolver {
private history: Set<string>;

View file

@ -24,12 +24,12 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
if (note.cw != null) {
summary += `CW: ${note.cw}`;
} else if (note.text) {
summary += note.text ? note.text : '';
summary += note.text;
}
// ファイルが添付されているとき
if ((note.files ?? []).length !== 0) {
summary += ` (📎${note.files!.length})`;
if (note.files && note.files.length !== 0) {
summary += ` (📎${note.files.length})`;
}
// 投票が添付されているとき

View file

@ -16,6 +16,7 @@ export class MiNote {
public id: string;
@Column('timestamp with time zone', {
comment: 'The update time of the Note.',
default: null,
})
public updatedAt: Date | null;

View file

@ -69,7 +69,7 @@ function getJobInfo(job: Bull.Job | undefined, increment = false): string {
// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
const maxAttempts = job.opts.attempts ?? 0;
const maxAttempts = job.opts ? job.opts.attempts : 0;
return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
}