diff --git a/plugin/sms-otp/src/main/java/org/fcitx/fcitx5/android/plugin/smsotp/MainService.kt b/plugin/sms-otp/src/main/java/org/fcitx/fcitx5/android/plugin/smsotp/MainService.kt index 87004201..1a2ebf4b 100644 --- a/plugin/sms-otp/src/main/java/org/fcitx/fcitx5/android/plugin/smsotp/MainService.kt +++ b/plugin/sms-otp/src/main/java/org/fcitx/fcitx5/android/plugin/smsotp/MainService.kt @@ -5,22 +5,45 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Build -import android.os.Handler -import android.os.Looper import android.util.Log -import android.widget.Toast import java.util.regex.Pattern import org.fcitx.fcitx5.android.common.FcitxPluginService import org.fcitx.fcitx5.android.common.ipc.FcitxRemoteConnection import org.fcitx.fcitx5.android.common.ipc.bindFcitxRemoteService fun extractOtp(message: String?): String? { - if (message == null) return null - val pattern = Pattern.compile( - "(?() + val codePositions = mutableListOf>() + while (matcher.find()) { + val code = matcher.group(2).replace("-", "") + results.add(code) + codePositions.add(Pair(code, matcher.start(2))) + } + if (results.size > 1) { + val keywords = listOf("code", "otp", "码", "碼", "コード", "코드", "код", "kodo") + val keywordPositions = keywords.flatMap { keyword -> + Regex(Regex.escape(keyword), RegexOption.IGNORE_CASE) + .findAll(message) + .map { it.range.first } + .toList() + } + if (keywordPositions.isNotEmpty()) { + // find the nearest one + val nearest = codePositions.minByOrNull { (code, pos) -> + keywordPositions.minOf { kp -> kotlin.math.abs(pos - kp) } + } + return nearest?.first + } else { + return results.firstOrNull() + } + } + else { + return results.firstOrNull() + } } class MainService : FcitxPluginService() { @@ -30,7 +53,7 @@ class MainService : FcitxPluginService() { override fun start() { registerSmsReceiver(this) connection = - bindFcitxRemoteService(BuildConfig.MAIN_APPLICATION_ID) { log("Bind to fcitx remote") } + bindFcitxRemoteService(BuildConfig.MAIN_APPLICATION_ID) { log("Bind to fcitx remote") } } override fun stop() { @@ -47,19 +70,19 @@ class MainService : FcitxPluginService() { private fun registerSmsReceiver(context: Context) { if (receiver != null) return receiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val messages = android.provider.Telephony.Sms.Intents.getMessagesFromIntent(intent) - for (sms in messages) { - val message = sms.messageBody - val code = extractOtp(message) - if (code != null) { - // showToast(context, "OTP Detected: $code") - tryInputOtp(context, code) - } - } - } + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val messages = android.provider.Telephony.Sms.Intents.getMessagesFromIntent(intent) + for (sms in messages) { + val message = sms.messageBody + val code = extractOtp(message) + if (code != null) { + log("OTP Detected: $code") + tryInputOtp(context, code) } + } + } + } val filter = IntentFilter("android.provider.Telephony.SMS_RECEIVED") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED) @@ -75,10 +98,6 @@ class MainService : FcitxPluginService() { } } - private fun showToast(context: Context, text: String) { - Handler(Looper.getMainLooper()).post { Toast.makeText(context, text, Toast.LENGTH_LONG).show() } - } - private fun tryInputOtp(context: Context, code: String) { try { connection.remoteService?.updateOtp(code) diff --git a/plugin/sms-otp/src/test/java/org/fcitx/fcitx5/android/plugin/smsotp/SmsFormatTest.kt b/plugin/sms-otp/src/test/java/org/fcitx/fcitx5/android/plugin/smsotp/SmsFormatTest.kt index b3b9eedb..e847f445 100644 --- a/plugin/sms-otp/src/test/java/org/fcitx/fcitx5/android/plugin/smsotp/SmsFormatTest.kt +++ b/plugin/sms-otp/src/test/java/org/fcitx/fcitx5/android/plugin/smsotp/SmsFormatTest.kt @@ -42,8 +42,14 @@ class SmSFormatTest { assertEquals("050475", extractOtp("Ваш код перевірки Poe: 050475. Не повідомляйте цей код іншим.")) assertEquals("429309", extractOtp("[抖音] 429309 is your verification code, valid for 5 minutes.")) assertEquals("8650", extractOtp("<#> 8650 is your Venmo phone verification code.")) + assertEquals("060973", extractOtp("12306用户注册或既有用户手机核验专用验证码:060973。如非本人直接访问12306,请停止操作,切勿将验证码提供给第三方。【铁路客服】")) + assertEquals("505513", extractOtp("您在付款,为防诈骗千万不要告诉他人验证码505513,商户为汇付天下,金额80元。如有疑问请停止操作。(短信编号:245747)【工商银行】")) + assertEquals("370000", extractOtp("任何向你索要验证码的都是骗子,千万别给!您正在向www(尾号4832)转账,验证码370000,100元。")) + assertEquals("927711", extractOtp("TAN to confirm mandate for Segpay UK Test Please enter TAN 927711 to set up your mandate 10751532bd3573241262f56b4e76a003 securely.")) + assertEquals("121093", extractOtp("Twój kod do Tindera to 121093 Nie udostępniaj @tinder.com #121093")) assertNull(extractOtp("OKX:USDTsuccessfully withdrawn: 132,619.89 【okxcash.com】Account: DTm888 Key: Swksf367")) assertNull(extractOtp("You can also tap on this link to verify your phone: v.whatsapp.com/696948")) assertNull(extractOtp("https://www.photosfromyourevent.com/4669/wauem8/")) } + }