summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/FunoutTimelineService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/core/FunoutTimelineService.ts')
-rw-r--r--packages/backend/src/core/FunoutTimelineService.ts80
1 files changed, 80 insertions, 0 deletions
diff --git a/packages/backend/src/core/FunoutTimelineService.ts b/packages/backend/src/core/FunoutTimelineService.ts
new file mode 100644
index 0000000000..512b2d9833
--- /dev/null
+++ b/packages/backend/src/core/FunoutTimelineService.ts
@@ -0,0 +1,80 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
+
+@Injectable()
+export class FunoutTimelineService {
+ constructor(
+ @Inject(DI.redisForTimelines)
+ private redisForTimelines: Redis.Redis,
+
+ private idService: IdService,
+ ) {
+ }
+
+ @bindThis
+ public push(tl: string, id: string, maxlen: number, pipeline: Redis.ChainableCommander) {
+ // リモートから遅れて届いた(もしくは後から追加された)投稿日時が古い投稿が追加されるとページネーション時に問題を引き起こすため、
+ // 3分以内に投稿されたものでない場合、Redisにある最古のIDより新しい場合のみ追加する
+ if (this.idService.parse(id).date.getTime() > Date.now() - 1000 * 60 * 3) {
+ pipeline.lpush('list:' + tl, id);
+ if (Math.random() < 0.1) { // 10%の確率でトリム
+ pipeline.ltrim('list:' + tl, 0, maxlen - 1);
+ }
+ } else {
+ // 末尾のIDを取得
+ this.redisForTimelines.lindex('list:' + tl, -1).then(lastId => {
+ if (lastId == null || (this.idService.parse(id).date.getTime() > this.idService.parse(lastId).date.getTime())) {
+ this.redisForTimelines.lpush('list:' + tl, id);
+ } else {
+ Promise.resolve();
+ }
+ });
+ }
+ }
+
+ @bindThis
+ public get(name: string, untilId?: string | null, sinceId?: string | null) {
+ if (untilId && sinceId) {
+ return this.redisForTimelines.lrange('list:' + name, 0, -1)
+ .then(ids => ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1));
+ } else if (untilId) {
+ return this.redisForTimelines.lrange('list:' + name, 0, -1)
+ .then(ids => ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1));
+ } else if (sinceId) {
+ return this.redisForTimelines.lrange('list:' + name, 0, -1)
+ .then(ids => ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1));
+ } else {
+ return this.redisForTimelines.lrange('list:' + name, 0, -1)
+ .then(ids => ids.sort((a, b) => a > b ? -1 : 1));
+ }
+ }
+
+ @bindThis
+ public getMulti(name: string[], untilId?: string | null, sinceId?: string | null): Promise<string[][]> {
+ const pipeline = this.redisForTimelines.pipeline();
+ for (const n of name) {
+ pipeline.lrange('list:' + n, 0, -1);
+ }
+ return pipeline.exec().then(res => {
+ if (res == null) return [];
+ const tls = res.map(r => r[1] as string[]);
+ return tls.map(ids =>
+ (untilId && sinceId)
+ ? ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1)
+ : untilId
+ ? ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1)
+ : sinceId
+ ? ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1)
+ : ids.sort((a, b) => a > b ? -1 : 1),
+ );
+ });
+ }
+}