新規にフォローした人のwithRepliesをtrueにする機能を追加 (#12048)
* feat: add defaultWithReplies to MiUser * feat: use defaultWithReplies when creating MiFollowing * feat: update defaultWithReplies from API * feat: return defaultWithReplies as a part of $i * feat(frontend): configure defaultWithReplies * docs(changelog): 新規にフォローした人のをデフォルトでTL二追加できるように * fix: typo * style: fix lint failure * chore: improve UI text * chore: make optional params of UserFollowingService.follow() object * chore: UserFollowingService.follow() accept withReplies * chore: add withReplies to MiFollowRequest * chore: process withReplies for follow request * feat: accept withReplies on 'following/create' endpoint * feat: store defaultWithReplies in client store * Revert "feat: return defaultWithReplies as a part of $i" This reverts commitf2cc4fe6
* Revert "feat: update defaultWithReplies from API" This reverts commit95e3cee6
* Revert "feat: add defaultWithReplies to MiUser" This reverts commit9f5ab14d70
. * feat: configuring withReplies in import-following * feat(frontend): configure withReplies * fix(frontend): incorrectly showRepliesToOthersInTimeline can be shown * fix(backend): withReplies of following/create not working * fix(frontend): importFollowing error * fix: withReplies is not working with follow import * fix(frontend): use v-model * style: fix lint --------- Co-authored-by: Sayamame-beans <61457993+sayamame-beans@users.noreply.github.com> Co-authored-by: syuilo <syuilotan@yahoo.co.jp>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
export class FollowRequestWithReplies1697441463087 {
|
||||
name = 'FollowRequestWithReplies1697441463087'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "follow_request" ADD "withReplies" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "follow_request" DROP COLUMN "withReplies"`);
|
||||
}
|
||||
}
|
@@ -237,10 +237,11 @@ export class QueueService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id']) {
|
||||
public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id'], withReplies?: boolean) {
|
||||
return this.dbQueue.add('importFollowing', {
|
||||
user: { id: user.id },
|
||||
fileId: fileId,
|
||||
withReplies,
|
||||
}, {
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
@@ -248,8 +249,8 @@ export class QueueService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public createImportFollowingToDbJob(user: ThinUser, targets: string[]) {
|
||||
const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel }));
|
||||
public createImportFollowingToDbJob(user: ThinUser, targets: string[], withReplies?: boolean) {
|
||||
const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel, withReplies }));
|
||||
return this.dbQueue.addBulk(jobs);
|
||||
}
|
||||
|
||||
@@ -342,7 +343,7 @@ export class QueueService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean }[]) {
|
||||
public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean, withReplies?: boolean }[]) {
|
||||
const jobs = followings.map(rel => this.generateRelationshipJobData('follow', rel));
|
||||
return this.relationshipQueue.addBulk(jobs);
|
||||
}
|
||||
@@ -384,6 +385,7 @@ export class QueueService {
|
||||
to: { id: data.to.id },
|
||||
silent: data.silent,
|
||||
requestId: data.requestId,
|
||||
withReplies: data.withReplies,
|
||||
},
|
||||
opts: {
|
||||
removeOnComplete: true,
|
||||
|
@@ -93,7 +93,15 @@ export class UserFollowingService implements OnModuleInit {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async follow(_follower: { id: MiUser['id'] }, _followee: { id: MiUser['id'] }, requestId?: string, silent = false): Promise<void> {
|
||||
public async follow(
|
||||
_follower: { id: MiUser['id'] },
|
||||
_followee: { id: MiUser['id'] },
|
||||
{ requestId, silent = false, withReplies }: {
|
||||
requestId?: string,
|
||||
silent?: boolean,
|
||||
withReplies?: boolean,
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const [follower, followee] = await Promise.all([
|
||||
this.usersRepository.findOneByOrFail({ id: _follower.id }),
|
||||
this.usersRepository.findOneByOrFail({ id: _followee.id }),
|
||||
@@ -171,12 +179,12 @@ export class UserFollowingService implements OnModuleInit {
|
||||
}
|
||||
|
||||
if (!autoAccept) {
|
||||
await this.createFollowRequest(follower, followee, requestId);
|
||||
await this.createFollowRequest(follower, followee, requestId, withReplies);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await this.insertFollowingDoc(followee, follower, silent);
|
||||
await this.insertFollowingDoc(followee, follower, silent, withReplies);
|
||||
|
||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
|
||||
@@ -193,6 +201,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']
|
||||
},
|
||||
silent = false,
|
||||
withReplies?: boolean,
|
||||
): Promise<void> {
|
||||
if (follower.id === followee.id) return;
|
||||
|
||||
@@ -202,6 +211,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
id: this.idService.gen(),
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
withReplies: withReplies,
|
||||
|
||||
// 非正規化
|
||||
followerHost: follower.host,
|
||||
@@ -454,6 +464,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox'];
|
||||
},
|
||||
requestId?: string,
|
||||
withReplies?: boolean,
|
||||
): Promise<void> {
|
||||
if (follower.id === followee.id) return;
|
||||
|
||||
@@ -471,6 +482,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
requestId,
|
||||
withReplies,
|
||||
|
||||
// 非正規化
|
||||
followerHost: follower.host,
|
||||
@@ -555,7 +567,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.');
|
||||
}
|
||||
|
||||
await this.insertFollowingDoc(followee, follower);
|
||||
await this.insertFollowingDoc(followee, follower, false, request.withReplies);
|
||||
|
||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee));
|
||||
|
@@ -164,7 +164,7 @@ export class ApInboxService {
|
||||
}
|
||||
|
||||
// don't queue because the sender may attempt again when timeout
|
||||
await this.userFollowingService.follow(actor, followee, activity.id);
|
||||
await this.userFollowingService.follow(actor, followee, { requestId: activity.id });
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
|
@@ -45,6 +45,11 @@ export class MiFollowRequest {
|
||||
})
|
||||
public requestId: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public withReplies: boolean;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
|
@@ -56,7 +56,7 @@ export class ImportFollowingProcessorService {
|
||||
|
||||
const csv = await this.downloadService.downloadTextFile(file.url);
|
||||
const targets = csv.trim().split('\n');
|
||||
this.queueService.createImportFollowingToDbJob({ id: user.id }, targets);
|
||||
this.queueService.createImportFollowingToDbJob({ id: user.id }, targets, job.data.withReplies);
|
||||
|
||||
this.logger.succ('Import jobs created');
|
||||
}
|
||||
@@ -93,9 +93,9 @@ export class ImportFollowingProcessorService {
|
||||
// skip myself
|
||||
if (target.id === job.data.user.id) return;
|
||||
|
||||
this.logger.info(`Follow ${target.id} ...`);
|
||||
this.logger.info(`Follow ${target.id} ${job.data.withReplies ? 'with replies' : 'without replies'} ...`);
|
||||
|
||||
this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true }]);
|
||||
this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true, withReplies: job.data.withReplies }]);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Error: ${e}`);
|
||||
}
|
||||
|
@@ -34,8 +34,12 @@ export class RelationshipProcessorService {
|
||||
|
||||
@bindThis
|
||||
public async processFollow(job: Bull.Job<RelationshipJobData>): Promise<string> {
|
||||
this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id}`);
|
||||
await this.userFollowingService.follow(job.data.from, job.data.to, job.data.requestId, job.data.silent);
|
||||
this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? "with replies" : "without replies"}`);
|
||||
await this.userFollowingService.follow(job.data.from, job.data.to, {
|
||||
requestId: job.data.requestId,
|
||||
silent: job.data.silent,
|
||||
withReplies: job.data.withReplies,
|
||||
});
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,7 @@ export type RelationshipJobData = {
|
||||
to: ThinUser;
|
||||
silent?: boolean;
|
||||
requestId?: string;
|
||||
withReplies?: boolean;
|
||||
}
|
||||
|
||||
export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T];
|
||||
@@ -79,6 +80,7 @@ export type DbUserDeleteJobData = {
|
||||
export type DbUserImportJobData = {
|
||||
user: ThinUser;
|
||||
fileId: MiDriveFile['id'];
|
||||
withReplies?: boolean;
|
||||
};
|
||||
|
||||
export type DBAntennaImportJobData = {
|
||||
@@ -89,6 +91,7 @@ export type DBAntennaImportJobData = {
|
||||
export type DbUserImportToDbJobData = {
|
||||
user: ThinUser;
|
||||
target: string;
|
||||
withReplies?: boolean;
|
||||
};
|
||||
|
||||
export type ObjectStorageJobData = ObjectStorageFileJobData | Record<string, unknown>;
|
||||
|
@@ -71,6 +71,7 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
withReplies: { type: 'boolean' }
|
||||
},
|
||||
required: ['userId'],
|
||||
} as const;
|
||||
@@ -112,7 +113,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
}
|
||||
|
||||
try {
|
||||
await this.userFollowingService.follow(follower, followee);
|
||||
await this.userFollowingService.follow(follower, followee, { withReplies: ps.withReplies });
|
||||
} catch (e) {
|
||||
if (e instanceof IdentifiableError) {
|
||||
if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking);
|
||||
|
@@ -52,6 +52,7 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
withReplies: { type: 'boolean' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
} as const;
|
||||
@@ -79,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
);
|
||||
if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
|
||||
|
||||
this.queueService.createImportFollowingJob(me, file.id);
|
||||
this.queueService.createImportFollowingJob(me, file.id, ps.withReplies);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user