diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt index 935adecc..537602d0 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt @@ -17,7 +17,7 @@ import com.idormy.sms.forwarder.utils.task.TaskUtils import com.xuexiang.xutil.resource.ResUtils.getString import java.io.Serializable import java.text.SimpleDateFormat -import java.util.* +import java.util.Date @Suppress("unused") data class MsgInfo( @@ -32,9 +32,6 @@ data class MsgInfo( var uid: Int = 0, //APP通知的UID ) : Serializable { - private val titleForSend: String - get() = getTitleForSend("", "") - //通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出 private val callTypeMap = mapOf( //"0" to getString(R.string.unknown_call), @@ -46,53 +43,18 @@ data class MsgInfo( "6" to getString(R.string.outgoing_call_started), ) - fun getTitleForSend(titleTemplate: String): String { - return getTitleForSend(titleTemplate, "") - } + val titleForSend = getTitleForSend() - @SuppressLint("SimpleDateFormat") - fun getTitleForSend(titleTemplate: String, regexReplace: String): String { + val smsVoForSend = getContentForSend() + + fun getTitleForSend(titleTemplate: String = "", regexReplace: String = ""): String { var template = titleTemplate.replace("null", "") if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from) - val deviceMark = extraDeviceMark.trim() - val versionName = AppUtils.getAppVersionName() - val splitSimInfo = simInfo.split("#####") - val title = splitSimInfo.getOrElse(0) { simInfo } - val scheme = splitSimInfo.getOrElse(1) { "" } - val titleForSend: String = template.replace(getString(R.string.tag_from), from) - .replace(getString(R.string.tag_package_name), from) - .replace(getString(R.string.tag_sms), content) - .replace(getString(R.string.tag_msg), content) - .replace(getString(R.string.tag_card_slot), title) - .replace(getString(R.string.tag_card_subid), subId.toString()) - .replace(getString(R.string.tag_title), title) - .replace(getString(R.string.tag_scheme), scheme) - .replace(getString(R.string.tag_uid), uid.toString()) - .replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)) - .replace(getString(R.string.tag_current_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())) - .replace(getString(R.string.tag_device_name), deviceMark) - .replace(getString(R.string.tag_app_version), versionName) - .replace(getString(R.string.tag_call_type), callTypeMap[callType.toString()] ?: getString(R.string.unknown_call)) - .replace(getString(R.string.tag_ipv4), TaskUtils.ipv4) - .replace(getString(R.string.tag_ipv6), TaskUtils.ipv6) - .replace(getString(R.string.tag_battery_pct), "%.2f".format(TaskUtils.batteryPct)) - .replace(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus)) - .replace(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged)) - .replace(getString(R.string.tag_battery_info), TaskUtils.batteryInfo) - .trim() - return replaceLocationTag(replaceAppName(regexReplace(titleForSend, regexReplace), from)) + + return replaceTemplate(template, regexReplace) } - val smsVoForSend: String - get() = getContentForSend("", "") - - fun getContentForSend(ruleSmsTemplate: String): String { - return getContentForSend(ruleSmsTemplate, "") - } - - @SuppressLint("SimpleDateFormat") - fun getContentForSend(ruleSmsTemplate: String, regexReplace: String): String { - val deviceMark = extraDeviceMark.trim() + fun getContentForSend(ruleSmsTemplate: String = "", regexReplace: String = ""): String { var customSmsTemplate: String = getString(R.string.tag_from).toString() + "\n" + getString(R.string.tag_sms) + "\n" + getString(R.string.tag_card_slot) + "\n" + @@ -110,76 +72,52 @@ data class MsgInfo( customSmsTemplate = smsTemplate.replace("null", "") } } - val versionName = AppUtils.getAppVersionName() - val splitSimInfo = simInfo.split("#####") - val title = splitSimInfo.getOrElse(0) { simInfo } - val scheme = splitSimInfo.getOrElse(1) { "" } - val smsVoForSend: String = customSmsTemplate.replace(getString(R.string.tag_from), from) - .replace(getString(R.string.tag_package_name), from) - .replace(getString(R.string.tag_sms), content) - .replace(getString(R.string.tag_msg), content) - .replace(getString(R.string.tag_card_slot), title) - .replace(getString(R.string.tag_card_subid), subId.toString()) - .replace(getString(R.string.tag_title), title) - .replace(getString(R.string.tag_scheme), scheme) - .replace(getString(R.string.tag_uid), uid.toString()) - .replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)) - .replace(getString(R.string.tag_current_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())) - .replace(getString(R.string.tag_device_name), deviceMark) - .replace(getString(R.string.tag_app_version), versionName) - .replace(getString(R.string.tag_call_type), callTypeMap[callType.toString()] ?: getString(R.string.unknown_call)) - .replace(getString(R.string.tag_ipv4), TaskUtils.ipv4) - .replace(getString(R.string.tag_ipv6), TaskUtils.ipv6) - .replace(getString(R.string.tag_battery_pct), "%.2f".format(TaskUtils.batteryPct)) - .replace(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus)) - .replace(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged)) - .replace(getString(R.string.tag_battery_info), TaskUtils.batteryInfo) - .trim() - return replaceLocationTag(replaceAppName(regexReplace(smsVoForSend, regexReplace), from)) + + return replaceTemplate(customSmsTemplate, regexReplace) } - @SuppressLint("SimpleDateFormat") fun getContentFromJson(jsonTemplate: String): String { var template = jsonTemplate.replace("null", "") if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from) - val deviceMark = extraDeviceMark.trim() - val versionName = AppUtils.getAppVersionName() - val splitSimInfo = simInfo.split("#####") - var title = splitSimInfo.getOrElse(0) { simInfo } - title = jsonInnerStr(title) - var scheme = splitSimInfo.getOrElse(1) { "" } - scheme = jsonInnerStr(scheme) - from = jsonInnerStr(from) - content = jsonInnerStr(content) + return replaceTemplate(template, "", true) + } - val msgForSend: String = template.replace(getString(R.string.tag_from), from) - .replace(getString(R.string.tag_package_name), from) - .replace(getString(R.string.tag_sms), content) - .replace(getString(R.string.tag_msg), content) - .replace(getString(R.string.tag_card_slot), title) - .replace(getString(R.string.tag_card_subid), subId.toString()) - .replace(getString(R.string.tag_title), title) - .replace(getString(R.string.tag_scheme), scheme) - .replace(getString(R.string.tag_uid), uid.toString()) - .replace(getString(R.string.tag_receive_time), jsonInnerStr(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date))) - .replace(getString(R.string.tag_current_time), jsonInnerStr(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()))) - .replace(getString(R.string.tag_device_name), jsonInnerStr(deviceMark)) - .replace(getString(R.string.tag_app_version), jsonInnerStr(versionName)) - .replace(getString(R.string.tag_call_type), jsonInnerStr(callTypeMap[callType.toString()] ?: getString(R.string.unknown_call))) - .replace(getString(R.string.tag_ipv4), jsonInnerStr(TaskUtils.ipv4)) - .replace(getString(R.string.tag_ipv6), jsonInnerStr(TaskUtils.ipv6)) - .replace(getString(R.string.tag_battery_pct), jsonInnerStr("%.2f".format(TaskUtils.batteryPct))) - .replace(getString(R.string.tag_battery_status), jsonInnerStr(BatteryUtils.getStatus(TaskUtils.batteryStatus))) - .replace(getString(R.string.tag_battery_plugged), jsonInnerStr(BatteryUtils.getPlugged(TaskUtils.batteryPlugged))) - .replace(getString(R.string.tag_battery_info), jsonInnerStr(TaskUtils.batteryInfo)) + @SuppressLint("SimpleDateFormat") + fun replaceTemplate(template: String, regexReplace: String, needJson: Boolean = false): String { + val splitSimInfo = simInfo.split("#####") + val title = splitSimInfo.getOrElse(0) { simInfo } + val scheme = splitSimInfo.getOrElse(1) { "" } + + return template.replaceTag(getString(R.string.tag_from), from, needJson) + .replaceTag(getString(R.string.tag_package_name), from, needJson) + .replaceTag(getString(R.string.tag_sms), content, needJson) + .replaceTag(getString(R.string.tag_msg), content, needJson) + .replaceTag(getString(R.string.tag_card_slot), title, needJson) + .replaceTag(getString(R.string.tag_card_subid), subId.toString(), needJson) + .replaceTag(getString(R.string.tag_title), title, needJson) + .replaceTag(getString(R.string.tag_scheme), scheme, needJson) + .replaceTag(getString(R.string.tag_uid), uid.toString(), needJson) + .replaceTag(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date), needJson) + .replaceTag(getString(R.string.tag_current_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()), needJson) + .replaceTag(getString(R.string.tag_device_name), extraDeviceMark.trim(), needJson) + .replaceTag(getString(R.string.tag_app_version), AppUtils.getAppVersionName(), needJson) + .replaceTag(getString(R.string.tag_call_type), callTypeMap[callType.toString()] ?: getString(R.string.unknown_call), needJson) + .replaceTag(getString(R.string.tag_ipv4), TaskUtils.ipv4, needJson) + .replaceTag(getString(R.string.tag_ipv6), TaskUtils.ipv6, needJson) + .replaceTag(getString(R.string.tag_battery_pct), "%.2f".format(TaskUtils.batteryPct), needJson) + .replaceTag(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus), needJson) + .replaceTag(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged), needJson) + .replaceTag(getString(R.string.tag_battery_info), TaskUtils.batteryInfo, needJson) + .replaceAppNameTag(from, needJson) + .replaceLocationTag(needJson) + .regexReplace(regexReplace) .trim() - return replaceLocationTag(replaceAppName(msgForSend, from, true), true) } //正则替换内容 - private fun regexReplace(content: String, regexReplace: String): String { - return if (TextUtils.isEmpty(regexReplace)) content else try { - var newContent = content + private fun String.regexReplace(regexReplace: String): String { + return if (TextUtils.isEmpty(regexReplace)) this else try { + var newContent = this val lineArray = regexReplace.split("\\n".toRegex()).toTypedArray() for (line in lineArray) { val lineSplit = line.split("===".toRegex()).toTypedArray() @@ -192,14 +130,44 @@ data class MsgInfo( newContent } catch (e: Exception) { Log.e("RegexReplace", "Failed to get the receiving phone number:" + e.message) - content + this } } + //替换标签(支持正则替换) + private fun String.replaceTag(tag: String, info: String, needJson: Boolean = false, ignoreCase: Boolean = true): String { + var result = if (needJson) { + this.replace(tag, toJsonStr(info), ignoreCase) + } else { + this.replace(tag, info, ignoreCase) + } + + val tagName = tag.removePrefix("{{").removeSuffix("}}") + val tagRegex = "\\{\\{${tagName}###([^=]+)===(.*?)\\}\\}".toRegex() + tagRegex.findAll(result).forEach { + try { + Log.d("MsgInfo", "tagRegex: ${it.value}, ${it.groupValues}") + val regex = it.groupValues[1] + val replacement = it.groupValues[2] + val temp = info.replace(regex.toRegex(), replacement) + Log.d("MsgInfo", "tagRegex: regex=$regex, replacement=$replacement, temp=$temp") + result = if (needJson) { + result.replace(it.value, toJsonStr(temp)) + } else { + result.replace(it.value, temp) + } + } catch (e: Exception) { + Log.e("MsgInfo", "Failed to replace tagRegex: ${e.message}") + } + } + + return result + } + //替换{{APP名称}}标签 - private fun replaceAppName(content: String, packageName: String, needJson: Boolean = false): String { - if (TextUtils.isEmpty(content)) return content - if (content.indexOf(getString(R.string.tag_app_name)) == -1) return content + private fun String.replaceAppNameTag(packageName: String, needJson: Boolean = false): String { + if (TextUtils.isEmpty(this)) return this + if (this.indexOf(getString(R.string.tag_app_name)) == -1) return this var appName = "" if (SettingUtils.enableLoadUserAppList && App.UserAppList.isNotEmpty()) { @@ -219,30 +187,30 @@ data class MsgInfo( } } if (needJson) { - appName = jsonInnerStr(appName) + appName = toJsonStr(appName) } - return content.replace(getString(R.string.tag_app_name), appName) + return this.replaceTag(getString(R.string.tag_app_name), appName) } //替换 {{定位信息}} 标签 - private fun replaceLocationTag(content: String, needJson: Boolean = false): String { - if (TextUtils.isEmpty(content)) return content - //if (content.indexOf(getString(R.string.tag_location)) == -1) return content + private fun String.replaceLocationTag(needJson: Boolean = false): String { + if (TextUtils.isEmpty(this)) return this val location = HttpServerUtils.apiLocationCache var locationStr = location.toString() var address = location.address if (needJson) { - locationStr = jsonInnerStr(locationStr) - address = jsonInnerStr(address) + locationStr = toJsonStr(locationStr) + address = toJsonStr(address) } - return content.replace(getString(R.string.tag_location), locationStr) - .replace(getString(R.string.tag_location_longitude), location.longitude.toString()) - .replace(getString(R.string.tag_location_latitude), location.latitude.toString()) - .replace(getString(R.string.tag_location_address), address) + return this.replaceTag(getString(R.string.tag_location), locationStr) + .replaceTag(getString(R.string.tag_location_longitude), location.longitude.toString()) + .replaceTag(getString(R.string.tag_location_latitude), location.latitude.toString()) + .replaceTag(getString(R.string.tag_location_address), address) } - private fun jsonInnerStr(string: String?): String { + //直接插入json字符串需要转义 + private fun toJsonStr(string: String?): String { if (string == null) return "null" val jsonStr: String = Gson().toJson(string) diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt index 2096dfff..9fdf1de4 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt @@ -702,6 +702,10 @@ class RulesEditFragment : BaseFragment(), View.OnClic } val smsTemplate = binding!!.etSmsTemplate.text.toString().trim() + val checkResult = CommonUtils.checkTemplateTag(smsTemplate) + if (checkResult.isNotEmpty()) { + throw Exception(checkResult) + } val regexReplace = binding!!.etRegexReplace.text.toString().trim() val lineNum = checkRegexReplace(regexReplace) if (lineNum > 0) { @@ -766,6 +770,14 @@ class RulesEditFragment : BaseFragment(), View.OnClic for (line in lineArray!!) { val position = line.indexOf("===") if (position < 1) return lineNum + + // 校验正则表达式部分是否合法 + try { + line.substring(0, position).toRegex() + } catch (e: Exception) { + return lineNum + } + lineNum++ } return 0 diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt index 50c1c864..2ed01cc0 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt @@ -207,6 +207,24 @@ class CommonUtils private constructor() { PageOption.to(MarkdownFragment::class.java).putString(MarkdownFragment.KEY_MD_TITLE, title).putString(MarkdownFragment.KEY_MD_URL, url).putBoolean(MarkdownFragment.KEY_IS_IMMERSIVE, isImmersive).open(fragment!!) } + //检查自定义模板中的标签是否合法 + fun checkTemplateTag(template: String): String { + val tagRegex = "\\{\\{[^#]+###([^=]+)===(.*?)\\}\\}".toRegex() + tagRegex.findAll(template).forEach { + try { + it.groupValues[1].toRegex() + //TODO:怎么测试反向引用是否正确? + /*val replacement = it.groupValues[2] + if (replacement.isNotEmpty()) { + "pppscn/SmsForwarder".replace(regex, replacement) + }*/ + } catch (e: Exception) { + return String.format(getString(R.string.invalid_tag), it.value, e.message) + } + } + return "" + } + //是否合法的url fun checkUrl(urls: String?): Boolean { return checkUrl(urls, false) diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 9355196c..e71e7eab 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -1383,4 +1383,6 @@ Optional, download mp3/ogg/wav to the Download directory. Alarm Volume Play Times(0=Infinite) + + %s tag is invalid: %s diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 7e95a3d3..8c31ab7d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1384,4 +1384,6 @@ 可选,下载 mp3/ogg/wav 到 Download 目录 播放音量 播放次数(0=无限) + + %s 标签无效:%s diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b26e182d..0ef20f1a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1384,4 +1384,6 @@ 可選,下載 mp3/ogg/wav 到 Download 目錄 播放音量 播放次數(0=無限) + + %s 標籤無效:%s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b077a3b9..32cfd98c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1384,4 +1384,6 @@ 可选,下载 mp3/ogg/wav 到 Download 目录 播放音量 播放次数(0=无限) + + %s 标签无效:%s