mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 15:34:13 +00:00 
			
		
		
		
	
							parent
							
								
									2a88d8ee2d
								
							
						
					
					
						commit
						6cc02fee99
					
				
					 5 changed files with 190 additions and 81 deletions
				
			
		| 
						 | 
				
			
			@ -19,6 +19,8 @@
 | 
			
		|||
- Feat: サーバーサイレンス機能が追加されました
 | 
			
		||||
- Enhance: 依存関係の更新
 | 
			
		||||
- Enhance: 新規にフォローした人のをデフォルトでTLに追加できるように
 | 
			
		||||
- Enhance: HTLとLTLを2023.10.0アップデート以前まで遡れるように
 | 
			
		||||
- Enhance: フォロー/フォロー解除したときに過去分のHTLにも含まれる投稿が反映されるように
 | 
			
		||||
 | 
			
		||||
### Client
 | 
			
		||||
- Enhance: TLの返信表示オプションを記憶するように
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,4 +77,9 @@ export class FunoutTimelineService {
 | 
			
		|||
			);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public purge(name: string) {
 | 
			
		||||
		return this.redisForTimelines.del('list:' + name);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +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 Logger from '../logger.js';
 | 
			
		||||
 | 
			
		||||
const logger = new Logger('following/create');
 | 
			
		||||
| 
						 | 
				
			
			@ -83,6 +84,7 @@ export class UserFollowingService implements OnModuleInit {
 | 
			
		|||
		private webhookService: WebhookService,
 | 
			
		||||
		private apRendererService: ApRendererService,
 | 
			
		||||
		private accountMoveService: AccountMoveService,
 | 
			
		||||
		private funoutTimelineService: FunoutTimelineService,
 | 
			
		||||
		private perUserFollowingChart: PerUserFollowingChart,
 | 
			
		||||
		private instanceChart: InstanceChart,
 | 
			
		||||
	) {
 | 
			
		||||
| 
						 | 
				
			
			@ -288,8 +290,8 @@ export class UserFollowingService implements OnModuleInit {
 | 
			
		|||
			this.perUserFollowingChart.update(follower, followee, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Publish follow event
 | 
			
		||||
		if (this.userEntityService.isLocalUser(follower) && !silent) {
 | 
			
		||||
			// Publish follow event
 | 
			
		||||
			this.userEntityService.pack(followee.id, follower, {
 | 
			
		||||
				detail: true,
 | 
			
		||||
			}).then(async packed => {
 | 
			
		||||
| 
						 | 
				
			
			@ -302,6 +304,8 @@ export class UserFollowingService implements OnModuleInit {
 | 
			
		|||
					});
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Publish followed event
 | 
			
		||||
| 
						 | 
				
			
			@ -355,8 +359,8 @@ export class UserFollowingService implements OnModuleInit {
 | 
			
		|||
 | 
			
		||||
		this.decrementFollowing(following.follower, following.followee);
 | 
			
		||||
 | 
			
		||||
		// Publish unfollow event
 | 
			
		||||
		if (!silent && this.userEntityService.isLocalUser(follower)) {
 | 
			
		||||
			// Publish unfollow event
 | 
			
		||||
			this.userEntityService.pack(followee.id, follower, {
 | 
			
		||||
				detail: true,
 | 
			
		||||
			}).then(async packed => {
 | 
			
		||||
| 
						 | 
				
			
			@ -369,6 +373,8 @@ export class UserFollowingService implements OnModuleInit {
 | 
			
		|||
					});
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -707,4 +713,12 @@ export class UserFollowingService implements OnModuleInit {
 | 
			
		|||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public getFollowees(userId: MiUser['id']) {
 | 
			
		||||
		return this.followingsRepository.createQueryBuilder('following')
 | 
			
		||||
			.select('following.followeeId')
 | 
			
		||||
			.where('following.followerId = :followerId', { followerId: userId })
 | 
			
		||||
			.getMany();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@
 | 
			
		|||
 | 
			
		||||
import { Brackets } from 'typeorm';
 | 
			
		||||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import * as Redis from 'ioredis';
 | 
			
		||||
import type { MiNote, NotesRepository } from '@/models/_.js';
 | 
			
		||||
import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
			
		||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +15,7 @@ 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 { QueryService } from '@/core/QueryService.js';
 | 
			
		||||
import { ApiError } from '../../error.js';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
| 
						 | 
				
			
			@ -59,9 +59,6 @@ export const paramDef = {
 | 
			
		|||
@Injectable()
 | 
			
		||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | 
			
		||||
	constructor(
 | 
			
		||||
		@Inject(DI.redisForTimelines)
 | 
			
		||||
		private redisForTimelines: Redis.Redis,
 | 
			
		||||
 | 
			
		||||
		@Inject(DI.notesRepository)
 | 
			
		||||
		private notesRepository: NotesRepository,
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
		private idService: IdService,
 | 
			
		||||
		private cacheService: CacheService,
 | 
			
		||||
		private funoutTimelineService: FunoutTimelineService,
 | 
			
		||||
		private queryService: QueryService,
 | 
			
		||||
	) {
 | 
			
		||||
		super(meta, paramDef, async (ps, me) => {
 | 
			
		||||
			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
 | 
			
		||||
| 
						 | 
				
			
			@ -106,10 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
 | 
			
		||||
			noteIds = noteIds.slice(0, ps.limit);
 | 
			
		||||
 | 
			
		||||
			if (noteIds.length === 0) {
 | 
			
		||||
				return [];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (noteIds.length > 0) {
 | 
			
		||||
				const query = this.notesRepository.createQueryBuilder('note')
 | 
			
		||||
					.where('note.id IN (:...noteIds)', { noteIds: noteIds })
 | 
			
		||||
					.innerJoinAndSelect('note.user', 'user')
 | 
			
		||||
| 
						 | 
				
			
			@ -149,6 +144,35 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				});
 | 
			
		||||
 | 
			
		||||
				return await this.noteEntityService.packMany(timeline, me);
 | 
			
		||||
			} else { // fallback to db
 | 
			
		||||
				const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
 | 
			
		||||
					ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
 | 
			
		||||
					.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
 | 
			
		||||
					.innerJoinAndSelect('note.user', 'user')
 | 
			
		||||
					.leftJoinAndSelect('note.reply', 'reply')
 | 
			
		||||
					.leftJoinAndSelect('note.renote', 'renote')
 | 
			
		||||
					.leftJoinAndSelect('reply.user', 'replyUser')
 | 
			
		||||
					.leftJoinAndSelect('renote.user', 'renoteUser');
 | 
			
		||||
 | 
			
		||||
				this.queryService.generateVisibilityQuery(query, me);
 | 
			
		||||
				if (me) this.queryService.generateMutedUserQuery(query, me);
 | 
			
		||||
				if (me) this.queryService.generateBlockedUserQuery(query, me);
 | 
			
		||||
				if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
 | 
			
		||||
 | 
			
		||||
				if (ps.withFiles) {
 | 
			
		||||
					query.andWhere('note.fileIds != \'{}\'');
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				const timeline = await query.limit(ps.limit).getMany();
 | 
			
		||||
 | 
			
		||||
				process.nextTick(() => {
 | 
			
		||||
					if (me) {
 | 
			
		||||
						this.activeUsersChart.read(me);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				return await this.noteEntityService.packMany(timeline, me);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,8 +5,7 @@
 | 
			
		|||
 | 
			
		||||
import { Brackets } from 'typeorm';
 | 
			
		||||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import * as Redis from 'ioredis';
 | 
			
		||||
import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js';
 | 
			
		||||
import type { NotesRepository } from '@/models/_.js';
 | 
			
		||||
import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
			
		||||
import { QueryService } from '@/core/QueryService.js';
 | 
			
		||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +15,7 @@ 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 { UserFollowingService } from '@/core/UserFollowingService.js';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	tags: ['notes'],
 | 
			
		||||
| 
						 | 
				
			
			@ -53,9 +53,6 @@ export const paramDef = {
 | 
			
		|||
@Injectable()
 | 
			
		||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | 
			
		||||
	constructor(
 | 
			
		||||
		@Inject(DI.redisForTimelines)
 | 
			
		||||
		private redisForTimelines: Redis.Redis,
 | 
			
		||||
 | 
			
		||||
		@Inject(DI.notesRepository)
 | 
			
		||||
		private notesRepository: NotesRepository,
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +61,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
		private idService: IdService,
 | 
			
		||||
		private cacheService: CacheService,
 | 
			
		||||
		private funoutTimelineService: FunoutTimelineService,
 | 
			
		||||
		private userFollowingService: UserFollowingService,
 | 
			
		||||
		private queryService: QueryService,
 | 
			
		||||
	) {
 | 
			
		||||
		super(meta, paramDef, async (ps, me) => {
 | 
			
		||||
			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
 | 
			
		||||
| 
						 | 
				
			
			@ -84,10 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
			let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId);
 | 
			
		||||
			noteIds = noteIds.slice(0, ps.limit);
 | 
			
		||||
 | 
			
		||||
			if (noteIds.length === 0) {
 | 
			
		||||
				return [];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (noteIds.length > 0) {
 | 
			
		||||
				const query = this.notesRepository.createQueryBuilder('note')
 | 
			
		||||
					.where('note.id IN (:...noteIds)', { noteIds: noteIds })
 | 
			
		||||
					.innerJoinAndSelect('note.user', 'user')
 | 
			
		||||
| 
						 | 
				
			
			@ -127,6 +123,74 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				});
 | 
			
		||||
 | 
			
		||||
				return await this.noteEntityService.packMany(timeline, me);
 | 
			
		||||
			} else { // fallback to db
 | 
			
		||||
				const followees = await this.userFollowingService.getFollowees(me.id);
 | 
			
		||||
 | 
			
		||||
				//#region Construct query
 | 
			
		||||
				const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
 | 
			
		||||
					ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
 | 
			
		||||
					.innerJoinAndSelect('note.user', 'user')
 | 
			
		||||
					.leftJoinAndSelect('note.reply', 'reply')
 | 
			
		||||
					.leftJoinAndSelect('note.renote', 'renote')
 | 
			
		||||
					.leftJoinAndSelect('reply.user', 'replyUser')
 | 
			
		||||
					.leftJoinAndSelect('renote.user', 'renoteUser');
 | 
			
		||||
 | 
			
		||||
				if (followees.length > 0) {
 | 
			
		||||
					const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
 | 
			
		||||
 | 
			
		||||
					query.andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
 | 
			
		||||
				} else {
 | 
			
		||||
					query.andWhere('note.userId = :meId', { meId: me.id });
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				this.queryService.generateVisibilityQuery(query, me);
 | 
			
		||||
				this.queryService.generateMutedUserQuery(query, me);
 | 
			
		||||
				this.queryService.generateBlockedUserQuery(query, me);
 | 
			
		||||
				this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
 | 
			
		||||
 | 
			
		||||
				if (ps.includeMyRenotes === false) {
 | 
			
		||||
					query.andWhere(new Brackets(qb => {
 | 
			
		||||
						qb.orWhere('note.userId != :meId', { meId: me.id });
 | 
			
		||||
						qb.orWhere('note.renoteId IS NULL');
 | 
			
		||||
						qb.orWhere('note.text IS NOT NULL');
 | 
			
		||||
						qb.orWhere('note.fileIds != \'{}\'');
 | 
			
		||||
						qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
 | 
			
		||||
					}));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (ps.includeRenotedMyNotes === false) {
 | 
			
		||||
					query.andWhere(new Brackets(qb => {
 | 
			
		||||
						qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
 | 
			
		||||
						qb.orWhere('note.renoteId IS NULL');
 | 
			
		||||
						qb.orWhere('note.text IS NOT NULL');
 | 
			
		||||
						qb.orWhere('note.fileIds != \'{}\'');
 | 
			
		||||
						qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
 | 
			
		||||
					}));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (ps.includeLocalRenotes === false) {
 | 
			
		||||
					query.andWhere(new Brackets(qb => {
 | 
			
		||||
						qb.orWhere('note.renoteUserHost IS NOT NULL');
 | 
			
		||||
						qb.orWhere('note.renoteId IS NULL');
 | 
			
		||||
						qb.orWhere('note.text IS NOT NULL');
 | 
			
		||||
						qb.orWhere('note.fileIds != \'{}\'');
 | 
			
		||||
						qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
 | 
			
		||||
					}));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (ps.withFiles) {
 | 
			
		||||
					query.andWhere('note.fileIds != \'{}\'');
 | 
			
		||||
				}
 | 
			
		||||
				//#endregion
 | 
			
		||||
 | 
			
		||||
				const timeline = await query.limit(ps.limit).getMany();
 | 
			
		||||
 | 
			
		||||
				process.nextTick(() => {
 | 
			
		||||
					this.activeUsersChart.read(me);
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				return await this.noteEntityService.packMany(timeline, me);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue