parent
82f9d5501b
commit
e68278f93e
12 changed files with 116 additions and 6 deletions
|
@ -20,6 +20,7 @@ You should also include the user name that made the change.
|
|||
|
||||
### Improvements
|
||||
- インスタンスデフォルトテーマを設定できるように @syuilo
|
||||
- ミュートに期限を設定できるように @syuilo
|
||||
- プロフィールの追加情報を最大16まで保存できるように @syuilo
|
||||
- 連合チャートにPub&Subを追加 @syuilo
|
||||
- デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo
|
||||
|
|
|
@ -834,6 +834,12 @@ searchByGoogle: "ググる"
|
|||
instanceDefaultLightTheme: "インスタンスデフォルトのライトテーマ"
|
||||
instanceDefaultDarkTheme: "インスタンスデフォルトのダークテーマ"
|
||||
instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。"
|
||||
mutePeriod: "ミュートする期限"
|
||||
indefinitely: "無期限"
|
||||
tenMinutes: "10分"
|
||||
oneHour: "1時間"
|
||||
oneDay: "1日"
|
||||
oneWeek: "1週間"
|
||||
|
||||
_emailUnavailable:
|
||||
used: "既に使用されています"
|
||||
|
|
13
packages/backend/migration/1646387162108-mute-expires-at.js
Normal file
13
packages/backend/migration/1646387162108-mute-expires-at.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export class muteExpiresAt1646387162108 {
|
||||
name = 'muteExpiresAt1646387162108'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "muting" ADD "expiresAt" TIMESTAMP WITH TIME ZONE`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_c1fd1c3dfb0627aa36c253fd14" ON "muting" ("expiresAt") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_c1fd1c3dfb0627aa36c253fd14"`);
|
||||
await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "expiresAt"`);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,13 @@ export class Muting {
|
|||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
public expiresAt: Date | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
|
|
|
@ -16,6 +16,7 @@ export class MutingRepository extends Repository<Muting> {
|
|||
return await awaitAll({
|
||||
id: muting.id,
|
||||
createdAt: muting.createdAt.toISOString(),
|
||||
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
||||
muteeId: muting.muteeId,
|
||||
mutee: Users.pack(muting.muteeId, me, {
|
||||
detail: true,
|
||||
|
|
|
@ -12,6 +12,11 @@ export const packedMutingSchema = {
|
|||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
expiresAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
muteeId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
@ -273,6 +273,11 @@ export default function() {
|
|||
repeat: { cron: '0 0 * * *' },
|
||||
});
|
||||
|
||||
systemQueue.add('checkExpiredMutings', {
|
||||
}, {
|
||||
repeat: { cron: '*/5 * * * *' },
|
||||
});
|
||||
|
||||
processSystemQueue(systemQueue);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { addFile } from '@/services/drive/add-file.js';
|
|||
import { format as dateFormat } from 'date-fns';
|
||||
import { getFullApAccount } from '@/misc/convert-host.js';
|
||||
import { Users, Mutings } from '@/models/index.js';
|
||||
import { MoreThan } from 'typeorm';
|
||||
import { IsNull, MoreThan } from 'typeorm';
|
||||
import { DbUserJobData } from '@/queue/types.js';
|
||||
|
||||
const logger = queueLogger.createSubLogger('export-mute');
|
||||
|
@ -40,6 +40,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
|
|||
const mutes = await Mutings.find({
|
||||
where: {
|
||||
muterId: user.id,
|
||||
expiresAt: IsNull(),
|
||||
...(cursor ? { id: MoreThan(cursor) } : {}),
|
||||
},
|
||||
take: 100,
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import Bull from 'bull';
|
||||
import { In } from 'typeorm';
|
||||
import { Mutings } from '@/models/index.js';
|
||||
import { queueLogger } from '../../logger.js';
|
||||
import { publishUserEvent } from '@/services/stream.js';
|
||||
|
||||
const logger = queueLogger.createSubLogger('check-expired-mutings');
|
||||
|
||||
export async function checkExpiredMutings(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> {
|
||||
logger.info(`Checking expired mutings...`);
|
||||
|
||||
const expired = await Mutings.createQueryBuilder('muting')
|
||||
.where('muting.expiresAt IS NOT NULL')
|
||||
.andWhere('muting.expiresAt < :now', { now: new Date() })
|
||||
.innerJoinAndSelect('muting.mutee', 'mutee')
|
||||
.getMany();
|
||||
|
||||
if (expired.length > 0) {
|
||||
await Mutings.delete({
|
||||
id: In(expired.map(m => m.id)),
|
||||
});
|
||||
|
||||
for (const m of expired) {
|
||||
publishUserEvent(m.muterId, 'unmute', m.mutee!);
|
||||
}
|
||||
}
|
||||
|
||||
logger.succ(`All expired mutings checked.`);
|
||||
done();
|
||||
}
|
|
@ -2,11 +2,13 @@ import Bull from 'bull';
|
|||
import { tickCharts } from './tick-charts.js';
|
||||
import { resyncCharts } from './resync-charts.js';
|
||||
import { cleanCharts } from './clean-charts.js';
|
||||
import { checkExpiredMutings } from './check-expired-mutings.js';
|
||||
|
||||
const jobs = {
|
||||
tickCharts,
|
||||
resyncCharts,
|
||||
cleanCharts,
|
||||
checkExpiredMutings,
|
||||
} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>;
|
||||
|
||||
export default function(dbQueue: Bull.Queue<Record<string, unknown>>) {
|
||||
|
|
|
@ -38,6 +38,7 @@ export const paramDef = {
|
|||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
expiresAt: { type: 'integer', nullable: true },
|
||||
},
|
||||
required: ['userId'],
|
||||
} as const;
|
||||
|
@ -67,10 +68,15 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
throw new ApiError(meta.errors.alreadyMuting);
|
||||
}
|
||||
|
||||
if (ps.expiresAt && ps.expiresAt <= Date.now()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create mute
|
||||
await Mutings.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
|
||||
muterId: muter.id,
|
||||
muteeId: mutee.id,
|
||||
} as Muting);
|
||||
|
|
|
@ -56,11 +56,44 @@ export function getUserMenu(user) {
|
|||
}
|
||||
|
||||
async function toggleMute() {
|
||||
os.apiWithDialog(user.isMuted ? 'mute/delete' : 'mute/create', {
|
||||
userId: user.id
|
||||
}).then(() => {
|
||||
user.isMuted = !user.isMuted;
|
||||
});
|
||||
if (user.isMuted) {
|
||||
os.apiWithDialog('mute/delete', {
|
||||
userId: user.id,
|
||||
}).then(() => {
|
||||
user.isMuted = false;
|
||||
});
|
||||
} else {
|
||||
const { canceled, result: period } = await os.select({
|
||||
title: i18n.ts.mutePeriod,
|
||||
items: [{
|
||||
value: 'indefinitely', text: i18n.ts.indefinitely,
|
||||
}, {
|
||||
value: 'tenMinutes', text: i18n.ts.tenMinutes,
|
||||
}, {
|
||||
value: 'oneHour', text: i18n.ts.oneHour,
|
||||
}, {
|
||||
value: 'oneDay', text: i18n.ts.oneDay,
|
||||
}, {
|
||||
value: 'oneWeek', text: i18n.ts.oneWeek,
|
||||
}],
|
||||
default: 'indefinitely',
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const expiresAt = period === 'indefinitely' ? null
|
||||
: period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10)
|
||||
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
|
||||
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
|
||||
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
|
||||
: null;
|
||||
|
||||
os.apiWithDialog('mute/create', {
|
||||
userId: user.id,
|
||||
expiresAt,
|
||||
}).then(() => {
|
||||
user.isMuted = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleBlock() {
|
||||
|
|
Loading…
Reference in a new issue