短信過(guò)濾 APP 開發(fā)
一直想開發(fā)一個(gè)自己的短信過(guò)濾 APP,但是一直沒有具體實(shí)施,現(xiàn)在終于靜下心來(lái),邊開發(fā)邊記錄下整體的開發(fā)過(guò)程。
01垃圾短信樣本
遇到的第一個(gè)問題是,既然要過(guò)濾垃圾短信,那首先要識(shí)別哪些是垃圾短信?如何識(shí)別呢?
參考之前訓(xùn)練識(shí)別鋼管計(jì)數(shù)的經(jīng)驗(yàn),決定通過(guò) CoreML 訓(xùn)練 Text 模型來(lái)識(shí)別,那問題來(lái)了,要訓(xùn)練模型的短信數(shù)據(jù)集怎么來(lái)?
一開始打算網(wǎng)上找到垃圾短信樣本,但找了好久沒找到,于是就想到用自己和家人手機(jī)里的短信,畢竟手機(jī)里短信一般不刪除,也有小幾千條,而且垃圾短信、推銷、廣告之類的應(yīng)有盡有。
所以問題就變成了,如何導(dǎo)出 iPhone 短信?
這里筆者也查了好久,找到的第三方軟件基本都是需要收費(fèi),最終發(fā)現(xiàn)了一個(gè)免費(fèi)導(dǎo)出的方案。
首先不加密備份手機(jī)到電腦,如下圖,選中Back up all the data on your iPhone to this Mac,點(diǎn)擊Back Up Now,等待備份完成,備份完成后,再點(diǎn)擊Manage Backups :
備份界面
Manage Backups 點(diǎn)擊后,界面如下,可以看到已備份的記錄,右鍵選擇 Show In Finder,在文件夾中打開:
管理備份
然后可以看到備份所在目錄已打開,這時(shí)候需要找到文件名為 3d0d7e5fb2ce288813306e4d4636395e047a3d28的文件,這個(gè)文件就是短信備份的數(shù)據(jù)庫(kù)文件。然后問題來(lái)了,怎么找呢?看到備份目錄一個(gè)個(gè)文件夾是不是懵,這怎么找,很簡(jiǎn)單,搜索,點(diǎn)擊右上角的搜索,直接把這個(gè)文件名輸入即可,注意搜索的范圍是當(dāng)前文件夾:
備份文件夾
搜索結(jié)果如下:
短信備份數(shù)據(jù)庫(kù)文件
然后把這個(gè)文件單獨(dú)拷貝到另一個(gè)地方,比如桌面,再用數(shù)據(jù)庫(kù)軟件打開,比如SQLPro for SQLLite,打開如下:
短信數(shù)據(jù)庫(kù)文件打開
然后觀察這個(gè)文件后發(fā)現(xiàn),手機(jī)號(hào)和短信記錄分布在不同表中,需要寫一個(gè) SQL 查出需要的內(nèi)容,SQL 內(nèi)容如下,參考 SQL to extract messages from backup,選中上圖中Query,輸入命令如下:
SELECT datetime(message.date, 'unixepoch', '+31 years', '-6 hours') as Timestamp, handle.id, message.text,
    case when message.is_from_me then 'From me' else 'To me' end as Sender
FROM message, handle WHERE message.handle_id = handle.ROWID AND message.text NOT NULL;然后點(diǎn)擊右上角的執(zhí)行, 可以看到,把短信都篩選出來(lái)了:
然后選中所有 row,右鍵選擇Export result set as 導(dǎo)出CSV,即可導(dǎo)出 excel 格式的文件:
導(dǎo)出短信
這樣就獲取到了所需的短信樣本。
02垃圾短信訓(xùn)練識(shí)別
有了樣本之后,再來(lái)看如何訓(xùn)練識(shí)別,打算使用蘋果的 CoreML識(shí)別,那么如何使用?樣本格式的要求是什么樣?訓(xùn)練需要多久?
先來(lái)看,創(chuàng)建一個(gè)文字訓(xùn)練的CoreML工程,選中 Xcode,點(diǎn)擊Open Developer Tool,選中 CoreML 打開,如下圖:
XcodeDevelperTool
然后選擇文件夾,并點(diǎn)擊新建New Document, 如下:
New Document
然后選中 Text Classification,如下圖:
Text Classification
接著輸入項(xiàng)目的名字和描述:
項(xiàng)目描述
點(diǎn)擊右下角創(chuàng)建,進(jìn)入主界面,如下:
主界面
點(diǎn)擊 Traing Data 的詳細(xì)說(shuō)明,可以看到 CoreML 要求的文字識(shí)別的格式,支持 JSON 和 CSV 文件,格式如下:
格式
JSON 格式如下:
// JSON file
[
    {
        "text": "The movie was fantastic!",
        "label": "positive"
    }, {
        "text": "Very boring. Fell asleep.",
        "label": "negative"
    }, {
        "text": "It was just OK.",
        "label": "neutral"
    } ...
]而 CSV 格式則是,一列 text ,一列 label,
text  | label  | 
這是一條普通短信  | label1  | 
這是一條垃圾短信  | label2  | 
由于再前一步中,已經(jīng)將短信導(dǎo)出為 CSV 格式,所以這里就需要把格式改為上圖中格式即可,只剩下一個(gè)問題需要解決,即:label 有哪些取值?
要看 label 有哪些取值,需要先看系統(tǒng)短信的過(guò)濾邏輯是什么樣?支持的過(guò)濾分類有哪些?否則自己想實(shí)現(xiàn)的分類,分組好了,最后發(fā)現(xiàn)系統(tǒng)不支持就尷尬了。
03短信過(guò)濾分類
系統(tǒng)短信的過(guò)濾邏輯
參考 SMS and MMS Message Filtering,可以看到,開發(fā)者是沒有權(quán)限創(chuàng)建新分組的,只能是針對(duì)收到未知聯(lián)系人的 SMS 或者 MMS ,攔截返回指定的分類。
這里需要注意的是,根據(jù)文檔的說(shuō)法,短信過(guò)濾不支持 iMessage 和通訊錄中聯(lián)系人短信的過(guò)濾,僅支持未知聯(lián)系人的 SMS 和 MMS。
短信過(guò)濾,又分為本地判斷過(guò)濾和服務(wù)端判斷過(guò)濾,示意圖如下:
短信本地過(guò)濾
短信服務(wù)端過(guò)濾
按照文檔的說(shuō)法,即使是服務(wù)端過(guò)濾,APP 也是不能直接訪問網(wǎng)絡(luò)的,系統(tǒng)會(huì)和設(shè)置的服務(wù)器交互;而且 App Extension 不能通過(guò)共享 Group 寫數(shù)據(jù),故而短信僅能在 App Extension中獲取到,不能存儲(chǔ),不能上傳,從而保證隱私和安全。關(guān)于服務(wù)端過(guò)濾更多的實(shí)現(xiàn),可以參考 Creating a Message Filter App Extension。
再來(lái)看支持的過(guò)濾類型,ILMessageFilterAction大分類支持五種:
none 沒有足夠信息,不能判斷,會(huì)展示信息,或進(jìn)一步請(qǐng)求服務(wù)端判斷過(guò)濾
allow 正常展示信息
junk 阻止正常展示信息,顯示在垃圾短信分類下
promotion 阻止正常展示信息,顯示在推送信息分類下
transation 阻止正常展示信息,顯示在交易信息分類下
而其中又可以細(xì)分子分類,ILMessageFilterSubAction,具體含義可以參考ILMessageFilterSubAction:
- none
 - promotion 支持的子分類有
 
others
offers
coupons
- transation 支持的子分類有
 - others
 - finance
 - orders
 - reminders
 - health
 - weather
 - carrier
 - rewards
 - publicServices
 
這里僅針對(duì)大分類做處理,具體的子分類不做詳細(xì)過(guò)濾,所以需要訓(xùn)練的 label 有哪些取值就很明確了,過(guò)濾垃圾短信、推廣信息、交易信息,至于 none 和 allow 不做區(qū)分,統(tǒng)一處理為 allow,所以總共需要訓(xùn)練的 label 取值有以下這些:
- allow
 - junk
 - promotion
 - transation
 
