parent
82f9d5501b
commit
e68278f93e
|
@ -20,6 +20,7 @@ You should also include the user name that made the change.
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
- インスタンスデフォルトテーマを設定できるように @syuilo
|
- インスタンスデフォルトテーマを設定できるように @syuilo
|
||||||
|
- ミュートに期限を設定できるように @syuilo
|
||||||
- プロフィールの追加情報を最大16まで保存できるように @syuilo
|
- プロフィールの追加情報を最大16まで保存できるように @syuilo
|
||||||
- 連合チャートにPub&Subを追加 @syuilo
|
- 連合チャートにPub&Subを追加 @syuilo
|
||||||
- デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo
|
- デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo
|
||||||
|
|
|
@ -834,6 +834,12 @@ searchByGoogle: "ググる"
|
||||||
instanceDefaultLightTheme: "インスタンスデフォルトのライトテーマ"
|
instanceDefaultLightTheme: "インスタンスデフォルトのライトテーマ"
|
||||||
instanceDefaultDarkTheme: "インスタンスデフォルトのダークテーマ"
|
instanceDefaultDarkTheme: "インスタンスデフォルトのダークテーマ"
|
||||||
instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。"
|
instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。"
|
||||||
|
mutePeriod: "ミュートする期限"
|
||||||
|
indefinitely: "無期限"
|
||||||
|
tenMinutes: "10分"
|
||||||
|
oneHour: "1時間"
|
||||||
|
oneDay: "1日"
|
||||||
|
oneWeek: "1週間"
|
||||||
|
|
||||||
_emailUnavailable:
|
_emailUnavailable:
|
||||||
used: "既に使用されています"
|
used: "既に使用されています"
|
||||||
|
|
|
@ -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;
|
public createdAt: Date;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('timestamp with time zone', {
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
public expiresAt: Date | null;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
|
|
|
@ -16,6 +16,7 @@ export class MutingRepository extends Repository<Muting> {
|
||||||
return await awaitAll({
|
return await awaitAll({
|
||||||
id: muting.id,
|
id: muting.id,
|
||||||
createdAt: muting.createdAt.toISOString(),
|
createdAt: muting.createdAt.toISOString(),
|
||||||
|
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
||||||
muteeId: muting.muteeId,
|
muteeId: muting.muteeId,
|
||||||
mutee: Users.pack(muting.muteeId, me, {
|
mutee: Users.pack(muting.muteeId, me, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
|
|
@ -12,6 +12,11 @@ export const packedMutingSchema = {
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
format: 'date-time',
|
format: 'date-time',
|
||||||
},
|
},
|
||||||
|
expiresAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
muteeId: {
|
muteeId: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -273,6 +273,11 @@ export default function() {
|
||||||
repeat: { cron: '0 0 * * *' },
|
repeat: { cron: '0 0 * * *' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
systemQueue.add('checkExpiredMutings', {
|
||||||
|
}, {
|
||||||
|
repeat: { cron: '*/5 * * * *' },
|
||||||
|
});
|
||||||
|
|
||||||
processSystemQueue(systemQueue);
|
processSystemQueue(systemQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { addFile } from '@/services/drive/add-file.js';
|
||||||
import { format as dateFormat } from 'date-fns';
|
import { format as dateFormat } from 'date-fns';
|
||||||
import { getFullApAccount } from '@/misc/convert-host.js';
|
import { getFullApAccount } from '@/misc/convert-host.js';
|
||||||
import { Users, Mutings } from '@/models/index.js';
|
import { Users, Mutings } from '@/models/index.js';
|
||||||
import { MoreThan } from 'typeorm';
|
import { IsNull, MoreThan } from 'typeorm';
|
||||||
import { DbUserJobData } from '@/queue/types.js';
|
import { DbUserJobData } from '@/queue/types.js';
|
||||||
|
|
||||||
const logger = queueLogger.createSubLogger('export-mute');
|
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({
|
const mutes = await Mutings.find({
|
||||||
where: {
|
where: {
|
||||||
muterId: user.id,
|
muterId: user.id,
|
||||||
|
expiresAt: IsNull(),
|
||||||
...(cursor ? { id: MoreThan(cursor) } : {}),
|
...(cursor ? { id: MoreThan(cursor) } : {}),
|
||||||
},
|
},
|
||||||
take: 100,
|
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 { tickCharts } from './tick-charts.js';
|
||||||
import { resyncCharts } from './resync-charts.js';
|
import { resyncCharts } from './resync-charts.js';
|
||||||
import { cleanCharts } from './clean-charts.js';
|
import { cleanCharts } from './clean-charts.js';
|
||||||
|
import { checkExpiredMutings } from './check-expired-mutings.js';
|
||||||
|
|
||||||
const jobs = {
|
const jobs = {
|
||||||
tickCharts,
|
tickCharts,
|
||||||
resyncCharts,
|
resyncCharts,
|
||||||
cleanCharts,
|
cleanCharts,
|
||||||
|
checkExpiredMutings,
|
||||||
} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>;
|
} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>;
|
||||||
|
|
||||||
export default function(dbQueue: Bull.Queue<Record<string, unknown>>) {
|
export default function(dbQueue: Bull.Queue<Record<string, unknown>>) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
userId: { type: 'string', format: 'misskey:id' },
|
userId: { type: 'string', format: 'misskey:id' },
|
||||||
|
expiresAt: { type: 'integer', nullable: true },
|
||||||
},
|
},
|
||||||
required: ['userId'],
|
required: ['userId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -67,10 +68,15 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
throw new ApiError(meta.errors.alreadyMuting);
|
throw new ApiError(meta.errors.alreadyMuting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.expiresAt && ps.expiresAt <= Date.now()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create mute
|
// Create mute
|
||||||
await Mutings.insert({
|
await Mutings.insert({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
|
||||||
muterId: muter.id,
|
muterId: muter.id,
|
||||||
muteeId: mutee.id,
|
muteeId: mutee.id,
|
||||||
} as Muting);
|
} as Muting);
|
||||||
|
|
|
@ -56,11 +56,44 @@ export function getUserMenu(user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleMute() {
|
async function toggleMute() {
|
||||||
os.apiWithDialog(user.isMuted ? 'mute/delete' : 'mute/create', {
|
if (user.isMuted) {
|
||||||
userId: user.id
|
os.apiWithDialog('mute/delete', {
|
||||||
}).then(() => {
|
userId: user.id,
|
||||||
user.isMuted = !user.isMuted;
|
}).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() {
|
async function toggleBlock() {
|
||||||
|
|
Loading…
Reference in New Issue