iyOmSd/Title: Swift

[Swift] iMessage 필터링 ILMessageFilterExtension (스팸 차단)

냄수 2024. 2. 27. 21:24
반응형

메시지 필터링을 개발할 기회가 생겨서 학습한 것을 기록하려고해요

 

먼저 프로젝트를 생성하고

file > new > target에서 

Message Filter Extension을 클릭하면

 

 

이렇게 파일이 생성됩니다!

 

기본적인 템플릿코드는 작성돼있고 설명도 같이 써져있어요

각 함수의 역할을 알아봅시다!

 

func handle(_ capabilitiesQueryRequest: ILMessageFilterCapabilitiesQueryRequest, context: ILMessageFilterExtensionContext, completion: @escaping (ILMessageFilterCapabilitiesQueryResponse) -> Void) {
    let response = ILMessageFilterCapabilitiesQueryResponse()

    // TODO: Update subActions from ILMessageFilterSubAction enum
    // response.transactionalSubActions = [...]
    // response.promotionalSubActions   = [...]

    completion(response)
}

 

이함수에서 completion으로 ILMessageFilterCapabilitiesQueryResponse를 전달해요

ILMessageFilterCapabilitiesQueryResponse를 이용해서

메시지 필터에 노출될 카테고리를 설정할 수 있어요

 

이 화면은

설정 > 메시지 > 알 수 없는 연락처 및 스팸 > 필터링 앱 선택

 직접 해줘야 활성화돼요 

 

 

설정후 iMessage에 들어가면 뒤로가기 버튼이 생겼을거에요

 

 

let response = ILMessageFilterCapabilitiesQueryResponse()
response.transactionalSubActions = [
    .transactionalFinance,
    .transactionalOrders,
    .transactionalHealth,
    .transactionalWeather,
    .transactionalOthers,
    .transactionalCarrier,
    .transactionalPublicServices,
    .transactionalReminders,
    .transactionalRewards
]
response.promotionalSubActions = [.promotionalOffers, .promotionalCoupons, .promotionalOthers]

 

ILMessageFilterCapabilitiesQueryResponse를 생성하고

transcationalSubAction: 거래 카테고리 

promotionalSubActions: 광고 카테고리 

를 지정할 수 있어요 

설정한 response를 completion에 넣으면돼요 

 

 

다음함수는

func handle(_ queryRequest: ILMessageFilterQueryRequest, context: ILMessageFilterExtensionContext, completion: @escaping (ILMessageFilterQueryResponse) -> Void) {
    // First, check whether to filter using offline data (if possible).
    let (offlineAction, offlineSubAction) = self.offlineAction(for: queryRequest)

    switch offlineAction {
    case .allow, .junk, .promotion, .transaction:
        // Based on offline data, we know this message should either be Allowed, Filtered as Junk, Promotional or Transactional. Send response immediately.
        let response = ILMessageFilterQueryResponse()
        response.action = offlineAction
        response.subAction = offlineSubAction

        completion(response)

    case .none:
        // Based on offline data, we do not know whether this message should be Allowed or Filtered. Defer to network.
        // Note: Deferring requests to network requires the extension target's Info.plist to contain a key with a URL to use. See documentation for details.
        context.deferQueryRequestToNetwork() { (networkResponse, error) in
            let response = ILMessageFilterQueryResponse()
            response.action = .none
            response.subAction = .none

            if let networkResponse = networkResponse {
                // If we received a network response, parse it to determine an action to return in our response.
                (response.action, response.subAction) = self.networkAction(for: networkResponse)
            } else {
                NSLog("Error deferring query request to network: \(String(describing: error))")
            }

            completion(response)
        }

    @unknown default:
        break
    }
}

엄청기네요

이 함수에서 메시지 내용에 따라 원하는 카테고리로 필터링 할 수 있어요

 

ILMessageFilterQueryRequest

메시지에 대한 정보를 가지고있어요

sender: 발신자 번호 (형식: +821012341234) 13자리

messageBody: 메시지내용

receiverISOCountryCode: ISO 3166-2 표준 국가코드

 

주의 사항으로는 

내용을 print 로그로 찍어볼 수가 없어요!

시뮬에서 테스트 불가능하기 때문에

osLog를 이용해서 실기기로 문자를 주고받으며 테스트했는데 

내용이 private으로 떠요 

위에 번호형식이 써있는데 저건... 노가다..를 통해 어떤형식인지 찾았어요 ㅎ..

 

뒤늦게알앗는데 

\()으로 사용하는 문자열 보간법에

기본값으로 개인정보와 관련된것같은 내용을 알아서추론해서

private처리해주기 때문에

보고싶다면 명시적으로 public선언을 해줘야한다고하네요!!

/// 비공개
logger.warning("🔵sender: \(queryRequest.sender ?? "")")

// 공개
logger.warning("🔵sender: \(queryRequest.sender ?? "", privacy: .public)")

이미지인경우 그냥 빈칸이 떠요

 

 

알고있는번호라면 message extension이 동작하지않아요!

 

iMessage간 메세지라면 message extension이 동작하지않아요!

테스트시 iMessage를 끄거나, 안드로이드폰과 sms을 주고받아야합니다! 

iMessage는 종단간 암호화가 잘돼있어서 안전하기때문에 필터링을 따로 지원하지 않는다고 합니다

 

messageBody에 사진도 string count가 1로 취급됩니다!

따라서 문자내용을 카운팅할때 주의해야합니다

 

ILMessageFilterAction

큰 카테고리를 나타냅니다

  • allow - 허용
  • junk - 정크함
  • promotion - 광고
  • transaction - 거래

 

ILMessageFilterSubAction

상세 카테고리를 나타냅니다

  • transactionalOthers
  • transactionalFinance
  • transactionalOrders
  • transactionalReminders
  • transactionalHealth
  • transactionalWeather
  • transactionalCarrier
  • transactionalRewards
  • transactionalPublicServices
  • promotionalOthers
  • promotionalOffers
  • promotionalCoupons

 

주어진 함수중에 

private func offlineAction(for queryRequest: ILMessageFilterQueryRequest) -> (ILMessageFilterAction, ILMessageFilterSubAction) {
    // TODO: Replace with logic to perform offline check whether to filter first (if possible).
    return (.none, .none)
}

이 함수는 request를 가져와서 원하는 필터를 적용하여 반환하는 함수에요

이 함수에서 필터링을 처리하면돼요

 

번호필터 예시

if let sender = queryRequest.sender, sender.contains("+82") {
    logger.warning("🔵filter: +82")
}

request.sender를 통해서 발신자정보를 가져오고 원하는 번호필터를 할 수 있어요 

 

내용필터 예시

guard let message = queryRequest.messageBody else { return (.none, .none) }
switch message {
case _ where message.contains("debited"):
    return (.transaction, .transactionalFinance)
case _ where message.contains("coupon"):
    return (.promotion, .promotionalCoupons)
default:
    return (.none, .none)
}

messageBody를 통해서 

필터링할 단어가 있는지 필터할 수 있어요

 

 

이미지필터 예시

이 부분은 정확하지않아요

어떤형식으로 오는지 찾지못했어요...ㅠ (아시는분.. 알려주세요!!)

많은 테스트로 추론한 결과

 

1. 이미지 1장당 count가 1로  찍혀요

`안녕하세요`와 `사진`을 같이보내는경우

count: 6 이 찍히는거에요

 

2. 이미지가 data타입인가? -> ❌

 

3. 이미지가 UIImage 타입인가? -> ❌

 

4. 이미지가 base64 String 타입인가? -> ❌

 

5. string에서 어떤 문자열이 들어오나 정규식을 적용해봤는데 

모든 한글 영어 숫자 특수문자를 해봤지만 걸리지 않았어요

 

이점을 이용해서

모든 문자열이 아니라면 이미지가 포함돼있다를 적용해서 

예외케이스가 더 있을지 몰라서 정확하지 않지만 이미지필터는 되더라구요

let allPattern = "^[ㄱ-ㅎㅏ-ㅣ-가-힣0-9a-zA-Z\(letter)]+$"
let allRegex = try? NSRegularExpression(pattern: allPattern)
if let message = queryRequest.messageBody, allRegex?.firstMatch(in: message, range: NSRange(location: 0, length: message.count)) == nil {
    logger.warning("🔵이미지!!!")
}

 

 

deferQueryRequestToNetwork() 호출은

정의해둔 URL로 요청해서 필터링을 시도하는데 

네트워크에 대한 액션은 앱내에서가 아닌

서버와 직접 통신해서 결과만 가져온다 라는데

아쉽게도 네트워크 처리까진 못해봤어요 ㅠ

 

 

아래사진은 필터링 로직이에요 

 

반응형