Merge branch 'develop' of https://github.com/misskey-dev/misskey into notification-hide-muting-user
This commit is contained in:
commit
e59be544e9
14 changed files with 326 additions and 145 deletions
|
|
@ -79,7 +79,7 @@
|
|||
"@fastify/multipart": "8.1.0",
|
||||
"@fastify/static": "6.12.0",
|
||||
"@fastify/view": "8.2.0",
|
||||
"@misskey-dev/sharp-read-bmp": "^1.1.1",
|
||||
"@misskey-dev/sharp-read-bmp": "^1.2.0",
|
||||
"@misskey-dev/summaly": "^5.0.3",
|
||||
"@nestjs/common": "10.2.10",
|
||||
"@nestjs/core": "10.2.10",
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
"rxjs": "7.8.1",
|
||||
"sanitize-html": "2.11.0",
|
||||
"secure-json-parse": "2.7.0",
|
||||
"sharp": "0.32.6",
|
||||
"sharp": "0.33.2",
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import isSvg from 'is-svg';
|
|||
import probeImageSize from 'probe-image-size';
|
||||
import { type predictionType } from 'nsfwjs';
|
||||
import sharp from 'sharp';
|
||||
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
|
||||
import { encode } from 'blurhash';
|
||||
import { createTempDir } from '@/misc/create-temp.js';
|
||||
import { AiService } from '@/core/AiService.js';
|
||||
|
|
@ -122,7 +123,7 @@ export class FileInfoService {
|
|||
'image/avif',
|
||||
'image/svg+xml',
|
||||
].includes(type.mime)) {
|
||||
blurhash = await this.getBlurhash(path).catch(e => {
|
||||
blurhash = await this.getBlurhash(path, type.mime).catch(e => {
|
||||
warnings.push(`getBlurhash failed: ${e}`);
|
||||
return undefined;
|
||||
});
|
||||
|
|
@ -407,9 +408,9 @@ export class FileInfoService {
|
|||
* Calculate average color of image
|
||||
*/
|
||||
@bindThis
|
||||
private getBlurhash(path: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
sharp(path)
|
||||
private getBlurhash(path: string, type: string): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
(await sharpBmp(path, type))
|
||||
.raw()
|
||||
.ensureAlpha()
|
||||
.resize(64, 64, { fit: 'inside' })
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
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';
|
||||
|
||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||
|
||||
|
|
@ -151,8 +152,6 @@ type Option = {
|
|||
export class NoteCreateService implements OnApplicationShutdown {
|
||||
#shutdownController = new AbortController();
|
||||
|
||||
public static ContainsProhibitedWordsError = class extends Error {};
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
|
@ -264,7 +263,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
|
||||
throw new NoteCreateService.ContainsProhibitedWordsError();
|
||||
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
||||
}
|
||||
|
||||
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
|
||||
|
|
|
|||
|
|
@ -322,35 +322,36 @@ export class ReactionService {
|
|||
//#endregion
|
||||
}
|
||||
|
||||
/**
|
||||
* 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、
|
||||
* データベース上には存在する「0個のリアクションがついている」という情報を削除する。
|
||||
*/
|
||||
@bindThis
|
||||
public convertLegacyReactions(reactions: Record<string, number>) {
|
||||
const _reactions = {} as Record<string, number>;
|
||||
public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
|
||||
return Object.entries(reactions)
|
||||
.filter(([, count]) => {
|
||||
// `ReactionService.prototype.delete`ではリアクション削除時に、
|
||||
// `MiNote['reactions']`のエントリの値をデクリメントしているが、
|
||||
// デクリメントしているだけなのでエントリ自体は0を値として持つ形で残り続ける。
|
||||
// そのため、この処理がなければ、「0個のリアクションがついている」ということになってしまう。
|
||||
return count > 0;
|
||||
})
|
||||
.map(([reaction, count]) => {
|
||||
// unchecked indexed access
|
||||
const convertedReaction = legacies[reaction] as string | undefined;
|
||||
|
||||
for (const reaction of Object.keys(reactions)) {
|
||||
if (reactions[reaction] <= 0) continue;
|
||||
const key = this.decodeReaction(convertedReaction ?? reaction).reaction;
|
||||
|
||||
if (Object.keys(legacies).includes(reaction)) {
|
||||
if (_reactions[legacies[reaction]]) {
|
||||
_reactions[legacies[reaction]] += reactions[reaction];
|
||||
} else {
|
||||
_reactions[legacies[reaction]] = reactions[reaction];
|
||||
}
|
||||
} else {
|
||||
if (_reactions[reaction]) {
|
||||
_reactions[reaction] += reactions[reaction];
|
||||
} else {
|
||||
_reactions[reaction] = reactions[reaction];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [key, count] as const;
|
||||
})
|
||||
.reduce<MiNote['reactions']>((acc, [key, count]) => {
|
||||
// unchecked indexed access
|
||||
const prevCount = acc[key] as number | undefined;
|
||||
|
||||
const _reactions2 = {} as Record<string, number>;
|
||||
acc[key] = (prevCount ?? 0) + count;
|
||||
|
||||
for (const reaction of Object.keys(_reactions)) {
|
||||
_reactions2[this.decodeReaction(reaction).reaction] = _reactions[reaction];
|
||||
}
|
||||
|
||||
return _reactions2;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
|||
import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js';
|
||||
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type { InboxJobData } from '../types.js';
|
||||
|
||||
|
|
@ -180,7 +181,14 @@ export class InboxProcessorService {
|
|||
});
|
||||
|
||||
// アクティビティを処理
|
||||
await this.apInboxService.performActivity(authUser.user, activity);
|
||||
try {
|
||||
await this.apInboxService.performActivity(authUser.user, activity);
|
||||
} catch (e) {
|
||||
if (e instanceof IdentifiableError) {
|
||||
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') return 'blocked notes with prohibited words';
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return 'ok';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -376,8 +377,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
};
|
||||
} catch (e) {
|
||||
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
|
||||
if (e instanceof NoteCreateService.ContainsProhibitedWordsError) {
|
||||
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
if (e instanceof IdentifiableError) {
|
||||
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
}
|
||||
|
||||
throw e;
|
||||
|
|
|
|||
|
|
@ -90,4 +90,45 @@ describe('ReactionService', () => {
|
|||
assert.strictEqual(await reactionService.normalize('unknown'), '❤');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertLegacyReactions', () => {
|
||||
test('空の入力に対しては何もしない', () => {
|
||||
const input = {};
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
|
||||
});
|
||||
|
||||
test('Unicode絵文字リアクションを変換してしまわない', () => {
|
||||
const input = { '👍': 1, '🍮': 2 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
|
||||
});
|
||||
|
||||
test('カスタム絵文字リアクションを変換してしまわない', () => {
|
||||
const input = { ':like@.:': 1, ':pudding@example.tld:': 2 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
|
||||
});
|
||||
|
||||
test('文字列によるレガシーなリアクションを変換する', () => {
|
||||
const input = { 'like': 1, 'pudding': 2 };
|
||||
const output = { '👍': 1, '🍮': 2 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
|
||||
});
|
||||
|
||||
test('host部分が省略されたレガシーなカスタム絵文字リアクションを変換する', () => {
|
||||
const input = { ':custom_emoji:': 1 };
|
||||
const output = { ':custom_emoji@.:': 1 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
|
||||
});
|
||||
|
||||
test('「0個のリアクション」情報を削除する', () => {
|
||||
const input = { 'angry': 0 };
|
||||
const output = {};
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
|
||||
});
|
||||
|
||||
test('host部分の有無によりデコードすると同じ表記になるカスタム絵文字リアクションの個数情報を正しく足し合わせる', () => {
|
||||
const input = { ':custom_emoji:': 1, ':custom_emoji@.:': 2 };
|
||||
const output = { ':custom_emoji@.:': 3 };
|
||||
assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ async function fetchLanguage(to: string): Promise<void> {
|
|||
return bundle.id === language || bundle.aliases?.includes(language);
|
||||
});
|
||||
if (bundles.length > 0) {
|
||||
console.log(`Loading language: ${language}`);
|
||||
if (_DEV_) console.log(`Loading language: ${language}`);
|
||||
await highlighter.loadLanguage(bundles[0].import);
|
||||
codeLang.value = language;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ export class UserPreview {
|
|||
this.el.removeEventListener('mouseover', this.onMouseover);
|
||||
this.el.removeEventListener('mouseleave', this.onMouseleave);
|
||||
this.el.removeEventListener('click', this.onClick);
|
||||
window.clearInterval(this.checkTimer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@ const pagination = {
|
|||
})),
|
||||
};
|
||||
|
||||
console.log(Misskey);
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ if (game.value.isStarted && !game.value.isEnded) {
|
|||
crc32: crc32.toString(),
|
||||
}).then((res) => {
|
||||
if (res.desynced) {
|
||||
console.log('resynced');
|
||||
if (_DEV_) console.log('resynced');
|
||||
restoreGame(res.game!);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template v-if="column.channelId">
|
||||
<div style="padding: 8px; text-align: center;">
|
||||
<MkButton primary gradate rounded inline @click="post"><i class="ti ti-pencil"></i></MkButton>
|
||||
<MkButton primary gradate rounded inline small @click="post"><i class="ti ti-pencil"></i></MkButton>
|
||||
</div>
|
||||
<MkTimeline ref="timeline" src="channel" :channel="column.channelId"/>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue