废物短信是一个长期存在、令人困扰的问题。本文将介绍怎么阻挠这些短信、设备端的检测以及整合动态的服务器检测等。

Apple 在 WWDC 2017(iOS 11) 推出了 IdentityLookup 结构,让开发者能够参与到过滤短信的过程中。在 iOS 14,Apple 新增了两种过滤类别:交易信息(Promotion)、推广信息(transaction)。在 WWDC 2022(iOS 16),针对这两种类别,Apple 新增了 12 种子类别,推广信息包括 9 种子类别:其他(Others)、财务(Finance)、订单(Orders)、提示(Reminders)、健康(Health)、气候(Weather)、运营商(Carrier)、奖励(Rewards)、公共服务(PublicServices)。交易信息包括 3 种子类别:其他(Others)、优惠(Offers)、优惠券(Coupons)。

在 iOS 中使用 IdentifyLookup 进行短信过滤

音讯过滤流程

音讯过滤经过运用程序扩展(App extension)来完结。当用户收到来自不知道发件人的音讯时,“音讯” APP 经过询问 Message Filter Extension,来确认该音讯的类别。Message Filter Extension 能够经过运用内置逻辑或推迟到相关服务器的分析来做出此决议。

IdentityLookup 仅适用于来自不知道发件人的短信和彩信,它不适用于联系人列表中发件人的音讯、不适用任何 iMessage 音讯、不适用于回复发件人 3 次及以上的会话。

在 iOS 中使用 IdentifyLookup 进行短信过滤

“音讯” APP 运用一个 ILMessageFilterQueryRequest 目标将信息传递给 Message Filter Extension。Message Filter Extension 确认该音讯的类别后,将 ILMessageFilterQueryResponse 目标回来给“音讯” APP。

假如 App extension 无法自行做出决议计划,“音讯” APP将会把有关信息发送到与 Message Filter Extension 相关的服务器,并将呼应传递给 Message Filter Extension。Message Filter Extension 解析服务器的呼应并回来终究的 ILMessageFilterQueryResponse 目标,如下图所示。

在 iOS 中使用 IdentifyLookup 进行短信过滤

出于隐私原因,体系会处理与相关的服务器的一切通信;Message Filter Extension 无法直接访问网络,也无法将数据写入运用的共享容器中。

音讯过滤实践

为 APP 新增 Message Filter Extension:

在 iOS 中使用 IdentifyLookup 进行短信过滤
在 iOS 中使用 IdentifyLookup 进行短信过滤

咱们顺次来看 MessageFilterExtension.swift 文件中的代码:

import IdentityLookup
final class MessageFilterExtension: ILMessageFilterExtension {}

ILMessageFilterExtension 是的主要类的抽象基类。在 Info.plist 中被设置 NSExtensionPrincipalClass,将在收到音讯时被构造:

在 iOS 中使用 IdentifyLookup 进行短信过滤

ILMessageFilterExtension 类无其他要求或限制:

open class ILMessageFilterExtension : NSObject {
}

MessageFilterExtension 实现了 ILMessageFilterQueryHandlingILMessageFilterCapabilitiesQueryHandling 协议:

extension MessageFilterExtension: ILMessageFilterQueryHandling, ILMessageFilterCapabilitiesQueryHandling {
    // ...
}

ILMessageFilterQueryHandling

ILMessageFilterExtension 子类必须符合 ILMessageFilterQueryHandling协议,经过包括短信信息的 queryRequest 、供给恳求相关网络服务器才能的 context,来进行短信类别的判断。终究回来供给包括类别信息的 response

@available(iOS 11.0, *)
public protocol ILMessageFilterQueryHandling : NSObjectProtocol {
  // 闭包
  func handle(_ queryRequest: ILMessageFilterQueryRequest, 
        context: ILMessageFilterExtensionContext, 
        completion: @escaping (ILMessageFilterQueryResponse) -> Void)
    // 异步函数
  func handle(_ queryRequest: ILMessageFilterQueryRequest, 
        context: ILMessageFilterExtensionContext
  ) async -> ILMessageFilterQueryResponse
}

queryRequest 的信息如下,包括发件人号码 sender、短信内容 messageBodyISO 国家代码 receiverISOCountryCode

@available(iOS 11.0, *)
open class ILMessageFilterQueryRequest : NSObject, NSSecureCoding {
  open var sender: String? { get }
  open var messageBody: String? { get }
  @available(iOS 16.0, *)
  open var receiverISOCountryCode: String? { get }
}

context 供给恳求相关网络服务器才能,咱们也只能运用该才能访问网络:

@available(iOS 11.0, *)
open class ILMessageFilterExtensionContext : NSExtensionContext {
    // 闭包
  open func deferQueryRequestToNetwork(completion: @escaping (ILNetworkResponse?, Error?) -> Void)
    // 异步函数
  open func deferQueryRequestToNetwork() async throws -> ILNetworkResponse
}

URL 记录在 Info.plistILMessageFilterExtensionNetworkURL 中,无法进行自界说。

在 iOS 中使用 IdentifyLookup 进行短信过滤

response 界说如下,需求供给对应的类别和子类别:

@available(iOS 11.0, *)
open class ILMessageFilterQueryResponse : NSObject, NSSecureCoding {
  open var action: ILMessageFilterAction
  @available(iOS 16.0, *)
  open var subAction: ILMessageFilterSubAction
}

noneallowjunkpromotiontransaction 类别,noneallow 的行为相同:

@available(iOS 11.0, *)
public enum ILMessageFilterAction : Int, @unchecked Sendable {
  case none = 0
  case allow = 1
  case junk = 2
  @available(iOS 14.0, *)
  case promotion = 3
  @available(iOS 14.0, *)
  case transaction = 4
}

以及文章最初提到的 12 种子类别:

@available(iOS 16.0, *)
public enum ILMessageFilterSubAction : Int, @unchecked Sendable {
  case none = 0
  
  /// TRANSACTIONAL SUB-ACTIONS
  
  case transactionalOthers = 10000
  case transactionalFinance = 10001
  case transactionalOrders = 10002
  case transactionalReminders = 10003
  case transactionalHealth = 10004
  case transactionalWeather = 10005
  case transactionalCarrier = 10006
  case transactionalRewards = 10007
  case transactionalPublicServices = 10008
  
  /// PROMOTIONAL SUB-ACTIONS
  
  case promotionalOffers = 20001
  case promotionalCoupons = 20002
}

因而,全体的过滤代码结构如下,顺次进行设备端的检测、服务器检测:

func handle(_ queryRequest: ILMessageFilterQueryRequest,
      context: ILMessageFilterExtensionContext,
      completion: @escaping (ILMessageFilterQueryResponse) -> Void
) {
  // 设备端的检测
  let (offlineAction, offlineSubAction) = self.offlineAction(for: queryRequest)
  switch offlineAction {
  case .allow, .junk, .promotion, .transaction:
    let response = ILMessageFilterQueryResponse()
    response.action = offlineAction
    response.subAction = offlineSubAction
    completion(response)
  case .none:
    // 服务器检测
    context.deferQueryRequestToNetwork() { (networkResponse, error) in
      let response = ILMessageFilterQueryResponse()
      if let networkResponse = networkResponse {
        (response.action, response.subAction) = self.networkAction(for: networkResponse)
      }
      completion(response)
    }
  @unknown default:
    break
  }
}

这儿需求留意,Apple 界说了服务器检测网络恳求的格式,开发者无法进行自界说:

