Merge branch 'develop' into schedule-note
This commit is contained in:
commit
1a54663956
76 changed files with 1820 additions and 1114 deletions
|
|
@ -16,7 +16,7 @@ import type { AntennasRepository, UserListMembershipsRepository } from '@/models
|
|||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -39,7 +39,7 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
|
||||
private utilityService: UtilityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
) {
|
||||
this.antennasFetched = false;
|
||||
this.antennas = [];
|
||||
|
|
@ -94,7 +94,7 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
const redisPipeline = this.redisForTimelines.pipeline();
|
||||
|
||||
for (const antenna of matchedAntennas) {
|
||||
this.funoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
|
||||
this.fanoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
|
||||
this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ import { FileInfoService } from './FileInfoService.js';
|
|||
import { SearchService } from './SearchService.js';
|
||||
import { ClipService } from './ClipService.js';
|
||||
import { FeaturedService } from './FeaturedService.js';
|
||||
import { FunoutTimelineService } from './FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from './FanoutTimelineService.js';
|
||||
import { ChannelFollowingService } from './ChannelFollowingService.js';
|
||||
import { RegistryApiService } from './RegistryApiService.js';
|
||||
import { ChartLoggerService } from './chart/ChartLoggerService.js';
|
||||
|
|
@ -194,7 +194,7 @@ const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: Fi
|
|||
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
|
||||
const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
|
||||
const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
|
||||
const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService };
|
||||
const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', useExisting: FanoutTimelineService };
|
||||
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
|
||||
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
|
||||
|
||||
|
|
@ -330,7 +330,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
SearchService,
|
||||
ClipService,
|
||||
FeaturedService,
|
||||
FunoutTimelineService,
|
||||
FanoutTimelineService,
|
||||
ChannelFollowingService,
|
||||
RegistryApiService,
|
||||
ChartLoggerService,
|
||||
|
|
@ -459,7 +459,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$SearchService,
|
||||
$ClipService,
|
||||
$FeaturedService,
|
||||
$FunoutTimelineService,
|
||||
$FanoutTimelineService,
|
||||
$ChannelFollowingService,
|
||||
$RegistryApiService,
|
||||
$ChartLoggerService,
|
||||
|
|
@ -589,7 +589,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
SearchService,
|
||||
ClipService,
|
||||
FeaturedService,
|
||||
FunoutTimelineService,
|
||||
FanoutTimelineService,
|
||||
ChannelFollowingService,
|
||||
RegistryApiService,
|
||||
FederationChart,
|
||||
|
|
@ -717,7 +717,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$SearchService,
|
||||
$ClipService,
|
||||
$FeaturedService,
|
||||
$FunoutTimelineService,
|
||||
$FanoutTimelineService,
|
||||
$ChannelFollowingService,
|
||||
$RegistryApiService,
|
||||
$FederationChart,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
@Injectable()
|
||||
export class FunoutTimelineService {
|
||||
export class FanoutTimelineService {
|
||||
constructor(
|
||||
@Inject(DI.redisForTimelines)
|
||||
private redisForTimelines: Redis.Redis,
|
||||
|
|
@ -5,11 +5,12 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { MiNote, MiUser } from '@/models/_.js';
|
||||
import type { MiGalleryPost, MiNote, MiUser } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
const GLOBAL_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
|
||||
export const GALLERY_POSTS_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
|
||||
const PER_USER_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 7; // 1週間ごと
|
||||
const HASHTAG_RANKING_WINDOW = 1000 * 60 * 60; // 1時間ごと
|
||||
|
||||
|
|
@ -79,6 +80,11 @@ export class FeaturedService {
|
|||
return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public updateGalleryPostsRanking(galleryPostId: MiGalleryPost['id'], score = 1): Promise<void> {
|
||||
return this.updateRankingOf('featuredGalleryPostsRanking', GALLERY_POSTS_RANKING_WINDOW, galleryPostId, score);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public updateInChannelNotesRanking(channelId: MiNote['channelId'], noteId: MiNote['id'], score = 1): Promise<void> {
|
||||
return this.updateRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
|
||||
|
|
@ -99,6 +105,11 @@ export class FeaturedService {
|
|||
return this.getRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, threshold);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getGalleryPostsRanking(threshold: number): Promise<MiGalleryPost['id'][]> {
|
||||
return this.getRankingOf('featuredGalleryPostsRanking', GALLERY_POSTS_RANKING_WINDOW, threshold);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getInChannelNotesRanking(channelId: MiNote['channelId'], threshold: number): Promise<MiNote['id'][]> {
|
||||
return this.getRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, threshold);
|
||||
|
|
|
|||
|
|
@ -250,6 +250,12 @@ export class MfmService {
|
|||
}
|
||||
}
|
||||
|
||||
function fnDefault(node: mfm.MfmFn) {
|
||||
const el = doc.createElement('i');
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
}
|
||||
|
||||
const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any } = {
|
||||
bold: (node) => {
|
||||
const el = doc.createElement('b');
|
||||
|
|
@ -276,17 +282,68 @@ export class MfmService {
|
|||
},
|
||||
|
||||
fn: (node) => {
|
||||
if (node.props.name === 'unixtime') {
|
||||
const text = node.children[0]!.type === 'text' ? node.children[0].props.text : '';
|
||||
const date = new Date(parseInt(text, 10) * 1000);
|
||||
const el = doc.createElement('time');
|
||||
el.setAttribute('datetime', date.toISOString());
|
||||
el.textContent = date.toISOString();
|
||||
return el;
|
||||
} else {
|
||||
const el = doc.createElement('i');
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
switch (node.props.name) {
|
||||
case 'unixtime': {
|
||||
const text = node.children[0].type === 'text' ? node.children[0].props.text : '';
|
||||
try {
|
||||
const date = new Date(parseInt(text, 10) * 1000);
|
||||
const el = doc.createElement('time');
|
||||
el.setAttribute('datetime', date.toISOString());
|
||||
el.textContent = date.toISOString();
|
||||
return el;
|
||||
} catch (err) {
|
||||
return fnDefault(node);
|
||||
}
|
||||
}
|
||||
|
||||
case 'ruby': {
|
||||
if (node.children.length === 1) {
|
||||
const child = node.children[0];
|
||||
const text = child.type === 'text' ? child.props.text : '';
|
||||
const rubyEl = doc.createElement('ruby');
|
||||
const rtEl = doc.createElement('rt');
|
||||
|
||||
// ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする
|
||||
const rpStartEl = doc.createElement('rp');
|
||||
rpStartEl.appendChild(doc.createTextNode('('));
|
||||
const rpEndEl = doc.createElement('rp');
|
||||
rpEndEl.appendChild(doc.createTextNode(')'));
|
||||
|
||||
rubyEl.appendChild(doc.createTextNode(text.split(' ')[0]));
|
||||
rtEl.appendChild(doc.createTextNode(text.split(' ')[1]));
|
||||
rubyEl.appendChild(rpStartEl);
|
||||
rubyEl.appendChild(rtEl);
|
||||
rubyEl.appendChild(rpEndEl);
|
||||
return rubyEl;
|
||||
} else {
|
||||
const rt = node.children.at(-1);
|
||||
|
||||
if (!rt) {
|
||||
return fnDefault(node);
|
||||
}
|
||||
|
||||
const text = rt.type === 'text' ? rt.props.text : '';
|
||||
const rubyEl = doc.createElement('ruby');
|
||||
const rtEl = doc.createElement('rt');
|
||||
|
||||
// ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする
|
||||
const rpStartEl = doc.createElement('rp');
|
||||
rpStartEl.appendChild(doc.createTextNode('('));
|
||||
const rpEndEl = doc.createElement('rp');
|
||||
rpEndEl.appendChild(doc.createTextNode(')'));
|
||||
|
||||
appendChildren(node.children.slice(0, node.children.length - 1), rubyEl);
|
||||
rtEl.appendChild(doc.createTextNode(text.trim()));
|
||||
rubyEl.appendChild(rpStartEl);
|
||||
rubyEl.appendChild(rtEl);
|
||||
rubyEl.appendChild(rpEndEl);
|
||||
return rubyEl;
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
return fnDefault(node);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { SearchService } from '@/core/SearchService.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
|
||||
|
|
@ -162,7 +162,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private queueService: QueueService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private noteReadService: NoteReadService,
|
||||
private notificationService: NotificationService,
|
||||
private relayService: RelayService,
|
||||
|
|
@ -810,9 +810,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
const r = this.redisForTimelines.pipeline();
|
||||
|
||||
if (note.channelId) {
|
||||
this.funoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
|
||||
this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
|
||||
|
||||
this.funoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
|
||||
const channelFollowings = await this.channelFollowingsRepository.find({
|
||||
where: {
|
||||
|
|
@ -822,9 +822,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
});
|
||||
|
||||
for (const channelFollowing of channelFollowings) {
|
||||
this.funoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -862,9 +862,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
if (!following.withReplies) continue;
|
||||
}
|
||||
|
||||
this.funoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -880,36 +880,36 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
if (!userListMembership.withReplies) continue;
|
||||
}
|
||||
|
||||
this.funoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
|
||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
|
||||
this.funoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
|
||||
// 自分自身以外への返信
|
||||
if (note.replyId && note.replyUserId !== note.userId) {
|
||||
this.funoutTimelineService.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) {
|
||||
this.funoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
||||
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
||||
}
|
||||
} else {
|
||||
this.funoutTimelineService.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);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
|
||||
}
|
||||
|
||||
if (note.visibility === 'public' && note.userHost == null) {
|
||||
this.funoutTimelineService.push('localTimeline', note.id, 1000, r);
|
||||
this.fanoutTimelineService.push('localTimeline', note.id, 1000, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
|
||||
this.fanoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { IdService } from '@/core/IdService.js';
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
export type RolePolicies = {
|
||||
|
|
@ -110,7 +110,7 @@ export class RoleService implements OnApplicationShutdown {
|
|||
private globalEventService: GlobalEventService,
|
||||
private idService: IdService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
) {
|
||||
//this.onMessage = this.onMessage.bind(this);
|
||||
|
||||
|
|
@ -485,7 +485,7 @@ export class RoleService implements OnApplicationShutdown {
|
|||
const redisPipeline = this.redisForTimelines.pipeline();
|
||||
|
||||
for (const role of roles) {
|
||||
this.funoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline);
|
||||
this.fanoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline);
|
||||
this.globalEventService.publishRoleTimelineStream(role.id, 'note', note);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import { CacheService } from '@/core/CacheService.js';
|
|||
import type { Config } from '@/config.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import Logger from '../logger.js';
|
||||
|
||||
const logger = new Logger('following/create');
|
||||
|
|
@ -84,7 +84,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
private webhookService: WebhookService,
|
||||
private apRendererService: ApRendererService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private perUserFollowingChart: PerUserFollowingChart,
|
||||
private instanceChart: InstanceChart,
|
||||
) {
|
||||
|
|
@ -305,7 +305,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
}
|
||||
});
|
||||
|
||||
this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
|
||||
this.fanoutTimelineService.purge(`homeTimeline:${follower.id}`);
|
||||
}
|
||||
|
||||
// Publish followed event
|
||||
|
|
@ -374,7 +374,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
}
|
||||
});
|
||||
|
||||
this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
|
||||
this.fanoutTimelineService.purge(`homeTimeline:${follower.id}`);
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
||||
|
|
|
|||
|
|
@ -473,6 +473,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||
unreadNotificationsCount: notificationsInfo?.unreadCount,
|
||||
mutedWords: profile!.mutedWords,
|
||||
hardMutedWords: profile!.hardMutedWords,
|
||||
mutedInstances: profile!.mutedInstances,
|
||||
mutingNotificationTypes: [], // 後方互換性のため
|
||||
notificationRecieveConfig: profile!.notificationRecieveConfig,
|
||||
|
|
|
|||
|
|
@ -215,7 +215,12 @@ export class MiUserProfile {
|
|||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public mutedWords: string[][];
|
||||
public mutedWords: (string[] | string)[];
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public hardMutedWords: (string[] | string)[];
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
|
|
|
|||
|
|
@ -530,6 +530,18 @@ export const packedMeDetailedOnlySchema = {
|
|||
},
|
||||
},
|
||||
},
|
||||
hardMutedWords: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
mutedInstances: {
|
||||
type: 'array',
|
||||
nullable: true, optional: false,
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export class SignupApiService {
|
|||
code: invitationCode,
|
||||
});
|
||||
|
||||
if (ticket == null) {
|
||||
if (ticket == null || ticket.usedById != null) {
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { NoteReadService } from '@/core/NoteReadService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private noteReadService: NoteReadService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
|
@ -98,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
this.globalEventService.publishInternalEvent('antennaUpdated', antenna);
|
||||
}
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -69,15 +70,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private idService: IdService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private cacheService: CacheService,
|
||||
private activeUsersChart: ActiveUsersChart,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
const isRangeSpecified = untilId != null && sinceId != null;
|
||||
|
||||
const serverSettings = await this.metaService.fetch();
|
||||
|
||||
const channel = await this.channelsRepository.findOneBy({
|
||||
id: ps.channelId,
|
||||
});
|
||||
|
|
@ -88,14 +92,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (me) this.activeUsersChart.read(me);
|
||||
|
||||
if (isRangeSpecified || sinceId == null) {
|
||||
if (serverSettings.enableFanoutTimeline && (isRangeSpecified || sinceId == null)) {
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
] = me ? await Promise.all([
|
||||
this.cacheService.userMutingsCache.fetch(me.id),
|
||||
]) : [new Set<string>()];
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
if (noteIds.length > 0) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import type { GalleryPostsRepository } from '@/models/_.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
|
@ -27,25 +28,49 @@ export const meta = {
|
|||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
private galleryPostsRankingCache: string[] = [];
|
||||
private galleryPostsRankingCacheLastFetchedAt = 0;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.galleryPostsRepository)
|
||||
private galleryPostsRepository: GalleryPostsRepository,
|
||||
|
||||
private galleryPostEntityService: GalleryPostEntityService,
|
||||
private featuredService: FeaturedService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.galleryPostsRepository.createQueryBuilder('post')
|
||||
.andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) })
|
||||
.andWhere('post.likedCount > 0')
|
||||
.orderBy('post.likedCount', 'DESC');
|
||||
let postIds: string[];
|
||||
if (this.galleryPostsRankingCacheLastFetchedAt !== 0 && (Date.now() - this.galleryPostsRankingCacheLastFetchedAt < 1000 * 60 * 30)) {
|
||||
postIds = this.galleryPostsRankingCache;
|
||||
} else {
|
||||
postIds = await this.featuredService.getGalleryPostsRanking(100);
|
||||
this.galleryPostsRankingCache = postIds;
|
||||
this.galleryPostsRankingCacheLastFetchedAt = Date.now();
|
||||
}
|
||||
|
||||
const posts = await query.limit(10).getMany();
|
||||
postIds.sort((a, b) => a > b ? -1 : 1);
|
||||
if (ps.untilId) {
|
||||
postIds = postIds.filter(id => id < ps.untilId!);
|
||||
}
|
||||
postIds = postIds.slice(0, ps.limit);
|
||||
|
||||
if (postIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = this.galleryPostsRepository.createQueryBuilder('post')
|
||||
.where('post.id IN (:...postIds)', { postIds: postIds });
|
||||
|
||||
const posts = await query.getMany();
|
||||
|
||||
return await this.galleryPostEntityService.packMany(posts, me);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/_.js';
|
||||
import { FeaturedService, GALLERY_POSTS_RANKING_WINDOW } from '@/core/FeaturedService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
|
@ -57,6 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.galleryLikesRepository)
|
||||
private galleryLikesRepository: GalleryLikesRepository,
|
||||
|
||||
private featuredService: FeaturedService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
|
@ -88,6 +90,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
userId: me.id,
|
||||
});
|
||||
|
||||
// ランキング更新
|
||||
if (Date.now() - this.idService.parse(post.id).date.getTime() < GALLERY_POSTS_RANKING_WINDOW) {
|
||||
await this.featuredService.updateGalleryPostsRanking(post.id, 1);
|
||||
}
|
||||
|
||||
this.galleryPostsRepository.increment({ id: post.id }, 'likedCount', 1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { GalleryPostsRepository, GalleryLikesRepository } from '@/models/_.js';
|
||||
import { FeaturedService, GALLERY_POSTS_RANKING_WINDOW } from '@/core/FeaturedService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
|
|
@ -49,6 +51,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
@Inject(DI.galleryLikesRepository)
|
||||
private galleryLikesRepository: GalleryLikesRepository,
|
||||
|
||||
private featuredService: FeaturedService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId });
|
||||
|
|
@ -68,6 +73,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
// Delete like
|
||||
await this.galleryLikesRepository.delete(exist.id);
|
||||
|
||||
// ランキング更新
|
||||
if (Date.now() - this.idService.parse(post.id).date.getTime() < GALLERY_POSTS_RANKING_WINDOW) {
|
||||
await this.featuredService.updateGalleryPostsRanking(post.id, -1);
|
||||
}
|
||||
|
||||
this.galleryPostsRepository.decrement({ id: post.id }, 'likedCount', 1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,11 @@ export const meta = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
const muteWords = { type: 'array', items: { oneOf: [
|
||||
{ type: 'array', items: { type: 'string' } },
|
||||
{ type: 'string' }
|
||||
] } } as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
|
@ -171,7 +176,8 @@ export const paramDef = {
|
|||
autoSensitive: { type: 'boolean' },
|
||||
ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
|
||||
pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
mutedWords: { type: 'array' },
|
||||
mutedWords: muteWords,
|
||||
hardMutedWords: muteWords,
|
||||
mutedInstances: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
|
|
@ -234,16 +240,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (ps.location !== undefined) profileUpdates.location = ps.location;
|
||||
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
|
||||
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
|
||||
if (ps.mutedWords !== undefined) {
|
||||
|
||||
function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
|
||||
// TODO: ちゃんと数える
|
||||
const length = JSON.stringify(ps.mutedWords).length;
|
||||
if (length > (await this.roleService.getUserPolicies(user.id)).wordMuteLimit) {
|
||||
const length = JSON.stringify(mutedWords).length;
|
||||
if (length > limit) {
|
||||
throw new ApiError(meta.errors.tooManyMutedWords);
|
||||
}
|
||||
}
|
||||
|
||||
// validate regular expression syntax
|
||||
ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => {
|
||||
const regexp = x.match(/^\/(.+)\/(.*)$/);
|
||||
function validateMuteWordRegex(mutedWords: (string[] | string)[]) {
|
||||
for (const mutedWord of mutedWords) {
|
||||
if (typeof mutedWord !== "string") continue;
|
||||
|
||||
const regexp = mutedWord.match(/^\/(.+)\/(.*)$/);
|
||||
if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
|
||||
|
||||
try {
|
||||
|
|
@ -251,11 +261,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
} catch (err) {
|
||||
throw new ApiError(meta.errors.invalidRegexp);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.mutedWords !== undefined) {
|
||||
checkMuteWordCount(ps.mutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
|
||||
validateMuteWordRegex(ps.mutedWords);
|
||||
|
||||
profileUpdates.mutedWords = ps.mutedWords;
|
||||
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
||||
}
|
||||
if (ps.hardMutedWords !== undefined) {
|
||||
checkMuteWordCount(ps.hardMutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
|
||||
validateMuteWordRegex(ps.hardMutedWords);
|
||||
profileUpdates.hardMutedWords = ps.hardMutedWords;
|
||||
}
|
||||
if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
|
||||
if (ps.notificationRecieveConfig !== undefined) profileUpdates.notificationRecieveConfig = ps.notificationRecieveConfig;
|
||||
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
||||
|
|
|
|||
|
|
@ -64,16 +64,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
}
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||
if (ps.untilId) {
|
||||
noteIds = noteIds.filter(id => id < ps.untilId!);
|
||||
}
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
|
@ -77,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private activeUsersChart: ActiveUsersChart,
|
||||
private idService: IdService,
|
||||
private cacheService: CacheService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private queryService: QueryService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
private metaService: MetaService,
|
||||
|
|
@ -120,20 +120,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
let shouldFallbackToDb = false;
|
||||
|
||||
if (ps.withFiles) {
|
||||
const [htlNoteIds, ltlNoteIds] = await this.funoutTimelineService.getMulti([
|
||||
const [htlNoteIds, ltlNoteIds] = await this.fanoutTimelineService.getMulti([
|
||||
`homeTimelineWithFiles:${me.id}`,
|
||||
'localTimelineWithFiles',
|
||||
], untilId, sinceId);
|
||||
noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
|
||||
} else if (ps.withReplies) {
|
||||
const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.funoutTimelineService.getMulti([
|
||||
const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.fanoutTimelineService.getMulti([
|
||||
`homeTimeline:${me.id}`,
|
||||
'localTimeline',
|
||||
'localTimelineWithReplies',
|
||||
], untilId, sinceId);
|
||||
noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds, ...ltlReplyNoteIds]));
|
||||
} else {
|
||||
const [htlNoteIds, ltlNoteIds] = await this.funoutTimelineService.getMulti([
|
||||
const [htlNoteIds, ltlNoteIds] = await this.fanoutTimelineService.getMulti([
|
||||
`homeTimeline:${me.id}`,
|
||||
'localTimeline',
|
||||
], untilId, sinceId);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
|
|
@ -69,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private activeUsersChart: ActiveUsersChart,
|
||||
private idService: IdService,
|
||||
private cacheService: CacheService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private queryService: QueryService,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
|
|
@ -107,9 +107,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
let noteIds: string[];
|
||||
|
||||
if (ps.withFiles) {
|
||||
noteIds = await this.funoutTimelineService.get('localTimelineWithFiles', untilId, sinceId);
|
||||
noteIds = await this.fanoutTimelineService.get('localTimelineWithFiles', untilId, sinceId);
|
||||
} else {
|
||||
const [nonReplyNoteIds, replyNoteIds] = await this.funoutTimelineService.getMulti([
|
||||
const [nonReplyNoteIds, replyNoteIds] = await this.fanoutTimelineService.getMulti([
|
||||
'localTimeline',
|
||||
'localTimelineWithReplies',
|
||||
], untilId, sinceId);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
|
@ -65,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private activeUsersChart: ActiveUsersChart,
|
||||
private idService: IdService,
|
||||
private cacheService: CacheService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
private queryService: QueryService,
|
||||
private metaService: MetaService,
|
||||
|
|
@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
this.cacheService.userBlockedCache.fetch(me.id),
|
||||
]);
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
let redisTimeline: MiNote[] = [];
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
|
@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private activeUsersChart: ActiveUsersChart,
|
||||
private cacheService: CacheService,
|
||||
private idService: IdService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private queryService: QueryService,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
|
|
@ -123,7 +123,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
this.cacheService.userBlockedCache.fetch(me.id),
|
||||
]);
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
let redisTimeline: MiNote[] = [];
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { QueryService } from '@/core/QueryService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private idService: IdService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
|
|
@ -84,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
return [];
|
||||
}
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ import { CacheService } from '@/core/CacheService.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -70,7 +71,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private queryService: QueryService,
|
||||
private cacheService: CacheService,
|
||||
private idService: IdService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
|
|
@ -78,7 +80,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const isRangeSpecified = untilId != null && sinceId != null;
|
||||
const isSelf = me && (me.id === ps.userId);
|
||||
|
||||
if (isRangeSpecified || sinceId == null) {
|
||||
const serverSettings = await this.metaService.fetch();
|
||||
|
||||
if (serverSettings.enableFanoutTimeline && (isRangeSpecified || sinceId == null)) {
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
] = me ? await Promise.all([
|
||||
|
|
@ -86,9 +90,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
]) : [new Set<string>()];
|
||||
|
||||
const [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([
|
||||
this.funoutTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId),
|
||||
ps.withReplies ? this.funoutTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
|
||||
ps.withChannelNotes ? this.funoutTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
|
||||
this.fanoutTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId),
|
||||
ps.withReplies ? this.fanoutTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
|
||||
ps.withChannelNotes ? this.fanoutTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
|
||||
]);
|
||||
|
||||
let noteIds = Array.from(new Set([
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export class FeedService {
|
|||
const feed = new Feed({
|
||||
id: author.link,
|
||||
title: `${author.name} (@${user.username}@${this.config.host})`,
|
||||
updated: this.idService.parse(notes[0].id).date,
|
||||
updated: notes.length !== 0 ? this.idService.parse(notes[0].id).date : undefined,
|
||||
generator: 'Misskey',
|
||||
description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
|
||||
link: author.link,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue