Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

予約投稿 #483

Merged
merged 22 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1892003
予約投稿
mattyatea Nov 6, 2023
3bc7787
予約投稿をdbに保存する
kozakura913 Sep 28, 2024
00283de
予約投稿の一覧表示、削除をできるようにした
kozakura913 Sep 28, 2024
743dbc0
変更忘れ
mattyatea Nov 9, 2023
a7c9354
型エラー修正とちょっとした修正
kozakura913 Sep 28, 2024
da7dbb9
Feat: 予約投稿の削除して編集を実装
kozakura913 Sep 28, 2024
cd42e21
予約投稿のエンドポイントを移動
kozakura913 Sep 29, 2024
38e9c51
予約投稿APIの型を改善
kozakura913 Sep 29, 2024
4919bf0
予約投稿の権限
kozakura913 Sep 29, 2024
0ec41c2
idの状態でキューに格納しておく
kozakura913 Sep 29, 2024
974344f
write:notes-scheduleを定義
kozakura913 Oct 15, 2024
df70567
throw new ApiError
kozakura913 Oct 15, 2024
b284ee2
予約投稿の最大数を指定
kozakura913 Oct 15, 2024
0c60e47
予約投稿の詳細を開かない
kozakura913 Oct 15, 2024
df80f9e
予約投稿を削除する時に確認ダイアログを出す
kozakura913 Oct 15, 2024
8fb9cd9
エラーメッセージ修正
kozakura913 Oct 15, 2024
710c289
予約投稿を正しく削除できない不具合の修正
kozakura913 Oct 16, 2024
4f9255d
このフォークではローカルのみ投稿を認めない
kozakura913 Oct 16, 2024
34000ae
このフォークではチャンネルの存在を認めない
kozakura913 Oct 16, 2024
64c88c0
チャンネル削除
kozakura913 Oct 16, 2024
8409fd0
予約投稿の投票期限修正
kozakura913 Oct 16, 2024
b6a6379
予約投稿の未使用オプション削除
kozakura913 Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2946,10 +2946,18 @@ export interface Locale extends ILocale {
* アンケート
*/
"poll": string;
/**
* 予約投稿
*/
"schedulePost": string;
/**
* 内容を隠す
*/
"useCw": string;
/**
* 予約投稿一覧
*/
"schedulePostList": string;
/**
* プレイヤーを開く
*/
Expand Down Expand Up @@ -7668,6 +7676,10 @@ export interface Locale extends ILocale {
* ノートの編集
*/
"canEditNote": string;
/**
* 予約投稿の最大数
*/
"scheduleNoteMax": string;
/**
* ノート内の最大メンション数
*/
Expand Down Expand Up @@ -9323,6 +9335,14 @@ export interface Locale extends ILocale {
* ノートを作成・削除する
*/
"write:notes": string;
/**
* 予約投稿を見る
*/
"read:notes-schedule": string;
/**
* 予約投稿を作成・削除する
*/
"write:notes-schedule": string;
/**
* 通知を見る
*/
Expand Down
5 changes: 5 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,9 @@ invisibleNote: "非公開の投稿"
enableInfiniteScroll: "自動でもっと見る"
visibility: "公開範囲"
poll: "アンケート"
schedulePost: "予約投稿"
useCw: "内容を隠す"
schedulePostList: "予約投稿一覧"
enablePlayer: "プレイヤーを開く"
disablePlayer: "プレイヤーを閉じる"
expandTweet: "ポストを展開する"
Expand Down Expand Up @@ -1991,6 +1993,7 @@ _role:
ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可"
canEditNote: "ノートの編集"
scheduleNoteMax: "予約投稿の最大数"
mentionMax: "ノート内の最大メンション数"
canInvite: "サーバー招待コードの発行"
inviteLimit: "招待コードの作成可能数"
Expand Down Expand Up @@ -2453,6 +2456,8 @@ _permissions:
"read:mutes": "ミュートを見る"
"write:mutes": "ミュートを操作する"
"write:notes": "ノートを作成・削除する"
"read:notes-schedule": "予約投稿を見る"
"write:notes-schedule": "予約投稿を作成・削除する"
"read:notifications": "通知を見る"
"write:notifications": "通知を操作する"
"read:reactions": "リアクションを見る"
Expand Down
17 changes: 17 additions & 0 deletions packages/backend/migration/1699437894737-schedulenote.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class Schedulenote1699437894737 {
name = 'Schedulenote1699437894737'

async up(queryRunner) {
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"))`);
await queryRunner.query(`CREATE INDEX "IDX_e798958c40009bf0cdef4f28b5" ON "note_schedule" ("userId") `);
}

async down(queryRunner) {
await queryRunner.query(`DROP TABLE "note_schedule"`);
}
}
27 changes: 7 additions & 20 deletions packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
import { isReply } from '@/misc/is-reply.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { MiNoteSchedule } from '@/models/_.js';

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

Expand Down Expand Up @@ -139,6 +140,7 @@ type Option = {
files?: MiDriveFile[] | null;
poll?: IPoll | null;
event?: IEvent | null;
schedule?: MiNoteSchedule | null;
localOnly?: boolean | null;
reactionAcceptance?: MiNote['reactionAcceptance'];
disableRightClick?: boolean | null;
Expand Down Expand Up @@ -237,33 +239,17 @@ export class NoteCreateService implements OnApplicationShutdown {
isBot: MiUser['isBot'];
isCat: MiUser['isCat'];
}, data: Option, silent = false): Promise<MiNote> {
// チャンネル外にリプライしたら対象のスコープに合わせる
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
if (data.reply.channelId) {
data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId });
} else {
data.channel = null;
}
}

// チャンネル内にリプライしたら対象のスコープに合わせる
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
if (data.reply && (data.channel == null) && data.reply.channelId) {
data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId });
}
//このフォークではチャンネルの存在を認めない
data.channel = undefined;

if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = 'public';
if (data.localOnly == null) data.localOnly = false;
if (data.disableRightClick == null) data.disableRightClick = false;
if (data.channel != null) data.visibility = 'public';
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;

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

if (data.visibility === 'public' && data.channel == null) {
if (data.visibility === 'public') {
const sensitiveWords = meta.sensitiveWords;
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home';
Expand Down Expand Up @@ -400,7 +386,8 @@ export class NoteCreateService implements OnApplicationShutdown {
if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) {
throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions');
}

//このフォークではローカルのみ投稿を認めない
data.localOnly = false;
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);

setImmediate('post created', { signal: this.#shutdownController.signal }).then(
Expand Down
12 changes: 12 additions & 0 deletions packages/backend/src/core/QueueModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import {
RelationshipJobData,
UserWebhookDeliverJobData,
SystemWebhookDeliverJobData,
ScheduleNotePostJobData,
} from '../queue/types.js';
import type { Provider } from '@nestjs/common';

export type SystemQueue = Bull.Queue<Record<string, unknown>>;
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
export type ScheduleNotePostQueue = Bull.Queue<ScheduleNotePostJobData>;
export type DeliverQueue = Bull.Queue<DeliverJobData>;
export type InboxQueue = Bull.Queue<InboxJobData>;
export type DbQueue = Bull.Queue;
Expand All @@ -42,6 +44,12 @@ const $endedPollNotification: Provider = {
inject: [DI.config, DI.redisForJobQueue],
};

const $scheduleNotePost: Provider = {
provide: 'queue:scheduleNotePost',
useFactory: (config: Config, redisForJobQueue: Redis.Redis) => new Bull.Queue(QUEUE.SCHEDULE_NOTE_POST, baseQueueOptions(config, QUEUE.SCHEDULE_NOTE_POST, redisForJobQueue)),
inject: [DI.config, DI.redisForJobQueue],
};

const $deliver: Provider = {
provide: 'queue:deliver',
useFactory: (config: Config, redisForJobQueue: Redis.Redis) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER, redisForJobQueue)),
Expand Down Expand Up @@ -90,6 +98,7 @@ const $systemWebhookDeliver: Provider = {
providers: [
$system,
$endedPollNotification,
$scheduleNotePost,
$deliver,
$inbox,
$db,
Expand All @@ -101,6 +110,7 @@ const $systemWebhookDeliver: Provider = {
exports: [
$system,
$endedPollNotification,
$scheduleNotePost,
$deliver,
$inbox,
$db,
Expand All @@ -114,6 +124,7 @@ export class QueueModule implements OnApplicationShutdown {
constructor(
@Inject('queue:system') public systemQueue: SystemQueue,
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
@Inject('queue:scheduleNotePost') public scheduleNotePostQueue: ScheduleNotePostQueue,
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
@Inject('queue:inbox') public inboxQueue: InboxQueue,
@Inject('queue:db') public dbQueue: DbQueue,
Expand All @@ -130,6 +141,7 @@ export class QueueModule implements OnApplicationShutdown {
await Promise.all([
this.systemQueue.close(),
this.endedPollNotificationQueue.close(),
this.scheduleNotePostQueue.close(),
this.deliverQueue.close(),
this.inboxQueue.close(),
this.dbQueue.close(),
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/core/QueueService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
SystemQueue,
UserWebhookDeliverQueue,
SystemWebhookDeliverQueue,
ScheduleNotePostQueue,
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
Expand All @@ -45,6 +46,7 @@ export class QueueService {

@Inject('queue:system') public systemQueue: SystemQueue,
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
@Inject('queue:scheduleNotePost') public ScheduleNotePostQueue: ScheduleNotePostQueue,
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
@Inject('queue:inbox') public inboxQueue: InboxQueue,
@Inject('queue:db') public dbQueue: DbQueue,
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/core/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type RolePolicies = {
ltlAvailable: boolean;
canPublicNote: boolean;
canEditNote: boolean;
scheduleNoteMax: number;
mentionLimit: number;
canInvite: boolean;
inviteLimit: number;
Expand Down Expand Up @@ -70,6 +71,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
ltlAvailable: true,
canPublicNote: true,
canEditNote: true,
scheduleNoteMax: 5,
mentionLimit: 20,
canInvite: false,
inviteLimit: 0,
Expand Down Expand Up @@ -377,6 +379,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
scheduleNoteMax: calc('scheduleNoteMax', vs => Math.max(...vs)),
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/di-symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const DI = {
//#region Repositories
usersRepository: Symbol('usersRepository'),
notesRepository: Symbol('notesRepository'),
noteScheduleRepository: Symbol('noteScheduleRepository'),
abuseReportResolversRepository: Symbol('abuseReportResolversRepository'),
announcementsRepository: Symbol('announcementsRepository'),
announcementReadsRepository: Symbol('announcementReadsRepository'),
Expand Down
71 changes: 71 additions & 0 deletions packages/backend/src/models/NoteSchedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
import { noteVisibilities } from '@/types.js';
import { MiNote } from '@/models/Note.js';
import { id } from './util/id.js';
import { MiUser } from './User.js';
import { MiChannel } from './Channel.js';
import { EventSchema } from './Event.js';
import type { MiDriveFile } from './DriveFile.js';

type MinimumUser = {
id: MiUser['id'];
host: MiUser['host'];
username: MiUser['username'];
uri: MiUser['uri'];
};

export type MiScheduleNoteType={
/** Date.toISOString() */
createdAt: string;
visibility: 'public' | 'home' | 'followers' | 'specified';
visibleUsers: MinimumUser[];
channel?: MiChannel['id'];
poll: {
multiple: boolean;
choices: string[];
/** Date.toISOString() */
expiresAt: string | null
} | undefined;
renote?: MiNote['id'];
localOnly: boolean;
cw?: string|null;
reactionAcceptance: 'likeOnly'|'likeOnlyForRemote'| 'nonSensitiveOnly'| 'nonSensitiveOnlyForLocalLikeOnlyForRemote'| null;
files: MiDriveFile['id'][];
text?: string|null;
reply?: MiNote['id'];
event?: {
/** Date.toISOString() */
start: string;
/** Date.toISOString() */
end: string | null;
title: string;
metadata: EventSchema;
} | null;
disableRightClick:boolean,
apMentions?: MinimumUser[] | null;
apHashtags?: string[] | null;
apEmojis?: string[] | null;
}

@Entity('note_schedule')
export class MiNoteSchedule {
@PrimaryColumn(id())
public id: string;

@Column('jsonb')
public note:MiScheduleNoteType;

@Index()
@Column('varchar', {
length: 260,
})
public userId: MiUser['id'];

@Column('timestamp with time zone')
public expiresAt: Date;
}
9 changes: 9 additions & 0 deletions packages/backend/src/models/RepositoryModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import {
MiUserSecurityKey,
MiWebhook,
MiOfficialTag,
MiNoteSchedule,
} from './_.js';
import type { Provider } from '@nestjs/common';
import type { DataSource } from 'typeorm';
Expand All @@ -102,6 +103,12 @@ const $notesRepository: Provider = {
inject: [DI.db],
};

const $noteScheduleRepository: Provider = {
provide: DI.noteScheduleRepository,
useFactory: (db: DataSource) => db.getRepository(MiNoteSchedule).extend(miRepository as MiRepository<MiNoteSchedule>),
inject: [DI.db],
};

const $announcementsRepository: Provider = {
provide: DI.announcementsRepository,
useFactory: (db: DataSource) => db.getRepository(MiAnnouncement).extend(miRepository as MiRepository<MiAnnouncement>),
Expand Down Expand Up @@ -563,6 +570,7 @@ const $officialTagRepository: Provider = {
providers: [
$usersRepository,
$notesRepository,
$noteScheduleRepository,
$announcementsRepository,
$announcementReadsRepository,
$appsRepository,
Expand Down Expand Up @@ -643,6 +651,7 @@ const $officialTagRepository: Provider = {
exports: [
$usersRepository,
$notesRepository,
$noteScheduleRepository,
$announcementsRepository,
$announcementReadsRepository,
$appsRepository,
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/models/_.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiFlashLikeRemote } from '@/models/FlashLikeRemote.js';
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
import { MiNoteSchedule } from './NoteSchedule.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
Expand Down Expand Up @@ -173,6 +174,7 @@ export {
MiNoteThreadMuting,
MiNoteUnread,
MiOfficialTag,
MiNoteSchedule,
MiPage,
MiPageLike,
MiPasswordResetRequest,
Expand Down Expand Up @@ -253,6 +255,7 @@ export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
export type OfficialTagRepository = Repository<MiOfficialTag> & MiRepository<MiOfficialTag>;
export type NoteScheduleRepository = Repository<MiNoteSchedule>;
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
export type PageLikesRepository = Repository<MiPageLike> & MiRepository<MiPageLike>;
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest> & MiRepository<MiPasswordResetRequest>;
Expand Down
Loading
Loading