From 354cb2a6754b55fd3ad01388a4a17d3a76d4a09b Mon Sep 17 00:00:00 2001 From: dakkar Date: Sat, 9 Mar 2024 12:17:48 +0000 Subject: handle non-ASCII emoji names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use the more inclusive regexp for validating emoji names * always normalize emoji names, aliases, categories the latter point is necessary to allow matching, for example, `ä` against `a`+combining diaeresis this will also need to bump the version of `sfm-js` once we merge https://activitypub.software/TransFem-org/sfm-js/-/merge_requests/2 --- packages/frontend/src/scripts/autocomplete.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/frontend/src/scripts') diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/scripts/autocomplete.ts index 9fc8f7843e..d942402ffc 100644 --- a/packages/frontend/src/scripts/autocomplete.ts +++ b/packages/frontend/src/scripts/autocomplete.ts @@ -99,7 +99,7 @@ export class Autocomplete { const isHashtag = hashtagIndex !== -1; const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam?.includes(' '); const isMfmTag = mfmTagIndex !== -1 && !isMfmParam; - const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); + const isEmoji = emojiIndex !== -1 && text.split(/:[\p{Letter}\p{Number}\p{Mark}_+-]+:/u).pop()!.includes(':'); let opened = false; @@ -125,7 +125,7 @@ export class Autocomplete { if (isEmoji && !opened && this.onlyType.includes('emoji')) { const emoji = text.substring(emojiIndex + 1); if (!emoji.includes(' ')) { - this.open('emoji', emoji); + this.open('emoji', emoji.normalize('NFC')); opened = true; } } -- cgit v1.2.3-freya From fa60c1d4bf40be534cecb96d6c50560257318c73 Mon Sep 17 00:00:00 2001 From: dakkar Date: Fri, 19 Apr 2024 14:42:58 +0100 Subject: make eslint happy also add some types to `chiptune2.ts` --- .../src/pages/settings/notifications.notification-config.vue | 10 +++++----- packages/frontend/src/scripts/chiptune2.ts | 10 +++++++--- packages/frontend/src/scripts/nyaize.ts | 8 ++++---- 3 files changed, 16 insertions(+), 12 deletions(-) (limited to 'packages/frontend/src/scripts') diff --git a/packages/frontend/src/pages/settings/notifications.notification-config.vue b/packages/frontend/src/pages/settings/notifications.notification-config.vue index 6dde006106..8d78ce7031 100644 --- a/packages/frontend/src/pages/settings/notifications.notification-config.vue +++ b/packages/frontend/src/pages/settings/notifications.notification-config.vue @@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - - + + + + + diff --git a/packages/frontend/src/scripts/chiptune2.ts b/packages/frontend/src/scripts/chiptune2.ts index 3cc34c0040..56afb9b708 100644 --- a/packages/frontend/src/scripts/chiptune2.ts +++ b/packages/frontend/src/scripts/chiptune2.ts @@ -1,4 +1,3 @@ -// @ts-nocheck /* eslint-disable */ const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext; @@ -6,6 +5,11 @@ const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext; let libopenmpt let libopenmptLoadPromise +type ChiptuneJsConfig = { + repeatCount: number | null; + context: AudioContext | null; +}; + export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) { this.repeatCount = repeatCount; this.context = context; @@ -13,7 +17,7 @@ export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig; -export function ChiptuneJsPlayer (config: object) { +export function ChiptuneJsPlayer (config: ChiptuneJsConfig) { this.config = config; this.audioContext = config.context || new ChiptuneAudioContext(); this.context = this.audioContext.createGain(); @@ -27,7 +31,7 @@ ChiptuneJsPlayer.prototype.initialize = function() { if (libopenmptLoadPromise) return libopenmptLoadPromise; if (libopenmpt) return Promise.resolve(); - libopenmptLoadPromise = new Promise(async (resolve, reject) => { + libopenmptLoadPromise = new Promise(async (resolve, reject) => { try { const { Module } = await import('./libopenmpt/libopenmpt.js'); await new Promise((resolve) => { diff --git a/packages/frontend/src/scripts/nyaize.ts b/packages/frontend/src/scripts/nyaize.ts index 58ed88fed1..5e6fa298d1 100644 --- a/packages/frontend/src/scripts/nyaize.ts +++ b/packages/frontend/src/scripts/nyaize.ts @@ -9,9 +9,9 @@ const koRegex3 = /(야(?=\?))|(야$)|(야(?= ))/gm; function ifAfter(prefix, fn) { const preLen = prefix.length; - const regex = new RegExp(prefix,'i'); - return (x,pos,string) => { - return pos > 0 && string.substring(pos-preLen,pos).match(regex) ? fn(x) : x; + const regex = new RegExp(prefix, 'i'); + return (x, pos, string) => { + return pos > 0 && string.substring(pos - preLen, pos).match(regex) ? fn(x) : x; }; } @@ -25,7 +25,7 @@ export function nyaize(text: string): string { .replace(/one/gi, ifAfter('every', x => x === 'ONE' ? 'NYAN' : 'nyan')) // ko-KR .replace(koRegex1, match => String.fromCharCode( - match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0), + match.charCodeAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0), )) .replace(koRegex2, '다냥') .replace(koRegex3, '냥'); -- cgit v1.2.3-freya From 26f89194324da565aafd5943284d83fed9dcde5f Mon Sep 17 00:00:00 2001 From: Hazel Koehler Date: Thu, 2 May 2024 20:31:34 -0400 Subject: feat: check polls and media for muted keywords --- packages/frontend/src/scripts/check-word-mute.ts | 73 ++++++++++++++++-------- 1 file changed, 48 insertions(+), 25 deletions(-) (limited to 'packages/frontend/src/scripts') diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts index 67e896b4b9..6f3c6c40de 100644 --- a/packages/frontend/src/scripts/check-word-mute.ts +++ b/packages/frontend/src/scripts/check-word-mute.ts @@ -3,40 +3,63 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export function checkWordMute(note: Record, me: Record | null | undefined, mutedWords: Array): boolean { +import type { Note, MeDetailed } from "misskey-js/entities.js"; + +// TODO: this implementation is horribly inefficient. +// Each regex is validated (using a regex, ironically), transformed, and then parsed - for each note being checked. +// These regex objects should be cached somewhere. + +export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mutedWords: Array): boolean { // 自分自身 if (me && (note.userId === me.id)) return false; - if (mutedWords.length > 0) { - const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim(); + if (mutedWords.length < 1) return false; - if (text === '') return false; + const text = getNoteText(note); + if (text === '') return false; - const matched = mutedWords.some(filter => { - if (Array.isArray(filter)) { - // Clean up - const filteredFilter = filter.filter(keyword => keyword !== ''); - if (filteredFilter.length === 0) return false; + return mutedWords.some(filter => { + if (Array.isArray(filter)) { + // Clean up + const filteredFilter = filter.filter(keyword => keyword !== ''); + if (filteredFilter.length === 0) return false; - return filteredFilter.every(keyword => text.includes(keyword)); - } else { - // represents RegExp - const regexp = filter.match(/^\/(.+)\/(.*)$/); + return filteredFilter.every(keyword => text.includes(keyword)); + } else { + // represents RegExp + 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. - 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; - } + return false; } - }); + } + }); +} + +function getNoteText(note: Note): string { + const textParts: string[] = []; + + if (note.cw) + textParts.push(note.cw); + + if (note.text) + textParts.push(note.text); + + if (note.files) + for (const file of note.files) + if (file.comment) + textParts.push(file.comment); - if (matched) return true; - } + if (note.poll) + for (const choice of note.poll.choices) + if (choice.text) + textParts.push(choice.text); - return false; + return textParts.join('\n').trim(); } -- cgit v1.2.3-freya From cf317da2449f831fd0c80b15df4013ce26e08e91 Mon Sep 17 00:00:00 2001 From: Hazel Koehler Date: Fri, 3 May 2024 22:23:24 -0400 Subject: revert refactor and cleanup --- packages/frontend/src/scripts/check-word-mute.ts | 51 ++++++++++++------------ 1 file changed, 26 insertions(+), 25 deletions(-) (limited to 'packages/frontend/src/scripts') diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts index 6f3c6c40de..8d3e96cea5 100644 --- a/packages/frontend/src/scripts/check-word-mute.ts +++ b/packages/frontend/src/scripts/check-word-mute.ts @@ -5,41 +5,42 @@ import type { Note, MeDetailed } from "misskey-js/entities.js"; -// TODO: this implementation is horribly inefficient. -// Each regex is validated (using a regex, ironically), transformed, and then parsed - for each note being checked. -// These regex objects should be cached somewhere. - export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mutedWords: Array): boolean { // 自分自身 if (me && (note.userId === me.id)) return false; - if (mutedWords.length < 1) return false; - - const text = getNoteText(note); - if (text === '') return false; + if (mutedWords.length > 0) { + const text = getNoteText(note); - return mutedWords.some(filter => { - if (Array.isArray(filter)) { - // Clean up - const filteredFilter = filter.filter(keyword => keyword !== ''); - if (filteredFilter.length === 0) return false; + if (text === '') return false; - return filteredFilter.every(keyword => text.includes(keyword)); - } else { - // represents RegExp - const regexp = filter.match(/^\/(.+)\/(.*)$/); + const matched = mutedWords.some(filter => { + if (Array.isArray(filter)) { + // Clean up + const filteredFilter = filter.filter(keyword => keyword !== ''); + if (filteredFilter.length === 0) return false; - // This should never happen due to input sanitisation. - if (!regexp) return false; + return filteredFilter.every(keyword => text.includes(keyword)); + } else { + // represents RegExp + const regexp = filter.match(/^\/(.+)\/(.*)$/); - try { - return new RegExp(regexp[1], regexp[2]).test(text); - } catch (err) { // This should never happen due to input sanitisation. - return false; + 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; + } } - } - }); + }); + + if (matched) return true; + } + + return false; } function getNoteText(note: Note): string { -- cgit v1.2.3-freya From 2c40dd31f32edffcc8f1da7bea53b14589c5d2ad Mon Sep 17 00:00:00 2001 From: dakkar Date: Tue, 7 May 2024 20:19:52 +0000 Subject: laxer HTML sanitisation for admin-controlled text - fixes #447 --- .../frontend/src/components/MkSignupDialog.rules.vue | 2 +- .../frontend/src/components/MkVisitorDashboard.vue | 2 +- packages/frontend/src/pages/about.vue | 2 +- packages/frontend/src/scripts/sanitize-html.ts | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 packages/frontend/src/scripts/sanitize-html.ts (limited to 'packages/frontend/src/scripts') diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index 18a9eeda23..c2435b308f 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref } from 'vue'; import { instance } from '@/instance.js'; import { i18n } from '@/i18n.js'; -import sanitizeHtml from 'sanitize-html'; +import sanitizeHtml from '@/scripts/sanitize-html.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index d8e6ba9a09..f9f16c594e 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only