然后就是針對(duì)導(dǎo)出短信的 CSV 文件,針對(duì)每條短信,添加對(duì)應(yīng)的 label,這里只能手工,樣本的大小和 label 定義決定后續(xù)識(shí)別的準(zhǔn)確度,同時(shí)為了后續(xù)子分類的實(shí)現(xiàn),建議實(shí)事求是,不要把比如 promotion 里的分到 junk 里。。。
每條短信樣本都標(biāo)記好了之后,就可以導(dǎo)入Create ML來(lái)訓(xùn)練,生成需要的模型,步驟如下——
首先導(dǎo)入數(shù)據(jù)集:
導(dǎo)入數(shù)據(jù)集
然后點(diǎn)擊左上角的 Train:
訓(xùn)練Train
等訓(xùn)練好了之后,可以點(diǎn)擊 Preview ,模擬短信文本,看輸出的預(yù)測(cè),如下圖:
效果檢測(cè)
最后,導(dǎo)出模型,供 APP 使用:
導(dǎo)出模型
04APP開發(fā)
新建項(xiàng)目,然后使用 "new bing生成圖片" 來(lái)設(shè)計(jì) APPIcon,再用 ChatGPT-4,來(lái)生成 APP 名字。然后添加 Message Filter Extension Target,如下圖:
Message Filter Extension Target
在 MessageFilterExtension.swift 中,能看到蘋果已經(jīng)幫忙實(shí)現(xiàn)了基本的框架,只需要在框架對(duì)應(yīng)// TODO: 的地方,加入對(duì)應(yīng)的過(guò)濾邏輯即可。
然后導(dǎo)入訓(xùn)練結(jié)果集到項(xiàng)目中,注意 Target 要勾選主工程和 Message Filter Extension 的 Target,因?yàn)樾枰谶@個(gè) Target 中使用模型來(lái)實(shí)現(xiàn)過(guò)濾。
具體使用如下:
import Foundation
import IdentityLookup
import CoreML
import IdentityLookup
enum SMSFilterActionType: String {
    case transation
    case promotion
    case allow
    case junk
    
    func formatFilterAction() -> ILMessageFilterAction {
        switch self {
        case .transation:
            return ILMessageFilterAction.transaction
        case .promotion:
            return ILMessageFilterAction.promotion
        case .allow:
            return ILMessageFilterAction.allow
        case .junk:
            return ILMessageFilterAction.junk
        }
    }
}
struct SMSFilterUtil {
    static func filter(with messageBody: String) -> ILMessageFilterAction {
        var filterAction: ILMessageFilterAction = .none
        let configuration = MLModelConfiguration()
        do {
            let model = try SmsClassifier(configuration: configuration)
            let resultLabel = try model.prediction(text: messageBody).label
            if let resultFilterAction = SMSFilterActionType(rawValue: resultLabel)?.formatFilterAction() {
                filterAction = resultFilterAction
            }
        } catch {
            print(error)
        }
        return filterAction
    }
}然后在MessageFilterExtension.Swift 中 offlineAction(for queryRequest: ILMessageFilterQueryRequest) -> (ILMessageFilterAction, ILMessageFilterSubAction) 方法調(diào)用,如下:
@available(iOSApplicationExtension 16.0, *)
  private func offlineAction(for queryRequest: ILMessageFilterQueryRequest) -> (ILMessageFilterAction, ILMessageFilterSubAction) {
      guard let messageBody = queryRequest.messageBody else {
          return (.none, .none)
      }
      let action = MWSMSFilterUtil.filter(with: messageBody)
      return (action, .none)
  }這里需要注意下 APP 最低版本設(shè)置,ILMessageFilterSubAction只有 iOS 16 以上的手機(jī)才支持,而 ILMessageFilterSubAction 則是 iOS 14 以上。
如果想實(shí)現(xiàn)更精細(xì)的 SubAction 的過(guò)濾,則上面短信數(shù)據(jù)集的 label需要改為更精細(xì)的 label,然后訓(xùn)練出模型,再用來(lái)判斷。
另外,ILMessageFilterQueryRequest 中可以獲取到 sender 和 messageBody,所以如果想實(shí)現(xiàn)自定義規(guī)則,比如針對(duì)某個(gè)手機(jī)號(hào)設(shè)置對(duì)應(yīng)的規(guī)則,則需要從 APP 中設(shè)置對(duì)應(yīng)的規(guī)則,然后通過(guò) Group 共享到 Extension ,然后在上面的方法里通過(guò)規(guī)則匹配。
05總結(jié)
相信通過(guò)上面的步驟,大家都能開發(fā)出自己的短信過(guò)濾 APP。
上面的步驟是通過(guò)固定的訓(xùn)練模型來(lái)匹配的邏輯,步驟是:
- 獲取短信數(shù)據(jù)集
 - 通過(guò) CoreML 使用數(shù)據(jù)集訓(xùn)練并生成模型
 - 在項(xiàng)目中使用模型,進(jìn)行判斷
 
這種方式生成的模型其數(shù)據(jù)固定,每次更新模型需要重新訓(xùn)練并導(dǎo)入,然后更新 APP。是否有更好的方式呢?比如是否可以在 APP 中邊訓(xùn)練邊更新?又或者是否可以通過(guò)本地規(guī)則加本地模型加網(wǎng)絡(luò)模型這種方式?
假設(shè)方案一:
首先,在 APP 中邊訓(xùn)練邊更新,大概思路如下——
更新模型,需要知道一條數(shù)據(jù)的內(nèi)容和數(shù)據(jù)的分類,所以如果要在 APP 中訓(xùn)練模型,就需要通過(guò)另外的辦法獲取到分類,要不然用模型得到分類再回過(guò)頭來(lái)訓(xùn)練模型,意義不大。所以通過(guò)自定義規(guī)則獲取到數(shù)據(jù)分類,然后用數(shù)據(jù)和數(shù)據(jù)分類來(lái)更新模型,這種方式應(yīng)該是可行的。
假設(shè)方案二:
然后來(lái)考慮更完善的一種方式,即通過(guò)本地規(guī)則加本地模型加網(wǎng)絡(luò)模型的方式:
邏輯是首先通過(guò)本地規(guī)則匹配,如果本地規(guī)則匹配不到,則繼續(xù)使用本地模型匹配,如果本地模型也匹配不到,則通過(guò)請(qǐng)求服務(wù)端,服務(wù)端另有一套不斷訓(xùn)練更新的模型,來(lái)獲取對(duì)應(yīng)的分類,最后每次更新時(shí)把服務(wù)端當(dāng)前對(duì)應(yīng)最新的模型更新到項(xiàng)目中。
假設(shè)方案三:
方案二需要通過(guò)網(wǎng)絡(luò)模型,假設(shè)的前提是服務(wù)端有一套不斷訓(xùn)練更新的模型,那如果這個(gè)假設(shè)不存在?只有本地規(guī)則和本地模型,外加偶爾獲取到的更新數(shù)據(jù)集,是否有辦法在線更新本地模型?
目前本地模型是直接添加到APP 主 Bundle 中,可以考慮在首次啟動(dòng)時(shí)拷貝到 APP 和Extension 的共享 Group 中,每次打開 APP 時(shí),判斷模型是否有更新,有更新則下載替換這個(gè)目錄下的模型文件。在 Extension 中,通過(guò) URL 獲取這個(gè)目錄下的模型文件來(lái)進(jìn)行過(guò)濾。
幾種方案流程圖如下:
短信 APP 過(guò)濾流程圖
總結(jié)如下:
短信過(guò)濾 APP 開發(fā)流程
參考:
- SQL to extract messages from backup
 - https://apple.stackexchange.com/questions/300866/sql-to-extract-messages-from-backup)
 - Creating a Text Classifier Model
 - https://developer.apple.com/documentation/createml/creating-a-text-classifier-model
 - SMS and MMS Message Filtering
 - https://developer.apple.com/documentation/sms_and_call_reporting/sms_and_mms_message_filtering
 - Creating a Message Filter App Extension
 - https://developer.apple.com/documentation/sms_and_call_reporting/sms_and_mms_message_filtering/creating_a_message_filter_app_extension
 - ILMessageFilterAction
 - https://developer.apple.com/documentation/sms_and_call_reporting/ilmessagefilteraction
 
本文轉(zhuǎn)載自微信公眾號(hào)「搜狐技術(shù)產(chǎn)品」,作者「王德亮」,可以通過(guò)以下二維碼關(guān)注。

轉(zhuǎn)載本文請(qǐng)聯(lián)系「搜狐技術(shù)產(chǎn)品」公眾號(hào)。















 
 
 








 
 
 
 