mirror of
https://github.com/paricafe/misskey.git
synced 2024-11-24 05:56:44 -06:00
commit
9d8f9353e4
13 changed files with 142 additions and 43 deletions
|
@ -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
|
||||
|
|
|
@ -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,10 +771,10 @@ export class DriveService {
|
|||
if (file.webpublicUrl) {
|
||||
promises.push(this.deleteObjectStorageFile(file.webpublicAccessKey!));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
this.deletePostProcess(file, isExpired, deleter);
|
||||
}
|
||||
|
||||
|
|
|
@ -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]: {
|
||||
|
|
|
@ -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,7 +682,24 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
// Notify
|
||||
if (data.renote.userHost === null) {
|
||||
nm.push(data.renote.userId, type);
|
||||
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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,15 +113,25 @@ export class NoteDeleteService {
|
|||
this.perUserNotesChart.update(user, note, false);
|
||||
}
|
||||
|
||||
if (this.meta.enableStatsForFederatedInstances) {
|
||||
if (this.userEntityService.isRemoteUser(user)) {
|
||||
this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
|
||||
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.fetch(user.host).then(async i => {
|
||||
if (note.renoteId && note.text) {
|
||||
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
|
||||
if (this.meta.enableChartsForFederatedInstances) {
|
||||
this.instanceChart.updateNote(i.host, note, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (!note.renoteId) {
|
||||
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
|
||||
}
|
||||
if (this.meta.enableChartsForFederatedInstances) {
|
||||
this.instanceChart.updateNote(i.host, note, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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,10 +259,19 @@ export class ReactionService {
|
|||
|
||||
// リアクションされたユーザーがローカルユーザーなら通知を作成
|
||||
if (note.userHost === null) {
|
||||
this.notificationService.createNotification(note.userId, 'reaction', {
|
||||
noteId: note.id,
|
||||
reaction: reaction,
|
||||
}, user.id);
|
||||
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 配信
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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})`;
|
||||
}
|
||||
|
||||
// 投票が添付されているとき
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue