Skip to content

Commit b63166a

Browse files
authored
予約投稿 (kokonect-link#483)
2 parents 2801c8c + b6a6379 commit b63166a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1500
-35
lines changed

locales/index.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2946,10 +2946,18 @@ export interface Locale extends ILocale {
29462946
* アンケート
29472947
*/
29482948
"poll": string;
2949+
/**
2950+
* 予約投稿
2951+
*/
2952+
"schedulePost": string;
29492953
/**
29502954
* 内容を隠す
29512955
*/
29522956
"useCw": string;
2957+
/**
2958+
* 予約投稿一覧
2959+
*/
2960+
"schedulePostList": string;
29532961
/**
29542962
* プレイヤーを開く
29552963
*/
@@ -7678,6 +7686,10 @@ export interface Locale extends ILocale {
76787686
* ノートの編集
76797687
*/
76807688
"canEditNote": string;
7689+
/**
7690+
* 予約投稿の最大数
7691+
*/
7692+
"scheduleNoteMax": string;
76817693
/**
76827694
* ノート内の最大メンション数
76837695
*/
@@ -9333,6 +9345,14 @@ export interface Locale extends ILocale {
93339345
* ノートを作成・削除する
93349346
*/
93359347
"write:notes": string;
9348+
/**
9349+
* 予約投稿を見る
9350+
*/
9351+
"read:notes-schedule": string;
9352+
/**
9353+
* 予約投稿を作成・削除する
9354+
*/
9355+
"write:notes-schedule": string;
93369356
/**
93379357
* 通知を見る
93389358
*/

locales/ja-JP.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,9 @@ invisibleNote: "非公開の投稿"
731731
enableInfiniteScroll: "自動でもっと見る"
732732
visibility: "公開範囲"
733733
poll: "アンケート"
734+
schedulePost: "予約投稿"
734735
useCw: "内容を隠す"
736+
schedulePostList: "予約投稿一覧"
735737
enablePlayer: "プレイヤーを開く"
736738
disablePlayer: "プレイヤーを閉じる"
737739
expandTweet: "ポストを展開する"
@@ -1993,6 +1995,7 @@ _role:
19931995
ltlAvailable: "ローカルタイムラインの閲覧"
19941996
canPublicNote: "パブリック投稿の許可"
19951997
canEditNote: "ノートの編集"
1998+
scheduleNoteMax: "予約投稿の最大数"
19961999
mentionMax: "ノート内の最大メンション数"
19972000
canInvite: "サーバー招待コードの発行"
19982001
inviteLimit: "招待コードの作成可能数"
@@ -2455,6 +2458,8 @@ _permissions:
24552458
"read:mutes": "ミュートを見る"
24562459
"write:mutes": "ミュートを操作する"
24572460
"write:notes": "ノートを作成・削除する"
2461+
"read:notes-schedule": "予約投稿を見る"
2462+
"write:notes-schedule": "予約投稿を作成・削除する"
24582463
"read:notifications": "通知を見る"
24592464
"write:notifications": "通知を操作する"
24602465
"read:reactions": "リアクションを見る"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* SPDX-FileCopyrightText: syuilo and misskey-project
3+
* SPDX-License-Identifier: AGPL-3.0-only
4+
*/
5+
6+
export class Schedulenote1699437894737 {
7+
name = 'Schedulenote1699437894737'
8+
9+
async up(queryRunner) {
10+
await queryRunner.query(`CREATE TABLE "note_schedule" ("id" character varying(32) NOT NULL, "note" jsonb NOT NULL, "userId" character varying(260) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_3a1ae2db41988f4994268218436" PRIMARY KEY ("id"))`);
11+
await queryRunner.query(`CREATE INDEX "IDX_e798958c40009bf0cdef4f28b5" ON "note_schedule" ("userId") `);
12+
}
13+
14+
async down(queryRunner) {
15+
await queryRunner.query(`DROP TABLE "note_schedule"`);
16+
}
17+
}

packages/backend/src/core/NoteCreateService.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
6363
import { isReply } from '@/misc/is-reply.js';
6464
import { trackPromise } from '@/misc/promise-tracker.js';
6565
import { IdentifiableError } from '@/misc/identifiable-error.js';
66+
import { MiNoteSchedule } from '@/models/_.js';
6667

6768
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
6869

@@ -139,6 +140,7 @@ type Option = {
139140
files?: MiDriveFile[] | null;
140141
poll?: IPoll | null;
141142
event?: IEvent | null;
143+
schedule?: MiNoteSchedule | null;
142144
localOnly?: boolean | null;
143145
reactionAcceptance?: MiNote['reactionAcceptance'];
144146
disableRightClick?: boolean | null;
@@ -237,33 +239,17 @@ export class NoteCreateService implements OnApplicationShutdown {
237239
isBot: MiUser['isBot'];
238240
isCat: MiUser['isCat'];
239241
}, data: Option, silent = false): Promise<MiNote> {
240-
// チャンネル外にリプライしたら対象のスコープに合わせる
241-
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
242-
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
243-
if (data.reply.channelId) {
244-
data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId });
245-
} else {
246-
data.channel = null;
247-
}
248-
}
249-
250-
// チャンネル内にリプライしたら対象のスコープに合わせる
251-
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
252-
if (data.reply && (data.channel == null) && data.reply.channelId) {
253-
data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId });
254-
}
242+
//このフォークではチャンネルの存在を認めない
243+
data.channel = undefined;
255244

256245
if (data.createdAt == null) data.createdAt = new Date();
257246
if (data.visibility == null) data.visibility = 'public';
258247
if (data.localOnly == null) data.localOnly = false;
259248
if (data.disableRightClick == null) data.disableRightClick = false;
260-
if (data.channel != null) data.visibility = 'public';
261-
if (data.channel != null) data.visibleUsers = [];
262-
if (data.channel != null) data.localOnly = true;
263249

264250
const meta = await this.metaService.fetch();
265251

266-
if (data.visibility === 'public' && data.channel == null) {
252+
if (data.visibility === 'public') {
267253
const sensitiveWords = meta.sensitiveWords;
268254
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
269255
data.visibility = 'home';
@@ -400,7 +386,8 @@ export class NoteCreateService implements OnApplicationShutdown {
400386
if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) {
401387
throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions');
402388
}
403-
389+
//このフォークではローカルのみ投稿を認めない
390+
data.localOnly = false;
404391
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
405392

406393
setImmediate('post created', { signal: this.#shutdownController.signal }).then(

packages/backend/src/core/QueueModule.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import {
1717
RelationshipJobData,
1818
UserWebhookDeliverJobData,
1919
SystemWebhookDeliverJobData,
20+
ScheduleNotePostJobData,
2021
} from '../queue/types.js';
2122
import type { Provider } from '@nestjs/common';
2223

2324
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
2425
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
26+
export type ScheduleNotePostQueue = Bull.Queue<ScheduleNotePostJobData>;
2527
export type DeliverQueue = Bull.Queue<DeliverJobData>;
2628
export type InboxQueue = Bull.Queue<InboxJobData>;
2729
export type DbQueue = Bull.Queue;
@@ -42,6 +44,12 @@ const $endedPollNotification: Provider = {
4244
inject: [DI.config, DI.redisForJobQueue],
4345
};
4446

47+
const $scheduleNotePost: Provider = {
48+
provide: 'queue:scheduleNotePost',
49+
useFactory: (config: Config, redisForJobQueue: Redis.Redis) => new Bull.Queue(QUEUE.SCHEDULE_NOTE_POST, baseQueueOptions(config, QUEUE.SCHEDULE_NOTE_POST, redisForJobQueue)),
50+
inject: [DI.config, DI.redisForJobQueue],
51+
};
52+
4553
const $deliver: Provider = {
4654
provide: 'queue:deliver',
4755
useFactory: (config: Config, redisForJobQueue: Redis.Redis) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER, redisForJobQueue)),
@@ -90,6 +98,7 @@ const $systemWebhookDeliver: Provider = {
9098
providers: [
9199
$system,
92100
$endedPollNotification,
101+
$scheduleNotePost,
93102
$deliver,
94103
$inbox,
95104
$db,
@@ -101,6 +110,7 @@ const $systemWebhookDeliver: Provider = {
101110
exports: [
102111
$system,
103112
$endedPollNotification,
113+
$scheduleNotePost,
104114
$deliver,
105115
$inbox,
106116
$db,
@@ -114,6 +124,7 @@ export class QueueModule implements OnApplicationShutdown {
114124
constructor(
115125
@Inject('queue:system') public systemQueue: SystemQueue,
116126
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
127+
@Inject('queue:scheduleNotePost') public scheduleNotePostQueue: ScheduleNotePostQueue,
117128
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
118129
@Inject('queue:inbox') public inboxQueue: InboxQueue,
119130
@Inject('queue:db') public dbQueue: DbQueue,
@@ -130,6 +141,7 @@ export class QueueModule implements OnApplicationShutdown {
130141
await Promise.all([
131142
this.systemQueue.close(),
132143
this.endedPollNotificationQueue.close(),
144+
this.scheduleNotePostQueue.close(),
133145
this.deliverQueue.close(),
134146
this.inboxQueue.close(),
135147
this.dbQueue.close(),

packages/backend/src/core/QueueService.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type {
3333
SystemQueue,
3434
UserWebhookDeliverQueue,
3535
SystemWebhookDeliverQueue,
36+
ScheduleNotePostQueue,
3637
} from './QueueModule.js';
3738
import type httpSignature from '@peertube/http-signature';
3839
import type * as Bull from 'bullmq';
@@ -45,6 +46,7 @@ export class QueueService {
4546

4647
@Inject('queue:system') public systemQueue: SystemQueue,
4748
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
49+
@Inject('queue:scheduleNotePost') public ScheduleNotePostQueue: ScheduleNotePostQueue,
4850
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
4951
@Inject('queue:inbox') public inboxQueue: InboxQueue,
5052
@Inject('queue:db') public dbQueue: DbQueue,

packages/backend/src/core/RoleService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export type RolePolicies = {
3636
ltlAvailable: boolean;
3737
canPublicNote: boolean;
3838
canEditNote: boolean;
39+
scheduleNoteMax: number;
3940
mentionLimit: number;
4041
canInvite: boolean;
4142
inviteLimit: number;
@@ -70,6 +71,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
7071
ltlAvailable: true,
7172
canPublicNote: true,
7273
canEditNote: true,
74+
scheduleNoteMax: 5,
7375
mentionLimit: 20,
7476
canInvite: false,
7577
inviteLimit: 0,
@@ -377,6 +379,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
377379
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
378380
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
379381
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
382+
scheduleNoteMax: calc('scheduleNoteMax', vs => Math.max(...vs)),
380383
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
381384
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
382385
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),

packages/backend/src/di-symbols.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const DI = {
1919
//#region Repositories
2020
usersRepository: Symbol('usersRepository'),
2121
notesRepository: Symbol('notesRepository'),
22+
noteScheduleRepository: Symbol('noteScheduleRepository'),
2223
abuseReportResolversRepository: Symbol('abuseReportResolversRepository'),
2324
announcementsRepository: Symbol('announcementsRepository'),
2425
announcementReadsRepository: Symbol('announcementReadsRepository'),
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* SPDX-FileCopyrightText: syuilo and other misskey contributors
3+
* SPDX-License-Identifier: AGPL-3.0-only
4+
*/
5+
6+
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
7+
import { noteVisibilities } from '@/types.js';
8+
import { MiNote } from '@/models/Note.js';
9+
import { id } from './util/id.js';
10+
import { MiUser } from './User.js';
11+
import { MiChannel } from './Channel.js';
12+
import { EventSchema } from './Event.js';
13+
import type { MiDriveFile } from './DriveFile.js';
14+
15+
type MinimumUser = {
16+
id: MiUser['id'];
17+
host: MiUser['host'];
18+
username: MiUser['username'];
19+
uri: MiUser['uri'];
20+
};
21+
22+
export type MiScheduleNoteType={
23+
/** Date.toISOString() */
24+
createdAt: string;
25+
visibility: 'public' | 'home' | 'followers' | 'specified';
26+
visibleUsers: MinimumUser[];
27+
channel?: MiChannel['id'];
28+
poll: {
29+
multiple: boolean;
30+
choices: string[];
31+
/** Date.toISOString() */
32+
expiresAt: string | null
33+
} | undefined;
34+
renote?: MiNote['id'];
35+
localOnly: boolean;
36+
cw?: string|null;
37+
reactionAcceptance: 'likeOnly'|'likeOnlyForRemote'| 'nonSensitiveOnly'| 'nonSensitiveOnlyForLocalLikeOnlyForRemote'| null;
38+
files: MiDriveFile['id'][];
39+
text?: string|null;
40+
reply?: MiNote['id'];
41+
event?: {
42+
/** Date.toISOString() */
43+
start: string;
44+
/** Date.toISOString() */
45+
end: string | null;
46+
title: string;
47+
metadata: EventSchema;
48+
} | null;
49+
disableRightClick:boolean,
50+
apMentions?: MinimumUser[] | null;
51+
apHashtags?: string[] | null;
52+
apEmojis?: string[] | null;
53+
}
54+
55+
@Entity('note_schedule')
56+
export class MiNoteSchedule {
57+
@PrimaryColumn(id())
58+
public id: string;
59+
60+
@Column('jsonb')
61+
public note:MiScheduleNoteType;
62+
63+
@Index()
64+
@Column('varchar', {
65+
length: 260,
66+
})
67+
public userId: MiUser['id'];
68+
69+
@Column('timestamp with time zone')
70+
public expiresAt: Date;
71+
}

packages/backend/src/models/RepositoryModule.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import {
8686
MiUserSecurityKey,
8787
MiWebhook,
8888
MiOfficialTag,
89+
MiNoteSchedule,
8990
} from './_.js';
9091
import type { Provider } from '@nestjs/common';
9192
import type { DataSource } from 'typeorm';
@@ -102,6 +103,12 @@ const $notesRepository: Provider = {
102103
inject: [DI.db],
103104
};
104105

106+
const $noteScheduleRepository: Provider = {
107+
provide: DI.noteScheduleRepository,
108+
useFactory: (db: DataSource) => db.getRepository(MiNoteSchedule).extend(miRepository as MiRepository<MiNoteSchedule>),
109+
inject: [DI.db],
110+
};
111+
105112
const $announcementsRepository: Provider = {
106113
provide: DI.announcementsRepository,
107114
useFactory: (db: DataSource) => db.getRepository(MiAnnouncement).extend(miRepository as MiRepository<MiAnnouncement>),
@@ -563,6 +570,7 @@ const $officialTagRepository: Provider = {
563570
providers: [
564571
$usersRepository,
565572
$notesRepository,
573+
$noteScheduleRepository,
566574
$announcementsRepository,
567575
$announcementReadsRepository,
568576
$appsRepository,
@@ -643,6 +651,7 @@ const $officialTagRepository: Provider = {
643651
exports: [
644652
$usersRepository,
645653
$notesRepository,
654+
$noteScheduleRepository,
646655
$announcementsRepository,
647656
$announcementReadsRepository,
648657
$appsRepository,

0 commit comments

Comments
 (0)