Compare commits
13 Commits
682b3451c7
...
8704d8830b
Author | SHA1 | Date |
---|---|---|
|
8704d8830b | |
|
4d562e7439 | |
|
30df768d26 | |
|
32cf70eb9b | |
|
d639f4bd4a | |
|
85b8af4b98 | |
|
2adf917fcf | |
|
fe8f20779b | |
|
ca30df4b90 | |
|
cc1fdece65 | |
|
9a71514b4c | |
|
fbf7c61c07 | |
|
1d2f2eda30 |
|
@ -9,6 +9,7 @@
|
||||||
- Enhance: アンテナ、リスト等の名前をカラム名のデフォルト値にするように `#13992`
|
- Enhance: アンテナ、リスト等の名前をカラム名のデフォルト値にするように `#13992`
|
||||||
- Enhance: クライアントエラー画面の多言語対応
|
- Enhance: クライアントエラー画面の多言語対応
|
||||||
- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
|
- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
|
||||||
|
- Enhance: ノートに埋め込まれたメディアのコンテキストメニューから管理者用のファイル管理画面を開けるように ( #15440 )
|
||||||
- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
|
- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
|
||||||
- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
|
- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
"misskey-bubble-game": "workspace:*",
|
"misskey-bubble-game": "workspace:*",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
|
"modern-ahocorasick": "2.0.3",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.26.0",
|
"rollup": "4.26.0",
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { mainRouter } from '@/router/main.js';
|
||||||
import { makeHotkey } from '@/scripts/hotkey.js';
|
import { makeHotkey } from '@/scripts/hotkey.js';
|
||||||
import type { Keymap } from '@/scripts/hotkey.js';
|
import type { Keymap } from '@/scripts/hotkey.js';
|
||||||
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
|
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
|
||||||
|
import { initWordMuteInfo } from '@/scripts/check-word-mute.js';
|
||||||
|
|
||||||
export async function mainBoot() {
|
export async function mainBoot() {
|
||||||
const { isClientUpdated } = await common(() => {
|
const { isClientUpdated } = await common(() => {
|
||||||
|
@ -344,11 +345,14 @@ export async function mainBoot() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initWordMuteInfo();
|
||||||
|
|
||||||
const main = markRaw(stream.useChannel('main', null, 'System'));
|
const main = markRaw(stream.useChannel('main', null, 'System'));
|
||||||
|
|
||||||
// 自分の情報が更新されたとき
|
// 自分の情報が更新されたとき
|
||||||
main.on('meUpdated', i => {
|
main.on('meUpdated', i => {
|
||||||
updateAccountPartial(i);
|
updateAccountPartial(i);
|
||||||
|
initWordMuteInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllNotifications', () => {
|
main.on('readAllNotifications', () => {
|
||||||
|
|
|
@ -217,10 +217,9 @@ function showMenu(ev: MouseEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const details: MenuItem[] = [];
|
||||||
if ($i?.id === props.audio.userId) {
|
if ($i?.id === props.audio.userId) {
|
||||||
menu.push({
|
details.push({
|
||||||
type: 'divider',
|
|
||||||
}, {
|
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: i18n.ts._fileViewer.title,
|
text: i18n.ts._fileViewer.title,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
|
@ -228,6 +227,19 @@ function showMenu(ev: MouseEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (iAmModerator) {
|
||||||
|
details.push({
|
||||||
|
type: 'link',
|
||||||
|
text: i18n.ts.moderation,
|
||||||
|
icon: 'ti ti-photo-exclamation',
|
||||||
|
to: `/admin/file/${props.audio.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.length > 0) {
|
||||||
|
menu.push({ type: 'divider' }, ...details);
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultStore.state.devMode) {
|
if (defaultStore.state.devMode) {
|
||||||
menu.push({ type: 'divider' }, {
|
menu.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-id',
|
icon: 'ti ti-id',
|
||||||
|
|
|
@ -133,10 +133,9 @@ function showMenu(ev: MouseEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const details: MenuItem[] = [];
|
||||||
if ($i?.id === props.image.userId) {
|
if ($i?.id === props.image.userId) {
|
||||||
menuItems.push({
|
details.push({
|
||||||
type: 'divider',
|
|
||||||
}, {
|
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: i18n.ts._fileViewer.title,
|
text: i18n.ts._fileViewer.title,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
|
@ -144,6 +143,19 @@ function showMenu(ev: MouseEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (iAmModerator) {
|
||||||
|
details.push({
|
||||||
|
type: 'link',
|
||||||
|
text: i18n.ts.moderation,
|
||||||
|
icon: 'ti ti-photo-exclamation',
|
||||||
|
to: `/admin/file/${props.image.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.length > 0) {
|
||||||
|
menuItems.push({ type: 'divider' }, ...details);
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultStore.state.devMode) {
|
if (defaultStore.state.devMode) {
|
||||||
menuItems.push({ type: 'divider' }, {
|
menuItems.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-id',
|
icon: 'ti ti-id',
|
||||||
|
|
|
@ -242,10 +242,9 @@ function showMenu(ev: MouseEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const details: MenuItem[] = [];
|
||||||
if ($i?.id === props.video.userId) {
|
if ($i?.id === props.video.userId) {
|
||||||
menu.push({
|
details.push({
|
||||||
type: 'divider',
|
|
||||||
}, {
|
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: i18n.ts._fileViewer.title,
|
text: i18n.ts._fileViewer.title,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
|
@ -253,6 +252,19 @@ function showMenu(ev: MouseEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (iAmModerator) {
|
||||||
|
details.push({
|
||||||
|
type: 'link',
|
||||||
|
text: i18n.ts.moderation,
|
||||||
|
icon: 'ti ti-photo-exclamation',
|
||||||
|
to: `/admin/file/${props.video.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.length > 0) {
|
||||||
|
menu.push({ type: 'divider' }, ...details);
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultStore.state.devMode) {
|
if (defaultStore.state.devMode) {
|
||||||
menu.push({ type: 'divider' }, {
|
menu.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-id',
|
icon: 'ti ti-id',
|
||||||
|
|
|
@ -282,8 +282,8 @@ const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filte
|
||||||
const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
|
const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
|
||||||
const collapsed = ref(appearNote.value.cw == null && isLong);
|
const collapsed = ref(appearNote.value.cw == null && isLong);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
|
const muted = ref(checkMute(appearNote.value, 'soft'));
|
||||||
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true));
|
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, 'hard', true));
|
||||||
const showSoftWordMutedWord = computed(() => defaultStore.state.showSoftWordMutedWord);
|
const showSoftWordMutedWord = computed(() => defaultStore.state.showSoftWordMutedWord);
|
||||||
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
|
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
|
@ -302,20 +302,18 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/* Overload FunctionにLintが対応していないのでコメントアウト
|
/* Overload FunctionにLintが対応していないのでコメントアウト
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
|
function checkMute(noteToCheck: Misskey.entities.Note, type: 'soft' | 'hard', checkOnly: true): boolean;
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): Array<string | string[]> | false | 'sensitiveMute';
|
function checkMute(noteToCheck: Misskey.entities.Note, type: 'soft' | 'hard', checkOnly: false): Array<string | string[]> | false | 'sensitiveMute';
|
||||||
*/
|
*/
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): Array<string | string[]> | false | 'sensitiveMute' {
|
function checkMute(noteToCheck: Misskey.entities.Note, type: 'soft' | 'hard', checkOnly = false): Array<string | string[]> | false | 'sensitiveMute' {
|
||||||
if (mutedWords != null) {
|
const result = checkWordMute(noteToCheck, $i, type);
|
||||||
const result = checkWordMute(noteToCheck, $i, mutedWords);
|
if (Array.isArray(result)) return result;
|
||||||
if (Array.isArray(result)) return result;
|
|
||||||
|
|
||||||
const replyResult = noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords);
|
const replyResult = noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, type);
|
||||||
if (Array.isArray(replyResult)) return replyResult;
|
if (Array.isArray(replyResult)) return replyResult;
|
||||||
|
|
||||||
const renoteResult = noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords);
|
const renoteResult = noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, type);
|
||||||
if (Array.isArray(renoteResult)) return renoteResult;
|
if (Array.isArray(renoteResult)) return renoteResult;
|
||||||
}
|
|
||||||
|
|
||||||
if (checkOnly) return false;
|
if (checkOnly) return false;
|
||||||
|
|
||||||
|
|
|
@ -298,7 +298,7 @@ const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
|
||||||
const isMyRenote = $i && ($i.id === note.value.userId);
|
const isMyRenote = $i && ($i.id === note.value.userId);
|
||||||
const showContent = ref(false);
|
const showContent = ref(false);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false);
|
const muted = ref($i ? checkWordMute(appearNote.value, $i, 'soft') : false);
|
||||||
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
|
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
|
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
|
||||||
|
|
|
@ -62,7 +62,7 @@ const props = withDefaults(defineProps<{
|
||||||
depth: 1,
|
depth: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
|
const muted = ref($i ? checkWordMute(props.note, $i, 'soft') : false);
|
||||||
|
|
||||||
const showContent = ref(false);
|
const showContent = ref(false);
|
||||||
const replies = ref<Misskey.entities.Note[]>([]);
|
const replies = ref<Misskey.entities.Note[]>([]);
|
||||||
|
|
|
@ -109,6 +109,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div>
|
<div>
|
||||||
<a style="display: inline-block;" class="pepabo" title="GMO Pepabo" href="https://pepabo.com/" target="_blank"><img style="width: 100%;" src="https://assets.misskey-hub.net/sponsors/gmo_pepabo.svg" alt="GMO Pepabo"></a>
|
<a style="display: inline-block;" class="pepabo" title="GMO Pepabo" href="https://pepabo.com/" target="_blank"><img style="width: 100%;" src="https://assets.misskey-hub.net/sponsors/gmo_pepabo.svg" alt="GMO Pepabo"></a>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<a style="display: inline-block;" class="purpledotdigital" title="Purple Dot Digital" href="https://purpledotdigital.com/" target="_blank"><img style="width: 100%;" src="https://assets.misskey-hub.net/sponsors/purple-dot-digital.jpg" alt="Purple Dot Digital"></a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
<FormSection>
|
<FormSection>
|
||||||
|
|
|
@ -2,42 +2,101 @@
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import * as Misskey from 'misskey-js';
|
import * as AhoCorasick from 'modern-ahocorasick';
|
||||||
|
import type * as Misskey from 'misskey-js';
|
||||||
|
import { $i } from '@/account.js';
|
||||||
|
|
||||||
export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): Array<string | string[]> | false {
|
type WordMuteInfo = false | {
|
||||||
// 自分自身
|
normals: string[];
|
||||||
if (me && (note.userId === me.id)) return false;
|
and: string[][];
|
||||||
|
regex: Array<{ original: string; regex: RegExp }>;
|
||||||
|
ahoCorasick: AhoCorasick.default;
|
||||||
|
}
|
||||||
|
|
||||||
if (mutedWords.length > 0) {
|
type GlobalMisskeyWordMute = {
|
||||||
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
|
soft: WordMuteInfo;
|
||||||
|
hard: WordMuteInfo;
|
||||||
|
};
|
||||||
|
|
||||||
if (text === '') return false;
|
export function createWordMuteInfo(mutedWords: Array<string | string[]>) : WordMuteInfo {
|
||||||
|
if (mutedWords.length <= 0) return false;
|
||||||
|
const normalTexts: string[] = [];
|
||||||
|
const andTexts: string[][] = [];
|
||||||
|
const regexTexts: Array<{ original: string; regex: RegExp }> = [];
|
||||||
|
|
||||||
const matched = mutedWords.filter(filter => {
|
for (const filter of mutedWords) {
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
// Clean up
|
if (filter.length === 1) {
|
||||||
const filteredFilter = filter.filter(keyword => keyword !== '');
|
normalTexts.push(filter[0]);
|
||||||
if (filteredFilter.length === 0) return false;
|
|
||||||
|
|
||||||
return filteredFilter.every(keyword => text.includes(keyword));
|
|
||||||
} else {
|
} else {
|
||||||
// represents RegExp
|
andTexts.push(filter);
|
||||||
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
|
||||||
|
|
||||||
// This should never happen due to input sanitisation.
|
|
||||||
if (!regexp) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new RegExp(regexp[1], regexp[2]).test(text);
|
|
||||||
} catch (err) {
|
|
||||||
// This should never happen due to input sanitisation.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
} else if (filter.startsWith('/') && filter.endsWith('/')) {
|
||||||
|
const regExp = filter.match(/^\/(.+)\/(.*)$/);
|
||||||
if (matched.length > 0) return matched;
|
if (!regExp) continue;
|
||||||
|
try {
|
||||||
|
regexTexts.push({ original: filter, regex: new RegExp(filter.slice(1, -1)) });
|
||||||
|
} catch {
|
||||||
|
// 無効な正規表現はスキップ
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
normalTexts.push(filter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
const ac = new AhoCorasick.default(normalTexts);
|
||||||
|
|
||||||
|
return {
|
||||||
|
normals: normalTexts,
|
||||||
|
and: andTexts,
|
||||||
|
regex: regexTexts,
|
||||||
|
ahoCorasick: ac,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWordMuteInfo(mutedWords: Array<string | string[]>, hardMutedWords: Array<string | string[]>): void {
|
||||||
|
const soft = createWordMuteInfo(mutedWords);
|
||||||
|
const hard = createWordMuteInfo(hardMutedWords);
|
||||||
|
|
||||||
|
globalThis._misskeyWordMute = { soft, hard };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWordMuteInfo(): GlobalMisskeyWordMute | undefined {
|
||||||
|
if (!globalThis._misskeyWordMute) return undefined;
|
||||||
|
return globalThis._misskeyWordMute as unknown as GlobalMisskeyWordMute;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initWordMuteInfo(): void {
|
||||||
|
const mutedWords = $i?.mutedWords ?? [];
|
||||||
|
const hardMutedWords = $i?.hardMutedWords ?? [];
|
||||||
|
|
||||||
|
setWordMuteInfo(mutedWords, hardMutedWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkWordMute(
|
||||||
|
note: Misskey.entities.Note,
|
||||||
|
me: Misskey.entities.UserLite | null | undefined,
|
||||||
|
type: 'soft' | 'hard',
|
||||||
|
): Array<string | string[]> | false {
|
||||||
|
// 自分自身の投稿は対象外
|
||||||
|
if (me && (note.userId === me.id)) return false;
|
||||||
|
|
||||||
|
const wordMuteInfo = getWordMuteInfo()?.[type];
|
||||||
|
|
||||||
|
if (wordMuteInfo == null || wordMuteInfo === false) return false;
|
||||||
|
|
||||||
|
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
|
||||||
|
if (text === '') return false;
|
||||||
|
|
||||||
|
const normalMatches = wordMuteInfo.ahoCorasick.search(text);
|
||||||
|
|
||||||
|
// andTexts
|
||||||
|
const andMatches = wordMuteInfo.and.filter(texts => texts.filter(keyword => keyword !== '').every(keyword => text.includes(keyword)));
|
||||||
|
|
||||||
|
// RegExp
|
||||||
|
const regexMatches = wordMuteInfo.regex.filter(({ regex }) => regex.test(text));
|
||||||
|
|
||||||
|
const matched: Array<string | string[]> = normalMatches.map(match => match[1]).concat(andMatches, regexMatches.map(({ original }) => original));
|
||||||
|
|
||||||
|
return matched.length > 0 ? matched : false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type * as Misskey from 'misskey-js';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
export const UserLiteMock = vi.fn(() => {
|
||||||
|
return {
|
||||||
|
id: 'xxxxxxxx',
|
||||||
|
username: 'ai',
|
||||||
|
host: null,
|
||||||
|
name: '藍',
|
||||||
|
avatarUrl: null,
|
||||||
|
avatarBlurhash: null,
|
||||||
|
avatarDecorations: [],
|
||||||
|
emojis: {},
|
||||||
|
onlineStatus: 'online',
|
||||||
|
} as Misskey.entities.UserLite;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NoteMock = vi.fn((options?: {
|
||||||
|
text?: string,
|
||||||
|
cw?: string,
|
||||||
|
}) => {
|
||||||
|
const user = new UserLiteMock();
|
||||||
|
return {
|
||||||
|
id: 'xxxxxxxx',
|
||||||
|
// 2025/01/01 00:00:00 UTC on Unix time
|
||||||
|
createdAt: '1767225600000',
|
||||||
|
text: options?.text ?? 'Hello, Misskey!',
|
||||||
|
cw: options?.cw,
|
||||||
|
userId: user.id,
|
||||||
|
user: user,
|
||||||
|
visibility: 'public',
|
||||||
|
reactionAcceptance: null,
|
||||||
|
reactionEmojis: {},
|
||||||
|
reactions: {},
|
||||||
|
reactionCount: 0,
|
||||||
|
renoteCount: 0,
|
||||||
|
repliesCount: 0,
|
||||||
|
} as Misskey.entities.Note;
|
||||||
|
});
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, assert } from 'vitest';
|
||||||
|
import { createWordMuteInfo, checkWordMute } from '@/scripts/check-word-mute.js';
|
||||||
|
import { NoteMock } from './mocks.js';
|
||||||
|
|
||||||
|
type TestCases = {
|
||||||
|
text: string,
|
||||||
|
cw?: string,
|
||||||
|
mutedWords: Array<string | string[]>,
|
||||||
|
result: false | Array<string | string[]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('check-word-mute', () => {
|
||||||
|
const cases:Array<TestCases> = [
|
||||||
|
{
|
||||||
|
text: 'Hello, Misskey!',
|
||||||
|
mutedWords: ['Misskey'],
|
||||||
|
result: [['Misskey']],
|
||||||
|
},
|
||||||
|
// cw
|
||||||
|
{
|
||||||
|
text: 'Hello, Misskey!',
|
||||||
|
cw: 'ai',
|
||||||
|
mutedWords: ['ai'],
|
||||||
|
result: [['ai']],
|
||||||
|
},
|
||||||
|
// nothing
|
||||||
|
{
|
||||||
|
text: 'Hello, Misskey!',
|
||||||
|
mutedWords: [],
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
// surrogate pair
|
||||||
|
{
|
||||||
|
text: '𠮟る',
|
||||||
|
mutedWords: ['𠮟'],
|
||||||
|
result: [['𠮟']],
|
||||||
|
},
|
||||||
|
// grapheme cluster
|
||||||
|
{
|
||||||
|
text: '👩❤️👨',
|
||||||
|
mutedWords: ['👩'],
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
// regex
|
||||||
|
{
|
||||||
|
text: 'Hello, Misskey!',
|
||||||
|
mutedWords: ['/M[isk]*ey/'],
|
||||||
|
result: ['/M[isk]*ey/'],
|
||||||
|
},
|
||||||
|
// multi words
|
||||||
|
{
|
||||||
|
text: 'Hello, Misskey!',
|
||||||
|
mutedWords: [['Hello', 'Misskey'], ['Mi']],
|
||||||
|
result: [['Mi'],['Hello', 'Misskey']],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
cases.forEach((c) => {
|
||||||
|
test(`text: ${c.text}, cw: ${c.cw}, mutedWords: ${c.mutedWords}` , async () => {
|
||||||
|
// initWordMuteInfoが実行されないので代わりにここで初期化
|
||||||
|
(globalThis as any)._misskeyWordMute = {
|
||||||
|
soft: createWordMuteInfo(c.mutedWords),
|
||||||
|
hard: createWordMuteInfo([]),
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = NoteMock({ text: c.text, cw: c.cw });
|
||||||
|
const result = checkWordMute(note, null, 'soft');
|
||||||
|
assert.deepEqual(result, c.result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -799,6 +799,9 @@ importers:
|
||||||
misskey-reversi:
|
misskey-reversi:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../misskey-reversi
|
version: link:../misskey-reversi
|
||||||
|
modern-ahocorasick:
|
||||||
|
specifier: 2.0.3
|
||||||
|
version: 2.0.3
|
||||||
photoswipe:
|
photoswipe:
|
||||||
specifier: 5.4.4
|
specifier: 5.4.4
|
||||||
version: 5.4.4
|
version: 5.4.4
|
||||||
|
@ -7854,6 +7857,7 @@ packages:
|
||||||
|
|
||||||
lodash.get@4.4.2:
|
lodash.get@4.4.2:
|
||||||
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||||
|
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
|
||||||
|
|
||||||
lodash.isarguments@3.1.0:
|
lodash.isarguments@3.1.0:
|
||||||
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
||||||
|
@ -8311,6 +8315,9 @@ packages:
|
||||||
resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==}
|
resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
modern-ahocorasick@2.0.3:
|
||||||
|
resolution: {integrity: sha512-3vsbnf5DmpsaE8Ye892HecJU7kaT2svsIBXNhne1J080WlU9RKjTtE5PgX+OCc2huqGqGYO+rVEsJlJJuQj+Qw==}
|
||||||
|
|
||||||
module-details-from-path@1.0.3:
|
module-details-from-path@1.0.3:
|
||||||
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
|
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
|
||||||
|
|
||||||
|
@ -19896,6 +19903,8 @@ snapshots:
|
||||||
|
|
||||||
mock-socket@9.3.1: {}
|
mock-socket@9.3.1: {}
|
||||||
|
|
||||||
|
modern-ahocorasick@2.0.3: {}
|
||||||
|
|
||||||
module-details-from-path@1.0.3: {}
|
module-details-from-path@1.0.3: {}
|
||||||
|
|
||||||
ms@2.0.0: {}
|
ms@2.0.0: {}
|
||||||
|
|
Loading…
Reference in New Issue