2023-10-09 11:31:39 +00:00
|
|
|
/*
|
|
|
|
* 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()
|
2023-11-26 01:02:22 +00:00
|
|
|
export class FanoutTimelineService {
|
2023-10-09 11:31:39 +00:00
|
|
|
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)
|
2023-10-09 12:23:07 +00:00
|
|
|
.then(ids => ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1));
|
2023-10-09 11:31:39 +00:00
|
|
|
} else if (untilId) {
|
|
|
|
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
2023-10-09 12:23:07 +00:00
|
|
|
.then(ids => ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1));
|
2023-10-09 11:31:39 +00:00
|
|
|
} else if (sinceId) {
|
|
|
|
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
2023-10-09 12:23:07 +00:00
|
|
|
.then(ids => ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1));
|
2023-10-09 11:31:39 +00:00
|
|
|
} else {
|
|
|
|
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
2023-10-09 12:52:31 +00:00
|
|
|
.then(ids => ids.sort((a, b) => a > b ? -1 : 1));
|
2023-10-09 11:31:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@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)
|
2023-10-09 12:23:07 +00:00
|
|
|
? ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1)
|
2023-10-09 11:31:39 +00:00
|
|
|
: untilId
|
2023-10-09 12:23:07 +00:00
|
|
|
? ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1)
|
2023-10-09 11:31:39 +00:00
|
|
|
: sinceId
|
2023-10-09 12:23:07 +00:00
|
|
|
? ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1)
|
2023-10-09 12:52:31 +00:00
|
|
|
: ids.sort((a, b) => a > b ? -1 : 1),
|
2023-10-09 11:31:39 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2023-10-18 03:26:16 +00:00
|
|
|
|
|
|
|
@bindThis
|
|
|
|
public purge(name: string) {
|
|
|
|
return this.redisForTimelines.del('list:' + name);
|
|
|
|
}
|
2023-10-09 11:31:39 +00:00
|
|
|
}
|