Привет, Хабр! На связи снова Александр Пиманов (по-прежнему iOS-разработчик МТС Диджитал). Сегодня поделюсь своим опытом в одной интересной нишевой теме: фильтрации нецензурной лексики в приложении для iOS.
Да, мало кому может понадобиться фильтровать мат на клиенте, но если у вас есть функция нейминга элементов в UI (добавление кастомного имени страницы, кнопки и так далее), запрос от бизнеса на такой фильтр и вы хотите сделать «проверку на дурака», то эта статья для вас. Прелюдия окончена, все подробности под катом.
Как я докатился до такой жизни
Решение делать первичную фильтрацию текста на клиенте пришло внезапно: бэк-разработчик закинул идею, мне она понравилась, а бизнес оценил положительно.
Возможно, сейчас в меня полетят помидоры: дорого по ресурсам, да и вообще, эта затея не имеет смысла. Но у нас большая часть сообщений летит с сокета, где уже есть фильтрация текста с бэка. Я же решил сделать первичную проверку мата на клиенте, чтобы избежать неприятных сюрпризов.
Регулярное выражение
Потратив N времени на ресерч, я нашел open-source регулярку под Java-машину. В этой регулярке берутся все популярные и часто используемые вариации мата (на одно слово несколько вариаций синтаксиса) и летят в основу фильтра, который я покажу чуть позже. Я переписал её на Swift с учетом всех особенностей языка и немного подправил.
Хочу отметить, что это первичная проверка на самые популярные слова и их разновидности. Никакая регулярка не покроет вам 100% кейсов, ибо на сцену вступает человеческий фактор: если юзер захочет выругаться, поверьте, он этo_!cделает, какая бы защита у вас не стояла. В нашем случае первичная фильтрация при отправке сообщений в чат покроет порядка 80–90% нецензурных выражений.
Проверка
Как я говорил выше, прогонять через фильтр по регулярке и искать совпадения мы будем только в пользовательских сообщениях:
func filterSwearWords(in message: String) -> String {
do {
let regex = try NSRegularExpression(pattern: swearWordsPattern, options: [.caseInsensitive, .allowCommentsAndWhitespace])
let range = NSRange(location: 0, length: message.count)
let matches = regex.matches(in: message, options: [], range: range)
var filteredMessage = message as NSString
// reversed() is to ensure that earlier replacements do not affect the positions of later matches.
for match in matches.reversed() {
let matchedWord = filteredMessage.substring(with: match.range)
let isFirstWord = message.starts(with: matchedWord)
guard matchedWord.count > 2,
let firstCharacter = isFirstWord ? matchedWord.first?.uppercased() : matchedWord.first?.lowercased(),
let lastCharacter = matchedWord.last else { return message }
let replacement = "\(firstCharacter)***\(lastCharacter)"
filteredMessage = filteredMessage.replacingCharacters(in: match.range, with: replacement) as NSString
}
return filteredMessage as String
} catch {
print("Creating regex error: \(error.localizedDescription)")
return message
}
}
Обратите внимание на строчку, где мы входим в цикл. Я там поставил reversed() так как словил интересный баг: ранние замены влияют на позиции более поздних совпадений.
Затем создается подстрока с нашим совпадением. Ее содержимое проверяется на положение этого слова в контексте всего сообщения (в начале или нет).
Если слово удовлетворяет требованию регулярки и в нем больше двух букв, я беру первую и последнюю букву и вставляю между ними ***. Я решил не подгонять число звездочек под количество заменяемых символов. Это вкусовщина, но фиксированный вариант показался самым подходящим.
Ну вот, собственно, и все! Теперь просто вызываем этот метод в момент отправки сообщения, куда скармливаем текст из нашего text field, и наблюдаем магию:
Скрытый текст
Выводы
По-хорошему такую регулярку можно и нужно получать с бэкэнда, чтобы не хранить локально. Со своей задачей она справится и сделает общение в чате «чище». На этом у меня все, надеюсь, статья была вам о***о полезна!
Комментарии (21)
randomsimplenumber
22.08.2024 16:56Застра##й ком@@@у корабля!
Кто-то прочитал старинные манускрипты и решил пробудить древнее зло? Есть всякие чудеса в unicode, есть картинки в сообщениях, есть ASCII art.. Кто захочет - найдет способ выразиться. А ему зачем то мешают.
kenomimi
22.08.2024 16:56+2А еще, когда на двачах запретили слово баттхерт - были такие времена - аноны стали писать любое слово капсом - "да у вас БАГЕТ, батенька!"
Автозамена матов разве что в детских чатах полезна, чтобы не поощрять тупую ругань в целом. В остальном против матершины, люмпен-жаргона и похабщины нужны административные методы, а не технические - написал задачу на фене - минус премия - все быстро культурные станут. Выругался в чат - бан на пару часов, чтобы остыл. Не понял и повторил - опять минус премия.
randomsimplenumber
22.08.2024 16:56+1написал задачу на фене - минус премия
Поставил задачу на словах - нормально так? ;) Если у вас в коллективе принято общаться матом - к чему эти ограничения? Если не принято - то и так никто не будет писать нехороших слов.
AleksandrPimanov Автор
22.08.2024 16:56Где написано, что задача была поставлена на словах? Во первых, это просто идея от бэк разработчика, во вторых она прошла этапы согласования и одобрения). Кроме того, у нас в команде матом не общаются) не пойму, к чему этот комментарий
randomsimplenumber
22.08.2024 16:56Где написано, что задача была поставлена на словах?
Если задача поставлена на словах, матом, и не записана - с кого минус премия? ;)
Во первых, это просто идея от бэк разработчика, во вторых она прошла этапы согласования и одобрения)
Ну и замечательно. Начальство виднее чем занять программистов;)
, у нас в команде матом не общаются
Ну и прекрасно ;)
Так то предыдущий комментатор размахивал шашкой и требовал штрафовать, банить и всякого кровопролития. Вот и интересно стало, с кем он общается, раз это настолько актуально.
Кстати, голосовые сообщения ваш чатик умеет? ;)
AleksandrPimanov Автор
22.08.2024 16:56голосовых пока не предвидится) может быть в будущем, но вряд ли
randomsimplenumber
22.08.2024 16:56это я к тому, что пока олды решают свои регэкспы, школота посылает друг другу голосовые сообщения.
Tirarex
22.08.2024 16:56Было бы забавно написать фильтр но с использованием нейронки, дабы сообщения аккуратно разворачивались в светский диалог.
AleksandrPimanov Автор
22.08.2024 16:56Ага)) использование нейронки это следующий этап рефакторинга фичи)
exTvr
22.08.2024 16:56Что-то из из баша/автозамены в почте и "Челом бьём боярин, но гонец твой не добёг до нас ещё, посему не раскурили мы таску толком - ещё хоть пару спринтов дай нам днесь".
SquareRootOfZero
22.08.2024 16:56+3И ответ приходит: "Ой вы, гой еси, сыны блудниц, идите на уд срамной."
konst90
22.08.2024 16:56+2Токарь в ответ на претензии мастера утверждает, что вступал в интимные отношение с матерью мастера, директором завода и изготавливаемой деталью.
SquareRootOfZero
22.08.2024 16:56Есть куда дорабатывать регексп. Например:
Спиздил до пизды пёзд: пизда пизды пизже. Сижу, пизжу про пёзды спизженные.
Не ловит слова "пизже", "пизжу", "спизженные".
AleksandrPimanov Автор
22.08.2024 16:56Верно, там могут быть слова, которые он пропускает, по мере получения таких «пропусков» вношу изменения) это нормально, всех слов сразу не уловишь, да и не к чему, я писал, что это лишь первоначальная проверка
anonymous
НЛО прилетело и опубликовало эту надпись здесь