POST /server-endpoint HTTP/1.1
Accept: */*
Content-Type: application/json; charset=utf-8
Content-Length: 148
{
   "_version": 1,
   "query": {
     "sender": "14085550001",
     "message": {
       "text": "This is a message"
     }
   },
   "app": {
     "version": "1.1"
   }
}

ILMessageFilterCapabilitiesQueryHandling

ILMessageFilterCapabilitiesQueryHandling 协议会更简略些:

@available(iOS 16.0, *)
public protocol ILMessageFilterCapabilitiesQueryHandling : NSObjectProtocol {
   // 闭包
  func handle(_ capabilitiesQueryRequest: ILMessageFilterCapabilitiesQueryRequest, 
        context: ILMessageFilterExtensionContext, 
        completion: @escaping (ILMessageFilterCapabilitiesQueryResponse
  ) -> Void)
    // 异步函数
  func handle(_ capabilitiesQueryRequest: ILMessageFilterCapabilitiesQueryRequest, 
        context: ILMessageFilterExtensionContext
  ) async -> ILMessageFilterCapabilitiesQueryResponse
}

其中,capabilitiesQueryRequest 无实践含义,context 同前文。需求供给的是 ILMessageFilterCapabilitiesQueryResponse:

@available(iOS 16.0, *)
open class ILMessageFilterCapabilitiesQueryResponse : NSObject, NSSecureCoding {
}
​
@available(iOS 16.0, *)
@available(macOS, unavailable)
extension ILMessageFilterCapabilitiesQueryResponse {
​
  @nonobjc final public var transactionalSubActions: [ILMessageFilterSubAction]
​
  final public var promotionalSubActions: [ILMessageFilterSubAction]
}

指定了 Message Filter Extension 能够显现的子类别。咱们能够这样展现以显现子类别:

func handle(_ capabilitiesQueryRequest: ILMessageFilterCapabilitiesQueryRequest,
      context: ILMessageFilterExtensionContext,
      completion: @escaping (ILMessageFilterCapabilitiesQueryResponse) -> Void
) {
  let response = ILMessageFilterCapabilitiesQueryResponse()
  response.transactionalSubActions = [    .transactionalOthers,    .transactionalFinance,    .transactionalOrders,    .transactionalReminders,    .transactionalHealth,    .transactionalWeather,    .transactionalCarrier,    .transactionalRewards,    .transactionalPublicServices  ]
  response.promotionalSubActions = [        .promotionalOthers,    .promotionalOffers,    .promotionalCoupons,  ]
  completion(response)
}

在 iOS16 设备上,不同装备款式如下:

在 iOS 中使用 IdentifyLookup 进行短信过滤
在 iOS 中使用 IdentifyLookup 进行短信过滤
在 iOS 中使用 IdentifyLookup 进行短信过滤
未装备短信过滤 装备短信过滤,无子类别 装备短信过滤,展现一切子类别

废物短信和废物电话上报

此外,咱们能够一个 App Extension,让用户将不需求的短信和电话上报为废物内容。上报电话需求用户在最近列表中进行左滑后选择陈述。关于在音讯记录中的短信,用户能够按下陈述废物信息按钮:

在 iOS 中使用 IdentifyLookup 进行短信过滤
在 iOS 中使用 IdentifyLookup 进行短信过滤
在 iOS 中使用 IdentifyLookup 进行短信过滤

创立 Unwanted Communication Reporting Extension:

在 iOS 中使用 IdentifyLookup 进行短信过滤
在 iOS 中使用 IdentifyLookup 进行短信过滤

咱们能够看到模版代码十分简略:

class UnwantedCommunicationReportingExtension: ILClassificationUIExtensionViewController {
  
  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Notify the system when you have completed gathering information
    // from the user and you are ready with a classification response
    self.extensionContext.isReadyForClassificationResponse = true
  }
  
  // Customize UI based on the classification request before the view is loaded
  override func prepare(for classificationRequest: ILClassificationRequest) {
    // Configure your views for the classification request
  }
  
  // Provide a classification response for the classification request
  override func classificationResponse(for request:ILClassificationRequest) -> ILClassificationResponse {
    return ILClassificationResponse(action: .reportJunk)
  }
}

当用户上报时,体系会启动 App Extension。收集用野外信息后,进行后续的上报或阻挠,如下图所示。

在 iOS 中使用 IdentifyLookup 进行短信过滤

具体来说,体系会顺次:

  1. 实例化 App Extension 中的 ILClassificationUIExtensionViewController 子类。
  2. 调用实例的 prepare(for:)`办法并将控制器出现给用户。
  1. 运用实例从用户那里收集数据,收集完结 isReadyForClassificationResponse 设置为 true
  2. 假如用户按下取消按钮,体系将封闭 ILClassificationUIExtensionViewController 子类实例。

在 iOS 中使用 IdentifyLookup 进行短信过滤

  1. 假如用户按下完结,体系将调用 classificationResponse(for:) 办法,传入一个 ILClassificationRequest 目标。

在 iOS 中使用 IdentifyLookup 进行短信过滤

  1. 体系依据办法的 ILClassificationResponse 呼应采取不同的操作。
@available(iOS 12.0, *)
open class ILClassificationResponse : NSObject, NSSecureCoding {
  open var action: ILClassificationAction { get }
  @available(iOS 12.1, *)
  open var userString: String?
  open var userInfo: [String : Any]?
  public init(action: ILClassificationAction)
}

ILClassificationAction 类型为:

/// Describes various classification actions.
@available(iOS 12.0, *)
public enum ILClassificationAction : Int, @unchecked Sendable {
  /// Indicate that no action is requested.
  case none = 0
  /// Report communication(s) as not junk.
  case reportNotJunk = 1
  /// Report communication(s) as junk.
  case reportJunk = 2
  /// Report communication(s) as junk and block the sender.
  case reportJunkAndBlockSender = 3
}

关于 ILClassificationAction.none,体系会封闭视图控制器,但不会采取任何其他操作。

关于 ILClassificationAction.reportNotJunkILClassificationAction.reportJunk,体系会依据 userInfo 特点生成陈述,然后将其发布到扩展程序的 Info.plist 文件中指定的服务端(ILClassificationExtensionNetworkReportDestination)或者运用短信发到对应的号码(ILClassificationExtensionSMSReportDestination)。

在 iOS 中使用 IdentifyLookup 进行短信过滤

关于 ILClassificationAction.reportJunkAndBlockSender,体系的呼应就像在 ILClassificationAction.reportJunk 操作中一样。 但是,在陈述步骤之后,体系会宣布提示,让用户知道该号码将被阻挠(拉黑)。

最后,为了维护用户隐私,体系会在 App Extension 停止后删去该容器。有关详细信息,请参阅关于 iOS 文件体系。

参考资料

  • SMS and Call Reporting
  • SMS and MMS Message Filtering
  • SMS and Call Reporting
  • Filtering Unwanted Messages with Identity Lookup
  • Explore SMS message filters

基于短信过滤才能。上线了喵喵消烦员 App:

喵喵消烦员是一款短信过滤工具软件。在现在信息爆炸的时代,您的隐私和安全由喵喵来守护!咱们运用 scikit-learn,经过朴素贝叶斯算法对废物短信进行识别,经过 Core ML 将模型布置在本地,然后完结离线过滤使命。

  1. 隐私安全:咱们不会索要任何位置、相机、告诉、无线数据(网络)等权限,用户的数据不会被保存,同时也不可能被上传,一切操作均在本地完结。
  2. 高效精准:经过先进的算法技能,自动识别并过滤掉废物短信、诈骗信息、广告推销等不必要打扰的内容。
  3. 自界说设置:支持自界说关键词过滤,方便您针对个人需求进行个性化设置,避免不必要的搅扰。
  4. 更新迭代:咱们会经过版本晋级定时更新过滤模型,保证始终能够准确地识别和阻拦最新的废物短信和诈骗信息。

模型、App 还在不断调整和优化,欢迎运用和供给建议!

在 iOS 中使用 IdentifyLookup 进行短信过滤