(enhance) `notes/create` で予約投稿できるように
This commit is contained in:
parent
1995400dac
commit
2bc15c09ed
|
@ -32,9 +32,9 @@ export class ScheduleNotePostProcessorService {
|
|||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<ScheduleNotePostJobData>): Promise<void> {
|
||||
this.scheduledNotesRepository.findOneBy({ id: job.data.scheduleNoteId }).then(async (data) => {
|
||||
this.scheduledNotesRepository.findOneBy({ id: job.data.scheduledNoteId }).then(async (data) => {
|
||||
if (!data) {
|
||||
this.logger.warn(`Schedule note ${job.data.scheduleNoteId} not found`);
|
||||
this.logger.warn(`Schedule note ${job.data.scheduledNoteId} not found`);
|
||||
} else {
|
||||
data.note.createdAt = new Date();
|
||||
const me = await this.usersRepository.findOneByOrFail({ id: data.userId });
|
||||
|
|
|
@ -109,7 +109,7 @@ export type EndedPollNotificationJobData = {
|
|||
};
|
||||
|
||||
export type ScheduleNotePostJobData = {
|
||||
scheduleNoteId: MiNote['id'];
|
||||
scheduledNoteId: MiNote['id'];
|
||||
}
|
||||
|
||||
type MinimumUser = {
|
||||
|
|
|
@ -7,7 +7,8 @@ import ms from 'ms';
|
|||
import { In } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/_.js';
|
||||
import type { UsersRepository, NotesRepository, ScheduledNotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/_.js';
|
||||
import type { MiNoteCreateOption } from '@/types.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { MiChannel } from '@/models/Channel.js';
|
||||
|
@ -15,6 +16,8 @@ import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
@ -39,9 +42,17 @@ export const meta = {
|
|||
properties: {
|
||||
createdNote: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
ref: 'Note',
|
||||
},
|
||||
scheduledNoteId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
scheduledNote: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -105,6 +116,22 @@ export const meta = {
|
|||
code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
|
||||
id: '33510210-8452-094c-6227-4a6c05d99f00',
|
||||
},
|
||||
|
||||
cannotCreateAlreadyExpiredSchedule: {
|
||||
message: 'Schedule is already expired.',
|
||||
code: 'CANNOT_CREATE_ALREADY_EXPIRED_SCHEDULE',
|
||||
id: '8a9bfb90-fc7e-4878-a3e8-d97faaf5fb07',
|
||||
},
|
||||
specifyScheduleDate: {
|
||||
message: 'Please specify schedule date.',
|
||||
code: 'PLEASE_SPECIFY_SCHEDULE_DATE',
|
||||
id: 'c93a6ad6-f7e2-4156-a0c2-3d03529e5e0f',
|
||||
},
|
||||
noSuchSchedule: {
|
||||
message: 'No such schedule.',
|
||||
code: 'NO_SUCH_SCHEDULE',
|
||||
id: '44dee229-8da1-4a61-856d-e3a4bbc12032',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
@ -164,6 +191,13 @@ export const paramDef = {
|
|||
},
|
||||
required: ['choices'],
|
||||
},
|
||||
schedule: {
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
properties: {
|
||||
expiresAt: { type: 'integer', nullable: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
// (re)note with text, files and poll are optional
|
||||
anyOf: [
|
||||
|
@ -172,6 +206,7 @@ export const paramDef = {
|
|||
{ required: ['fileIds'] },
|
||||
{ required: ['mediaIds'] },
|
||||
{ required: ['poll'] },
|
||||
{ required: ['schedule'] },
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
@ -184,6 +219,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
@Inject(DI.scheduledNotesRepository)
|
||||
private scheduledNotesRepository: ScheduledNotesRepository,
|
||||
|
||||
@Inject(DI.blockingsRepository)
|
||||
private blockingsRepository: BlockingsRepository,
|
||||
|
||||
|
@ -195,6 +233,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteCreateService: NoteCreateService,
|
||||
|
||||
private queueService: QueueService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
let visibleUsers: MiUser[] = [];
|
||||
|
@ -311,8 +352,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
}
|
||||
|
||||
// 投稿を作成
|
||||
const note = await this.noteCreateService.create(me, {
|
||||
const note: MiNoteCreateOption = {
|
||||
createdAt: new Date(),
|
||||
files: files,
|
||||
poll: ps.poll ? {
|
||||
|
@ -332,11 +372,45 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
apMentions: ps.noExtractMentions ? [] : undefined,
|
||||
apHashtags: ps.noExtractHashtags ? [] : undefined,
|
||||
apEmojis: ps.noExtractEmojis ? [] : undefined,
|
||||
};
|
||||
|
||||
if (ps.schedule) {
|
||||
if (!ps.schedule.expiresAt) {
|
||||
throw new ApiError(meta.errors.specifyScheduleDate);
|
||||
}
|
||||
|
||||
me.token = null;
|
||||
const scheduledNoteId = this.idService.gen(new Date().getTime());
|
||||
await this.scheduledNotesRepository.insert({
|
||||
id: scheduledNoteId,
|
||||
note: note,
|
||||
userId: me.id,
|
||||
expiresAt: new Date(ps.schedule.expiresAt),
|
||||
});
|
||||
|
||||
const delay = new Date(ps.schedule.expiresAt).getTime() - Date.now();
|
||||
await this.queueService.ScheduleNotePostQueue.add(String(delay), {
|
||||
scheduledNoteId,
|
||||
}, {
|
||||
jobId: scheduledNoteId,
|
||||
delay,
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
return {
|
||||
createdNote: await this.noteEntityService.pack(note, me),
|
||||
scheduledNoteId,
|
||||
scheduledNote: note,
|
||||
|
||||
// ↓互換性のため(微妙)
|
||||
createdNote: null,
|
||||
};
|
||||
} else {
|
||||
// 投稿を作成
|
||||
const createdNoteRaw = await this.noteCreateService.create(me, note);
|
||||
return {
|
||||
createdNote: await this.noteEntityService.pack(createdNoteRaw, me),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { ScheduledNotesRepository } from '@/models/_.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
|
@ -31,9 +32,9 @@ export const meta = {
|
|||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
noteId: { type: 'string', format: 'misskey:id' },
|
||||
scheduledNoteId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['noteId'],
|
||||
required: ['scheduledNoteId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -41,9 +42,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
@Inject(DI.scheduledNotesRepository)
|
||||
private scheduledNotesRepository: ScheduledNotesRepository,
|
||||
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.scheduledNotesRepository.delete({ id: ps.noteId });
|
||||
await this.scheduledNotesRepository.delete({ id: ps.scheduledNoteId });
|
||||
if (ps.scheduledNoteId) {
|
||||
await this.queueService.ScheduleNotePostQueue.remove(ps.scheduledNoteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
user: user,
|
||||
createdAt: new Date(item.expiresAt),
|
||||
isSchedule: true,
|
||||
// ↓TODO: NoteのIDに予約投稿IDを入れたくない(本来別ものなため)
|
||||
id: item.id,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -40,7 +40,9 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
async function deleteScheduleNote() {
|
||||
await os.apiWithDialog('notes/schedule/delete', { noteId: props.note.id })
|
||||
if (!props.note.isSchedule) return;
|
||||
// スケジュールつきノートの場合は、ノートIDのフィールドに予約投稿ID(scheduledNoteId)が入るので注意!!!!
|
||||
await os.apiWithDialog('notes/schedule/delete', { scheduledNoteId: props.note.id })
|
||||
.then(() => {
|
||||
isDeleted.value = true;
|
||||
});
|
||||
|
|
|
@ -751,7 +751,7 @@ async function post(ev?: MouseEvent) {
|
|||
renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
|
||||
channelId: props.channel ? props.channel.id : undefined,
|
||||
schedule,
|
||||
poll: poll,
|
||||
poll,
|
||||
cw: useCw ? cw ?? '' : null,
|
||||
localOnly: localOnly,
|
||||
visibility: visibility,
|
||||
|
@ -783,11 +783,11 @@ async function post(ev?: MouseEvent) {
|
|||
|
||||
if (postAccount) {
|
||||
const storedAccounts = await getAccounts();
|
||||
token = storedAccounts.find(x => x.id === postAccount.id)?.token;
|
||||
token = storedAccounts.find(x => x.id === postAccount?.id)?.token;
|
||||
}
|
||||
|
||||
posting = true;
|
||||
os.api(postData.schedule ? 'notes/schedule/create' : 'notes/create', postData, token).then(() => {
|
||||
os.api('notes/create', postData, token).then(() => {
|
||||
if (props.freezeAfterPosted) {
|
||||
posted = true;
|
||||
} else {
|
||||
|
|
|
@ -1748,6 +1748,9 @@ export type Endpoints = {
|
|||
expiresAt?: null | number;
|
||||
expiredAfter?: null | number;
|
||||
};
|
||||
schedule?: null | {
|
||||
expiresAt?: null | number;
|
||||
};
|
||||
};
|
||||
res: {
|
||||
createdNote: Note;
|
||||
|
@ -1771,13 +1774,18 @@ export type Endpoints = {
|
|||
};
|
||||
'notes/schedule/delete': {
|
||||
req: {
|
||||
noteId: Note['id'];
|
||||
scheduledNoteId: Note['id'];
|
||||
};
|
||||
res: null;
|
||||
};
|
||||
'notes/schedule/list': {
|
||||
req: TODO;
|
||||
res: Note[];
|
||||
res: {
|
||||
id: Note['id'];
|
||||
userId: User['id'];
|
||||
expiresAt: number;
|
||||
note: Note;
|
||||
}[];
|
||||
};
|
||||
'notes/favorites/create': {
|
||||
req: {
|
||||
|
@ -3055,7 +3063,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
|||
//
|
||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:635:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:643:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:116:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:627:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||
|
|
|
@ -507,11 +507,19 @@ export type Endpoints = {
|
|||
expiresAt?: null | number;
|
||||
expiredAfter?: null | number;
|
||||
};
|
||||
schedule?: null | {
|
||||
expiresAt?: null | number;
|
||||
};
|
||||
}; res: { createdNote: Note }; };
|
||||
'notes/delete': { req: { noteId: Note['id']; }; res: null; };
|
||||
'notes/schedule/create': { req: Partial<Note> & { schedule: { expiresAt: number; } }; res: { createdNote: Note }; };
|
||||
'notes/schedule/delete': { req: { noteId: Note['id']; }; res: null; };
|
||||
'notes/schedule/list': { req: TODO; res: Note[]; };
|
||||
'notes/schedule/delete': { req: { scheduledNoteId: Note['id']; }; res: null; };
|
||||
'notes/schedule/list': { req: TODO; res: {
|
||||
id: Note['id'];
|
||||
userId: User['id'];
|
||||
expiresAt: number;
|
||||
note: Note;
|
||||
}[]; };
|
||||
'notes/favorites/create': { req: { noteId: Note['id']; }; res: null; };
|
||||
'notes/favorites/delete': { req: { noteId: Note['id']; }; res: null; };
|
||||
'notes/featured': { req: TODO; res: Note[]; };
|
||||
|
|
Loading…
Reference in New Issue