整理:code review
This commit is contained in:
parent
992fc2eb8c
commit
e1660fe9bb
|
@ -1,174 +1,174 @@
|
|||
package com.idormy.sms.forwarder.service
|
||||
|
||||
import android.app.*
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.activity.MainActivity
|
||||
import com.idormy.sms.forwarder.database.AppDatabase
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.xuexiang.xutil.file.FileUtils
|
||||
import frpclib.Frpclib
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
@Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE")
|
||||
class ForegroundService : Service() {
|
||||
private val TAG: String = "ForegroundService"
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
private val frpcObserver = Observer { uid: String ->
|
||||
if (Frpclib.isRunning(uid)) {
|
||||
return@Observer
|
||||
}
|
||||
AppDatabase.getInstance(App.context)
|
||||
.frpcDao()
|
||||
.get(uid)
|
||||
.flatMap { (uid1, _, config) ->
|
||||
val error = Frpclib.runContent(uid1, config)
|
||||
Single.just(error)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : SingleObserver<String> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
compositeDisposable.add(d)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
e.printStackTrace()
|
||||
LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid)
|
||||
}
|
||||
|
||||
override fun onSuccess(msg: String) {
|
||||
if (!TextUtils.isEmpty(msg)) {
|
||||
Log.e(TAG, msg)
|
||||
LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid)
|
||||
} else {
|
||||
LiveEventBus.get(EVENT_FRPC_RUNNING_SUCCESS, String::class.java).post(uid)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
private var notificationManager: NotificationManager? = null
|
||||
|
||||
companion object {
|
||||
var isRunning = false
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
try {
|
||||
//纯客户端模式
|
||||
if (SettingUtils.enablePureClientMode) return
|
||||
|
||||
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
startForeground(FRONT_NOTIFY_ID, createForegroundNotification())
|
||||
|
||||
//开关通知监听服务
|
||||
if (SettingUtils.enableAppNotify && CommonUtils.isNotificationListenerServiceEnabled(this)) {
|
||||
CommonUtils.toggleNotificationListenerService(this)
|
||||
}
|
||||
|
||||
if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
|
||||
//监听Frpc启动指令
|
||||
LiveEventBus.get(INTENT_FRPC_APPLY_FILE, String::class.java).observeStickyForever(frpcObserver)
|
||||
//自启动的Frpc
|
||||
GlobalScope.async(Dispatchers.IO) {
|
||||
val frpcList = AppDatabase.getInstance(App.context).frpcDao().getAutorun()
|
||||
|
||||
if (frpcList.isEmpty()) {
|
||||
Log.d(TAG, "没有自启动的Frpc")
|
||||
return@async
|
||||
}
|
||||
|
||||
for (frpc in frpcList) {
|
||||
val error = Frpclib.runContent(frpc.uid, frpc.config)
|
||||
if (!TextUtils.isEmpty(error)) {
|
||||
Log.e(TAG, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isRunning = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
isRunning = true
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
//纯客户端模式
|
||||
if (SettingUtils.enablePureClientMode) {
|
||||
super.onDestroy()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
stopForeground(true)
|
||||
compositeDisposable.dispose()
|
||||
isRunning = false
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createForegroundNotification(): Notification {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val importance = NotificationManager.IMPORTANCE_HIGH
|
||||
val notificationChannel = NotificationChannel(FRONT_CHANNEL_ID, FRONT_CHANNEL_NAME, importance)
|
||||
notificationChannel.description = "Frpc Foreground Service"
|
||||
notificationChannel.enableLights(true)
|
||||
notificationChannel.lightColor = Color.GREEN
|
||||
notificationChannel.vibrationPattern = longArrayOf(0, 1000, 500, 1000)
|
||||
notificationChannel.enableVibration(true)
|
||||
if (notificationManager != null) {
|
||||
notificationManager!!.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
}
|
||||
val builder = NotificationCompat.Builder(this, FRONT_CHANNEL_ID)
|
||||
builder.setSmallIcon(R.drawable.ic_forwarder)
|
||||
builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_menu_frpc))
|
||||
// TODO: 部分机型标题会重复待排除
|
||||
// if (DeviceUtils.getDeviceBrand().contains("Xiaomi")) {
|
||||
builder.setContentTitle(getString(R.string.app_name))
|
||||
//}
|
||||
builder.setContentText(SettingUtils.notifyContent.toString())
|
||||
builder.setWhen(System.currentTimeMillis())
|
||||
val activityIntent = Intent(this, MainActivity::class.java)
|
||||
val flags = if (Build.VERSION.SDK_INT >= 30) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT
|
||||
val pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, flags)
|
||||
builder.setContentIntent(pendingIntent)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
package com.idormy.sms.forwarder.service
|
||||
|
||||
import android.app.*
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.activity.MainActivity
|
||||
import com.idormy.sms.forwarder.database.AppDatabase
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.xuexiang.xutil.file.FileUtils
|
||||
import frpclib.Frpclib
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
@Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE")
|
||||
class ForegroundService : Service() {
|
||||
private val TAG: String = "ForegroundService"
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
private val frpcObserver = Observer { uid: String ->
|
||||
if (Frpclib.isRunning(uid)) {
|
||||
return@Observer
|
||||
}
|
||||
AppDatabase.getInstance(App.context)
|
||||
.frpcDao()
|
||||
.get(uid)
|
||||
.flatMap { (uid1, _, config) ->
|
||||
val error = Frpclib.runContent(uid1, config)
|
||||
Single.just(error)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : SingleObserver<String> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
compositeDisposable.add(d)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
e.printStackTrace()
|
||||
LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid)
|
||||
}
|
||||
|
||||
override fun onSuccess(msg: String) {
|
||||
if (!TextUtils.isEmpty(msg)) {
|
||||
Log.e(TAG, msg)
|
||||
LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid)
|
||||
} else {
|
||||
LiveEventBus.get(EVENT_FRPC_RUNNING_SUCCESS, String::class.java).post(uid)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
private var notificationManager: NotificationManager? = null
|
||||
|
||||
companion object {
|
||||
var isRunning = false
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
try {
|
||||
//纯客户端模式
|
||||
if (SettingUtils.enablePureClientMode) return
|
||||
|
||||
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
startForeground(FRONT_NOTIFY_ID, createForegroundNotification())
|
||||
|
||||
//开关通知监听服务
|
||||
if (SettingUtils.enableAppNotify && CommonUtils.isNotificationListenerServiceEnabled(this)) {
|
||||
CommonUtils.toggleNotificationListenerService(this)
|
||||
}
|
||||
|
||||
if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
|
||||
//监听Frpc启动指令
|
||||
LiveEventBus.get(INTENT_FRPC_APPLY_FILE, String::class.java).observeStickyForever(frpcObserver)
|
||||
//自启动的Frpc
|
||||
GlobalScope.async(Dispatchers.IO) {
|
||||
val frpcList = AppDatabase.getInstance(App.context).frpcDao().getAutorun()
|
||||
|
||||
if (frpcList.isEmpty()) {
|
||||
Log.d(TAG, "没有自启动的Frpc")
|
||||
return@async
|
||||
}
|
||||
|
||||
for (frpc in frpcList) {
|
||||
val error = Frpclib.runContent(frpc.uid, frpc.config)
|
||||
if (!TextUtils.isEmpty(error)) {
|
||||
Log.e(TAG, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isRunning = true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
isRunning = true
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
//纯客户端模式
|
||||
if (SettingUtils.enablePureClientMode) {
|
||||
super.onDestroy()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
stopForeground(true)
|
||||
compositeDisposable.dispose()
|
||||
isRunning = false
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createForegroundNotification(): Notification {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val importance = NotificationManager.IMPORTANCE_HIGH
|
||||
val notificationChannel = NotificationChannel(FRONT_CHANNEL_ID, FRONT_CHANNEL_NAME, importance)
|
||||
notificationChannel.description = "Frpc Foreground Service"
|
||||
notificationChannel.enableLights(true)
|
||||
notificationChannel.lightColor = Color.GREEN
|
||||
notificationChannel.vibrationPattern = longArrayOf(0, 1000, 500, 1000)
|
||||
notificationChannel.enableVibration(true)
|
||||
if (notificationManager != null) {
|
||||
notificationManager!!.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
}
|
||||
val builder = NotificationCompat.Builder(this, FRONT_CHANNEL_ID)
|
||||
builder.setSmallIcon(R.drawable.ic_forwarder)
|
||||
builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_menu_frpc))
|
||||
// TODO: 部分机型标题会重复待排除
|
||||
// if (DeviceUtils.getDeviceBrand().contains("Xiaomi")) {
|
||||
builder.setContentTitle(getString(R.string.app_name))
|
||||
//}
|
||||
builder.setContentText(SettingUtils.notifyContent)
|
||||
builder.setWhen(System.currentTimeMillis())
|
||||
val activityIntent = Intent(this, MainActivity::class.java)
|
||||
val flags = if (Build.VERSION.SDK_INT >= 30) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT
|
||||
val pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, flags)
|
||||
builder.setContentIntent(pendingIntent)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
}
|
|
@ -35,7 +35,7 @@ class BarkUtils {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val requestUrl: String = setting.server //推送地址
|
||||
|
|
|
@ -35,7 +35,7 @@ class DingtalkGroupRobotUtils private constructor() {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
var requestUrl = if (setting.token.startsWith("http")) setting.token else "https://oapi.dingtalk.com/robot/send?access_token=" + setting.token
|
||||
|
|
|
@ -126,7 +126,7 @@ class DingtalkInnerRobotUtils private constructor() {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val msgParam: MutableMap<String, Any> = mutableMapOf()
|
||||
|
|
|
@ -1,151 +1,151 @@
|
|||
package com.idormy.sms.forwarder.utils.sender
|
||||
|
||||
import android.util.Log
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.setting.EmailSetting
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.mail.Mail
|
||||
import com.idormy.sms.forwarder.utils.mail.MailSender
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
|
||||
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
|
||||
class EmailUtils {
|
||||
companion object {
|
||||
|
||||
private val TAG: String = EmailUtils::class.java.simpleName
|
||||
|
||||
fun sendMsg(
|
||||
setting: EmailSetting,
|
||||
msgInfo: MsgInfo,
|
||||
rule: Rule?,
|
||||
logId: Long?,
|
||||
) {
|
||||
val title: String = if (rule != null) {
|
||||
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getTitleForSend(setting.title.toString())
|
||||
}
|
||||
val message: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
}
|
||||
|
||||
//常用邮箱类型的转换
|
||||
when (setting.mailType) {
|
||||
"@qq.com", "@foxmail.com" -> {
|
||||
setting.host = "smtp.qq.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@exmail.qq.com" -> {
|
||||
setting.host = "smtp.exmail.qq.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@msn.com" -> {
|
||||
setting.host = "smtp-mail.outlook.com"
|
||||
setting.port = "587"
|
||||
setting.ssl = false
|
||||
setting.startTls = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@outlook.com", "@office365.com", "@live.com", "@hotmail.com" -> {
|
||||
setting.host = "smtp.office365.com"
|
||||
setting.port = "587"
|
||||
setting.ssl = false
|
||||
setting.startTls = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@gmail.com" -> {
|
||||
setting.host = "smtp.gmail.com"
|
||||
setting.port = "587"
|
||||
setting.ssl = true
|
||||
setting.startTls = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@yeah.net" -> {
|
||||
setting.host = "smtp.yeah.net"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@163.com" -> {
|
||||
setting.host = "smtp.163.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@126.com" -> {
|
||||
setting.host = "smtp.126.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@sina.com" -> {
|
||||
setting.host = "smtp.sina.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@sina.cn" -> {
|
||||
setting.host = "smtp.sina.cn"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@139.com" -> {
|
||||
setting.host = "smtp.139.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@189.cn" -> {
|
||||
setting.host = "smtp.189.cn"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
//收件地址
|
||||
val toAddressList = setting.toEmail.toString().replace("[,,;;]".toRegex(), ",").trim(',').split(',')
|
||||
|
||||
//创建邮箱
|
||||
val mail = Mail().apply {
|
||||
mailServerHost = setting.host.toString()
|
||||
mailServerPort = setting.port.toString()
|
||||
fromAddress = setting.fromEmail.toString()
|
||||
fromNickname = msgInfo.getTitleForSend(setting.nickname.toString())
|
||||
password = setting.pwd.toString()
|
||||
toAddress = toAddressList
|
||||
subject = title
|
||||
content = message.replace("\n", "<br>")
|
||||
openSSL = setting.ssl == true
|
||||
startTls = setting.startTls == true
|
||||
}
|
||||
|
||||
MailSender.getInstance().sendMail(mail, object : MailSender.OnMailSendListener {
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e("MailSender", e.message.toString())
|
||||
SendUtils.updateLogs(logId, 0, e.message.toString())
|
||||
}
|
||||
|
||||
override fun onSuccess() {
|
||||
SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun sendMsg(setting: EmailSetting, msgInfo: MsgInfo) {
|
||||
sendMsg(setting, msgInfo, null, null)
|
||||
}
|
||||
}
|
||||
package com.idormy.sms.forwarder.utils.sender
|
||||
|
||||
import android.util.Log
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.setting.EmailSetting
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.mail.Mail
|
||||
import com.idormy.sms.forwarder.utils.mail.MailSender
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
|
||||
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
|
||||
class EmailUtils {
|
||||
companion object {
|
||||
|
||||
private val TAG: String = EmailUtils::class.java.simpleName
|
||||
|
||||
fun sendMsg(
|
||||
setting: EmailSetting,
|
||||
msgInfo: MsgInfo,
|
||||
rule: Rule?,
|
||||
logId: Long?,
|
||||
) {
|
||||
val title: String = if (rule != null) {
|
||||
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getTitleForSend(setting.title.toString())
|
||||
}
|
||||
val message: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
//常用邮箱类型的转换
|
||||
when (setting.mailType) {
|
||||
"@qq.com", "@foxmail.com" -> {
|
||||
setting.host = "smtp.qq.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@exmail.qq.com" -> {
|
||||
setting.host = "smtp.exmail.qq.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@msn.com" -> {
|
||||
setting.host = "smtp-mail.outlook.com"
|
||||
setting.port = "587"
|
||||
setting.ssl = false
|
||||
setting.startTls = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@outlook.com", "@office365.com", "@live.com", "@hotmail.com" -> {
|
||||
setting.host = "smtp.office365.com"
|
||||
setting.port = "587"
|
||||
setting.ssl = false
|
||||
setting.startTls = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@gmail.com" -> {
|
||||
setting.host = "smtp.gmail.com"
|
||||
setting.port = "587"
|
||||
setting.ssl = true
|
||||
setting.startTls = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@yeah.net" -> {
|
||||
setting.host = "smtp.yeah.net"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@163.com" -> {
|
||||
setting.host = "smtp.163.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@126.com" -> {
|
||||
setting.host = "smtp.126.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@sina.com" -> {
|
||||
setting.host = "smtp.sina.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@sina.cn" -> {
|
||||
setting.host = "smtp.sina.cn"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@139.com" -> {
|
||||
setting.host = "smtp.139.com"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
"@189.cn" -> {
|
||||
setting.host = "smtp.189.cn"
|
||||
setting.port = "465"
|
||||
setting.ssl = true
|
||||
setting.fromEmail += setting.mailType
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
//收件地址
|
||||
val toAddressList = setting.toEmail.toString().replace("[,,;;]".toRegex(), ",").trim(',').split(',')
|
||||
|
||||
//创建邮箱
|
||||
val mail = Mail().apply {
|
||||
mailServerHost = setting.host.toString()
|
||||
mailServerPort = setting.port.toString()
|
||||
fromAddress = setting.fromEmail.toString()
|
||||
fromNickname = msgInfo.getTitleForSend(setting.nickname.toString())
|
||||
password = setting.pwd.toString()
|
||||
toAddress = toAddressList
|
||||
subject = title
|
||||
content = message.replace("\n", "<br>")
|
||||
openSSL = setting.ssl == true
|
||||
startTls = setting.startTls == true
|
||||
}
|
||||
|
||||
MailSender.getInstance().sendMail(mail, object : MailSender.OnMailSendListener {
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e("MailSender", e.message.toString())
|
||||
SendUtils.updateLogs(logId, 0, e.message.toString())
|
||||
}
|
||||
|
||||
override fun onSuccess() {
|
||||
SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun sendMsg(setting: EmailSetting, msgInfo: MsgInfo) {
|
||||
sendMsg(setting, msgInfo, null, null)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -84,7 +84,7 @@ class FeishuAppUtils private constructor() {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val msgContent = if ("interactive" == setting.msgType) {
|
||||
|
|
|
@ -94,7 +94,7 @@ class FeishuUtils private constructor() {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val requestUrl = setting.webhook
|
||||
|
|
|
@ -1,89 +1,89 @@
|
|||
package com.idormy.sms.forwarder.utils.sender
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.result.GotifyResult
|
||||
import com.idormy.sms.forwarder.entity.setting.GotifySetting
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.cache.model.CacheMode
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
|
||||
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
|
||||
class GotifyUtils {
|
||||
companion object {
|
||||
|
||||
private val TAG: String = GotifyUtils::class.java.simpleName
|
||||
|
||||
fun sendMsg(
|
||||
setting: GotifySetting,
|
||||
msgInfo: MsgInfo,
|
||||
rule: Rule?,
|
||||
logId: Long?,
|
||||
) {
|
||||
val title: String = if (rule != null) {
|
||||
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getTitleForSend(setting.title.toString())
|
||||
}
|
||||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
}
|
||||
|
||||
val requestUrl: String = setting.webServer //推送地址
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
|
||||
//支持HTTP基本认证(Basic Authentication)
|
||||
val regex = "^(https?://)([^:]+):([^@]+)@(.+)"
|
||||
val matches = Regex(regex, RegexOption.IGNORE_CASE).findAll(requestUrl).toList().flatMap(MatchResult::groupValues)
|
||||
Log.i(TAG, "matches = $matches")
|
||||
val request = if (matches.isNotEmpty()) {
|
||||
XHttp.post(matches[1] + matches[4]).addInterceptor(BasicAuthInterceptor(matches[2], matches[3]))
|
||||
} else {
|
||||
XHttp.post(requestUrl)
|
||||
}
|
||||
|
||||
request.params("title", title)
|
||||
.params("message", content)
|
||||
.params("priority", setting.priority)
|
||||
.ignoreHttpsCert() //忽略https证书
|
||||
.keepJson(true)
|
||||
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
|
||||
.cacheMode(CacheMode.NO_CACHE)
|
||||
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
|
||||
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
|
||||
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
|
||||
.timeStamp(true)
|
||||
.execute(object : SimpleCallBack<String>() {
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
Log.e(TAG, e.detailMessage)
|
||||
SendUtils.updateLogs(logId, 0, e.displayMessage)
|
||||
}
|
||||
|
||||
override fun onSuccess(response: String) {
|
||||
Log.i(TAG, response)
|
||||
|
||||
val resp = Gson().fromJson(response, GotifyResult::class.java)
|
||||
if (resp?.id != null) {
|
||||
SendUtils.updateLogs(logId, 2, response)
|
||||
} else {
|
||||
SendUtils.updateLogs(logId, 0, response)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun sendMsg(setting: GotifySetting, msgInfo: MsgInfo) {
|
||||
sendMsg(setting, msgInfo, null, null)
|
||||
}
|
||||
}
|
||||
package com.idormy.sms.forwarder.utils.sender
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.result.GotifyResult
|
||||
import com.idormy.sms.forwarder.entity.setting.GotifySetting
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.cache.model.CacheMode
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
|
||||
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
|
||||
class GotifyUtils {
|
||||
companion object {
|
||||
|
||||
private val TAG: String = GotifyUtils::class.java.simpleName
|
||||
|
||||
fun sendMsg(
|
||||
setting: GotifySetting,
|
||||
msgInfo: MsgInfo,
|
||||
rule: Rule?,
|
||||
logId: Long?,
|
||||
) {
|
||||
val title: String = if (rule != null) {
|
||||
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getTitleForSend(setting.title.toString())
|
||||
}
|
||||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val requestUrl: String = setting.webServer //推送地址
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
|
||||
//支持HTTP基本认证(Basic Authentication)
|
||||
val regex = "^(https?://)([^:]+):([^@]+)@(.+)"
|
||||
val matches = Regex(regex, RegexOption.IGNORE_CASE).findAll(requestUrl).toList().flatMap(MatchResult::groupValues)
|
||||
Log.i(TAG, "matches = $matches")
|
||||
val request = if (matches.isNotEmpty()) {
|
||||
XHttp.post(matches[1] + matches[4]).addInterceptor(BasicAuthInterceptor(matches[2], matches[3]))
|
||||
} else {
|
||||
XHttp.post(requestUrl)
|
||||
}
|
||||
|
||||
request.params("title", title)
|
||||
.params("message", content)
|
||||
.params("priority", setting.priority)
|
||||
.ignoreHttpsCert() //忽略https证书
|
||||
.keepJson(true)
|
||||
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
|
||||
.cacheMode(CacheMode.NO_CACHE)
|
||||
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
|
||||
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
|
||||
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
|
||||
.timeStamp(true)
|
||||
.execute(object : SimpleCallBack<String>() {
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
Log.e(TAG, e.detailMessage)
|
||||
SendUtils.updateLogs(logId, 0, e.displayMessage)
|
||||
}
|
||||
|
||||
override fun onSuccess(response: String) {
|
||||
Log.i(TAG, response)
|
||||
|
||||
val resp = Gson().fromJson(response, GotifyResult::class.java)
|
||||
if (resp?.id != null) {
|
||||
SendUtils.updateLogs(logId, 2, response)
|
||||
} else {
|
||||
SendUtils.updateLogs(logId, 0, response)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun sendMsg(setting: GotifySetting, msgInfo: MsgInfo) {
|
||||
sendMsg(setting, msgInfo, null, null)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ class PushplusUtils private constructor() {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val requestUrl = "https://" + setting.website + "/send"
|
||||
|
@ -63,7 +63,6 @@ class PushplusUtils private constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
val requestMsg: String = Gson().toJson(msgMap)
|
||||
Log.i(TAG, "requestMsg:$requestMsg")
|
||||
|
||||
|
|
|
@ -1,83 +1,83 @@
|
|||
package com.idormy.sms.forwarder.utils.sender
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.result.ServerchanResult
|
||||
import com.idormy.sms.forwarder.entity.setting.ServerchanSetting
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.cache.model.CacheMode
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
|
||||
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
|
||||
class ServerchanUtils {
|
||||
companion object {
|
||||
|
||||
private val TAG: String = ServerchanUtils::class.java.simpleName
|
||||
|
||||
fun sendMsg(
|
||||
setting: ServerchanSetting,
|
||||
msgInfo: MsgInfo,
|
||||
rule: Rule?,
|
||||
logId: Long?,
|
||||
) {
|
||||
val title: String = if (rule != null) {
|
||||
msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getTitleForSend(setting.titleTemplate.toString())
|
||||
}
|
||||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
}
|
||||
|
||||
val requestUrl: String = String.format("https://sctapi.ftqq.com/%s.send", setting.sendKey) //推送地址
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
|
||||
val request = XHttp.post(requestUrl)
|
||||
.params("title", title)
|
||||
.params("desp", content)
|
||||
|
||||
if (!TextUtils.isEmpty(setting.channel)) request.params("channel", setting.channel)
|
||||
if (!TextUtils.isEmpty(setting.openid)) request.params("group", setting.openid)
|
||||
|
||||
request.keepJson(true)
|
||||
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
|
||||
.cacheMode(CacheMode.NO_CACHE)
|
||||
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
|
||||
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
|
||||
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
|
||||
.timeStamp(true)
|
||||
.execute(object : SimpleCallBack<String>() {
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
Log.e(TAG, e.detailMessage)
|
||||
SendUtils.updateLogs(logId, 0, e.displayMessage)
|
||||
}
|
||||
|
||||
override fun onSuccess(response: String) {
|
||||
Log.i(TAG, response)
|
||||
|
||||
val resp = Gson().fromJson(response, ServerchanResult::class.java)
|
||||
if (resp?.code == 0L) {
|
||||
SendUtils.updateLogs(logId, 2, response)
|
||||
} else {
|
||||
SendUtils.updateLogs(logId, 0, response)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun sendMsg(setting: ServerchanSetting, msgInfo: MsgInfo) {
|
||||
sendMsg(setting, msgInfo, null, null)
|
||||
}
|
||||
}
|
||||
package com.idormy.sms.forwarder.utils.sender
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.result.ServerchanResult
|
||||
import com.idormy.sms.forwarder.entity.setting.ServerchanSetting
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.cache.model.CacheMode
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
|
||||
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
|
||||
class ServerchanUtils {
|
||||
companion object {
|
||||
|
||||
private val TAG: String = ServerchanUtils::class.java.simpleName
|
||||
|
||||
fun sendMsg(
|
||||
setting: ServerchanSetting,
|
||||
msgInfo: MsgInfo,
|
||||
rule: Rule?,
|
||||
logId: Long?,
|
||||
) {
|
||||
val title: String = if (rule != null) {
|
||||
msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getTitleForSend(setting.titleTemplate.toString())
|
||||
}
|
||||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val requestUrl: String = String.format("https://sctapi.ftqq.com/%s.send", setting.sendKey) //推送地址
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
|
||||
val request = XHttp.post(requestUrl)
|
||||
.params("title", title)
|
||||
.params("desp", content)
|
||||
|
||||
if (!TextUtils.isEmpty(setting.channel)) request.params("channel", setting.channel)
|
||||
if (!TextUtils.isEmpty(setting.openid)) request.params("group", setting.openid)
|
||||
|
||||
request.keepJson(true)
|
||||
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
|
||||
.cacheMode(CacheMode.NO_CACHE)
|
||||
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
|
||||
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
|
||||
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
|
||||
.timeStamp(true)
|
||||
.execute(object : SimpleCallBack<String>() {
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
Log.e(TAG, e.detailMessage)
|
||||
SendUtils.updateLogs(logId, 0, e.displayMessage)
|
||||
}
|
||||
|
||||
override fun onSuccess(response: String) {
|
||||
Log.i(TAG, response)
|
||||
|
||||
val resp = Gson().fromJson(response, ServerchanResult::class.java)
|
||||
if (resp?.code == 0L) {
|
||||
SendUtils.updateLogs(logId, 2, response)
|
||||
} else {
|
||||
SendUtils.updateLogs(logId, 0, response)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun sendMsg(setting: ServerchanSetting, msgInfo: MsgInfo) {
|
||||
sendMsg(setting, msgInfo, null, null)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ class TelegramUtils private constructor() {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
var requestUrl = if (setting.apiToken.startsWith("http")) {
|
||||
|
|
|
@ -1,209 +1,209 @@
|
|||
package com.idormy.sms.forwarder.utils.sender
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.TextUtils
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.setting.WebhookSetting
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.cache.model.CacheMode
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
import com.xuexiang.xutil.app.AppUtils
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
|
||||
class WebhookUtils {
|
||||
companion object {
|
||||
|
||||
private val TAG: String = WebhookUtils::class.java.simpleName
|
||||
|
||||
fun sendMsg(
|
||||
setting: WebhookSetting,
|
||||
msgInfo: MsgInfo,
|
||||
rule: Rule?,
|
||||
logId: Long?,
|
||||
) {
|
||||
val from: String = msgInfo.from
|
||||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
}
|
||||
|
||||
var requestUrl: String = setting.webServer //推送地址
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val orgContent: String = msgInfo.content
|
||||
val deviceMark: String = SettingUtils.extraDeviceMark ?: ""
|
||||
val appVersion: String = AppUtils.getAppVersionName()
|
||||
val simInfo: String = msgInfo.simInfo
|
||||
@SuppressLint("SimpleDateFormat") val receiveTime =
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) //smsVo.getDate()
|
||||
|
||||
var sign = ""
|
||||
if (!TextUtils.isEmpty(setting.secret)) {
|
||||
val stringToSign = "$timestamp\n" + setting.secret
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(
|
||||
SecretKeySpec(
|
||||
setting.secret?.toByteArray(StandardCharsets.UTF_8),
|
||||
"HmacSHA256"
|
||||
)
|
||||
)
|
||||
val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8))
|
||||
sign = URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8")
|
||||
}
|
||||
|
||||
var webParams = setting.webParams?.trim()
|
||||
|
||||
//支持HTTP基本认证(Basic Authentication)
|
||||
val regex = "^(https?://)([^:]+):([^@]+)@(.+)"
|
||||
val matches = Regex(regex, RegexOption.IGNORE_CASE).findAll(requestUrl).toList()
|
||||
.flatMap(MatchResult::groupValues)
|
||||
Log.i(TAG, "matches = $matches")
|
||||
if (matches.isNotEmpty()) {
|
||||
requestUrl = matches[1] + matches[4]
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
}
|
||||
|
||||
val request = if (setting.method == "GET" && TextUtils.isEmpty(webParams)) {
|
||||
setting.webServer += (if (setting.webServer.contains("?")) "&" else "?") + "from=" + URLEncoder.encode(
|
||||
from,
|
||||
"UTF-8"
|
||||
)
|
||||
requestUrl += "&content=" + URLEncoder.encode(content, "UTF-8")
|
||||
if (!TextUtils.isEmpty(sign)) {
|
||||
requestUrl += "×tamp=$timestamp"
|
||||
requestUrl += "&sign=$sign"
|
||||
}
|
||||
Log.d(TAG, "method = GET, Url = $requestUrl")
|
||||
XHttp.get(requestUrl).keepJson(true)
|
||||
} else if (setting.method == "GET" && !TextUtils.isEmpty(webParams)) {
|
||||
webParams = webParams.toString().replace("[from]", URLEncoder.encode(from, "UTF-8"))
|
||||
.replace("[content]", URLEncoder.encode(content, "UTF-8"))
|
||||
.replace("[msg]", URLEncoder.encode(content, "UTF-8"))
|
||||
.replace("[org_content]", URLEncoder.encode(orgContent, "UTF-8"))
|
||||
.replace("[device_mark]", URLEncoder.encode(deviceMark, "UTF-8"))
|
||||
.replace("[app_version]", URLEncoder.encode(appVersion, "UTF-8"))
|
||||
.replace("[title]", URLEncoder.encode(simInfo, "UTF-8"))
|
||||
.replace("[card_slot]", URLEncoder.encode(simInfo, "UTF-8"))
|
||||
.replace("[receive_time]", URLEncoder.encode(receiveTime, "UTF-8"))
|
||||
.replace("\n", "%0A")
|
||||
if (!TextUtils.isEmpty(setting.secret)) {
|
||||
webParams = webParams.replace("[timestamp]", timestamp.toString())
|
||||
.replace("[sign]", URLEncoder.encode(sign, "UTF-8"))
|
||||
}
|
||||
requestUrl += if (webParams.startsWith("/")) {
|
||||
webParams
|
||||
} else {
|
||||
(if (requestUrl.contains("?")) "&" else "?") + webParams
|
||||
}
|
||||
Log.d(TAG, "method = GET, Url = $requestUrl")
|
||||
XHttp.get(requestUrl).keepJson(true)
|
||||
} else if (webParams != null && webParams.isNotEmpty() && webParams.startsWith("{")) {
|
||||
val bodyMsg = webParams.replace("[from]", from)
|
||||
.replace("[content]", escapeJson(content))
|
||||
.replace("[msg]", escapeJson(content))
|
||||
.replace("[org_content]", escapeJson(orgContent))
|
||||
.replace("[device_mark]", escapeJson(deviceMark))
|
||||
.replace("[app_version]", appVersion)
|
||||
.replace("[title]", escapeJson(simInfo))
|
||||
.replace("[card_slot]", escapeJson(simInfo))
|
||||
.replace("[receive_time]", receiveTime)
|
||||
.replace("[timestamp]", timestamp.toString())
|
||||
.replace("[sign]", sign)
|
||||
Log.d(TAG, "method = ${setting.method}, Url = $requestUrl, bodyMsg = $bodyMsg")
|
||||
when (setting.method) {
|
||||
"PUT" -> XHttp.put(requestUrl).keepJson(true).upJson(bodyMsg)
|
||||
"PATCH" -> XHttp.patch(requestUrl).keepJson(true).upJson(bodyMsg)
|
||||
else -> XHttp.post(requestUrl).keepJson(true).upJson(bodyMsg)
|
||||
}
|
||||
} else {
|
||||
if (webParams == null || webParams.isEmpty()) {
|
||||
webParams = "from=[from]&content=[content]×tamp=[timestamp]"
|
||||
if (!TextUtils.isEmpty(sign)) webParams += "&sign=[sign]"
|
||||
}
|
||||
Log.d(TAG, "method = ${setting.method}, Url = $requestUrl")
|
||||
val postRequest = when (setting.method) {
|
||||
"PUT" -> XHttp.put(requestUrl).keepJson(true)
|
||||
"PATCH" -> XHttp.patch(requestUrl).keepJson(true)
|
||||
else -> XHttp.post(requestUrl).keepJson(true)
|
||||
}
|
||||
webParams.trim('&').split("&").forEach {
|
||||
val param = it.split("=")
|
||||
if (param.size == 2) {
|
||||
postRequest.params(
|
||||
param[0], param[1].replace("[from]", from)
|
||||
.replace("[content]", content)
|
||||
.replace("[msg]", content)
|
||||
.replace("[org_content]", orgContent)
|
||||
.replace("[device_mark]", deviceMark)
|
||||
.replace("[app_version]", appVersion)
|
||||
.replace("[title]", simInfo)
|
||||
.replace("[card_slot]", simInfo)
|
||||
.replace("[receive_time]", receiveTime)
|
||||
.replace("[timestamp]", timestamp.toString())
|
||||
.replace("[sign]", sign)
|
||||
)
|
||||
}
|
||||
}
|
||||
postRequest
|
||||
}
|
||||
|
||||
//添加headers
|
||||
for ((key, value) in setting.headers?.entries!!) {
|
||||
request.headers(key, value)
|
||||
}
|
||||
|
||||
//支持HTTP基本认证(Basic Authentication)
|
||||
if (matches.isNotEmpty()) {
|
||||
request.addInterceptor(BasicAuthInterceptor(matches[2], matches[3]))
|
||||
}
|
||||
|
||||
request.ignoreHttpsCert() //忽略https证书
|
||||
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
|
||||
.cacheMode(CacheMode.NO_CACHE)
|
||||
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
|
||||
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
|
||||
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
|
||||
.timeStamp(true)
|
||||
.execute(object : SimpleCallBack<String>() {
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
Log.e(TAG, e.detailMessage)
|
||||
SendUtils.updateLogs(logId, 0, e.displayMessage)
|
||||
}
|
||||
|
||||
override fun onSuccess(response: String) {
|
||||
Log.i(TAG, response)
|
||||
SendUtils.updateLogs(logId, 2, response)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
//JSON需要转义的字符
|
||||
private fun escapeJson(str: String?): String {
|
||||
if (str == null) return "null"
|
||||
val jsonStr: String = Gson().toJson(str)
|
||||
return if (jsonStr.length >= 2) jsonStr.substring(1, jsonStr.length - 1) else jsonStr
|
||||
}
|
||||
|
||||
fun sendMsg(setting: WebhookSetting, msgInfo: MsgInfo) {
|
||||
sendMsg(setting, msgInfo, null, null)
|
||||
}
|
||||
}
|
||||
package com.idormy.sms.forwarder.utils.sender
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.TextUtils
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.setting.WebhookSetting
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.cache.model.CacheMode
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
import com.xuexiang.xutil.app.AppUtils
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
|
||||
class WebhookUtils {
|
||||
companion object {
|
||||
|
||||
private val TAG: String = WebhookUtils::class.java.simpleName
|
||||
|
||||
fun sendMsg(
|
||||
setting: WebhookSetting,
|
||||
msgInfo: MsgInfo,
|
||||
rule: Rule?,
|
||||
logId: Long?,
|
||||
) {
|
||||
val from: String = msgInfo.from
|
||||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
var requestUrl: String = setting.webServer //推送地址
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val orgContent: String = msgInfo.content
|
||||
val deviceMark: String = SettingUtils.extraDeviceMark
|
||||
val appVersion: String = AppUtils.getAppVersionName()
|
||||
val simInfo: String = msgInfo.simInfo
|
||||
@SuppressLint("SimpleDateFormat") val receiveTime =
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) //smsVo.getDate()
|
||||
|
||||
var sign = ""
|
||||
if (!TextUtils.isEmpty(setting.secret)) {
|
||||
val stringToSign = "$timestamp\n" + setting.secret
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(
|
||||
SecretKeySpec(
|
||||
setting.secret?.toByteArray(StandardCharsets.UTF_8),
|
||||
"HmacSHA256"
|
||||
)
|
||||
)
|
||||
val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8))
|
||||
sign = URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8")
|
||||
}
|
||||
|
||||
var webParams = setting.webParams?.trim()
|
||||
|
||||
//支持HTTP基本认证(Basic Authentication)
|
||||
val regex = "^(https?://)([^:]+):([^@]+)@(.+)"
|
||||
val matches = Regex(regex, RegexOption.IGNORE_CASE).findAll(requestUrl).toList()
|
||||
.flatMap(MatchResult::groupValues)
|
||||
Log.i(TAG, "matches = $matches")
|
||||
if (matches.isNotEmpty()) {
|
||||
requestUrl = matches[1] + matches[4]
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
}
|
||||
|
||||
val request = if (setting.method == "GET" && TextUtils.isEmpty(webParams)) {
|
||||
setting.webServer += (if (setting.webServer.contains("?")) "&" else "?") + "from=" + URLEncoder.encode(
|
||||
from,
|
||||
"UTF-8"
|
||||
)
|
||||
requestUrl += "&content=" + URLEncoder.encode(content, "UTF-8")
|
||||
if (!TextUtils.isEmpty(sign)) {
|
||||
requestUrl += "×tamp=$timestamp"
|
||||
requestUrl += "&sign=$sign"
|
||||
}
|
||||
Log.d(TAG, "method = GET, Url = $requestUrl")
|
||||
XHttp.get(requestUrl).keepJson(true)
|
||||
} else if (setting.method == "GET" && !TextUtils.isEmpty(webParams)) {
|
||||
webParams = webParams.toString().replace("[from]", URLEncoder.encode(from, "UTF-8"))
|
||||
.replace("[content]", URLEncoder.encode(content, "UTF-8"))
|
||||
.replace("[msg]", URLEncoder.encode(content, "UTF-8"))
|
||||
.replace("[org_content]", URLEncoder.encode(orgContent, "UTF-8"))
|
||||
.replace("[device_mark]", URLEncoder.encode(deviceMark, "UTF-8"))
|
||||
.replace("[app_version]", URLEncoder.encode(appVersion, "UTF-8"))
|
||||
.replace("[title]", URLEncoder.encode(simInfo, "UTF-8"))
|
||||
.replace("[card_slot]", URLEncoder.encode(simInfo, "UTF-8"))
|
||||
.replace("[receive_time]", URLEncoder.encode(receiveTime, "UTF-8"))
|
||||
.replace("\n", "%0A")
|
||||
if (!TextUtils.isEmpty(setting.secret)) {
|
||||
webParams = webParams.replace("[timestamp]", timestamp.toString())
|
||||
.replace("[sign]", URLEncoder.encode(sign, "UTF-8"))
|
||||
}
|
||||
requestUrl += if (webParams.startsWith("/")) {
|
||||
webParams
|
||||
} else {
|
||||
(if (requestUrl.contains("?")) "&" else "?") + webParams
|
||||
}
|
||||
Log.d(TAG, "method = GET, Url = $requestUrl")
|
||||
XHttp.get(requestUrl).keepJson(true)
|
||||
} else if (webParams != null && webParams.isNotEmpty() && webParams.startsWith("{")) {
|
||||
val bodyMsg = webParams.replace("[from]", from)
|
||||
.replace("[content]", escapeJson(content))
|
||||
.replace("[msg]", escapeJson(content))
|
||||
.replace("[org_content]", escapeJson(orgContent))
|
||||
.replace("[device_mark]", escapeJson(deviceMark))
|
||||
.replace("[app_version]", appVersion)
|
||||
.replace("[title]", escapeJson(simInfo))
|
||||
.replace("[card_slot]", escapeJson(simInfo))
|
||||
.replace("[receive_time]", receiveTime)
|
||||
.replace("[timestamp]", timestamp.toString())
|
||||
.replace("[sign]", sign)
|
||||
Log.d(TAG, "method = ${setting.method}, Url = $requestUrl, bodyMsg = $bodyMsg")
|
||||
when (setting.method) {
|
||||
"PUT" -> XHttp.put(requestUrl).keepJson(true).upJson(bodyMsg)
|
||||
"PATCH" -> XHttp.patch(requestUrl).keepJson(true).upJson(bodyMsg)
|
||||
else -> XHttp.post(requestUrl).keepJson(true).upJson(bodyMsg)
|
||||
}
|
||||
} else {
|
||||
if (webParams == null || webParams.isEmpty()) {
|
||||
webParams = "from=[from]&content=[content]×tamp=[timestamp]"
|
||||
if (!TextUtils.isEmpty(sign)) webParams += "&sign=[sign]"
|
||||
}
|
||||
Log.d(TAG, "method = ${setting.method}, Url = $requestUrl")
|
||||
val postRequest = when (setting.method) {
|
||||
"PUT" -> XHttp.put(requestUrl).keepJson(true)
|
||||
"PATCH" -> XHttp.patch(requestUrl).keepJson(true)
|
||||
else -> XHttp.post(requestUrl).keepJson(true)
|
||||
}
|
||||
webParams.trim('&').split("&").forEach {
|
||||
val param = it.split("=")
|
||||
if (param.size == 2) {
|
||||
postRequest.params(
|
||||
param[0], param[1].replace("[from]", from)
|
||||
.replace("[content]", content)
|
||||
.replace("[msg]", content)
|
||||
.replace("[org_content]", orgContent)
|
||||
.replace("[device_mark]", deviceMark)
|
||||
.replace("[app_version]", appVersion)
|
||||
.replace("[title]", simInfo)
|
||||
.replace("[card_slot]", simInfo)
|
||||
.replace("[receive_time]", receiveTime)
|
||||
.replace("[timestamp]", timestamp.toString())
|
||||
.replace("[sign]", sign)
|
||||
)
|
||||
}
|
||||
}
|
||||
postRequest
|
||||
}
|
||||
|
||||
//添加headers
|
||||
for ((key, value) in setting.headers?.entries!!) {
|
||||
request.headers(key, value)
|
||||
}
|
||||
|
||||
//支持HTTP基本认证(Basic Authentication)
|
||||
if (matches.isNotEmpty()) {
|
||||
request.addInterceptor(BasicAuthInterceptor(matches[2], matches[3]))
|
||||
}
|
||||
|
||||
request.ignoreHttpsCert() //忽略https证书
|
||||
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
|
||||
.cacheMode(CacheMode.NO_CACHE)
|
||||
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
|
||||
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
|
||||
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
|
||||
.timeStamp(true)
|
||||
.execute(object : SimpleCallBack<String>() {
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
Log.e(TAG, e.detailMessage)
|
||||
SendUtils.updateLogs(logId, 0, e.displayMessage)
|
||||
}
|
||||
|
||||
override fun onSuccess(response: String) {
|
||||
Log.i(TAG, response)
|
||||
SendUtils.updateLogs(logId, 2, response)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
//JSON需要转义的字符
|
||||
private fun escapeJson(str: String?): String {
|
||||
if (str == null) return "null"
|
||||
val jsonStr: String = Gson().toJson(str)
|
||||
return if (jsonStr.length >= 2) jsonStr.substring(1, jsonStr.length - 1) else jsonStr
|
||||
}
|
||||
|
||||
fun sendMsg(setting: WebhookSetting, msgInfo: MsgInfo) {
|
||||
sendMsg(setting, msgInfo, null, null)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -120,7 +120,7 @@ class WeworkAgentUtils private constructor() {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val textMsgMap: MutableMap<String, Any> = mutableMapOf()
|
||||
|
@ -132,7 +132,7 @@ class WeworkAgentUtils private constructor() {
|
|||
val textText: MutableMap<String, Any> = mutableMapOf()
|
||||
textText["content"] = content
|
||||
textMsgMap["text"] = textText
|
||||
var accessToken: String by SharedPreference("access_token_" + setting.agentID, "")
|
||||
val accessToken: String by SharedPreference("access_token_" + setting.agentID, "")
|
||||
val requestUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=$accessToken"
|
||||
Log.i(TAG, "requestUrl:$requestUrl")
|
||||
val requestMsg: String = Gson().toJson(textMsgMap)
|
||||
|
|
|
@ -28,7 +28,7 @@ class WeworkRobotUtils private constructor() {
|
|||
val content: String = if (rule != null) {
|
||||
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
|
||||
} else {
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
|
||||
msgInfo.getContentForSend(SettingUtils.smsTemplate)
|
||||
}
|
||||
|
||||
val requestUrl = setting.webHook
|
||||
|
|
|
@ -340,7 +340,7 @@
|
|||
<string name="sim2_remark" tools:ignore="Typos">SIM2 SubId/Label</string>
|
||||
<string name="carrier_mobile" tools:ignore="Typos">Label of SIM,\neg. AT&T_88888888</string>
|
||||
<string name="tip_number_only_error_message">Number must be greater than 0!</string>
|
||||
<string name="regexp_number_only" tools:ignore="Typos">^[1-9]?\\d+$</string>
|
||||
<string name="regexp_number_only" tools:ignore="TypographyDashes,Typos">^[1-9]?\\d+$</string>
|
||||
<string name="low_power_alarm_threshold">Low Power Alarm</string>
|
||||
<string name="low_power_alarm_threshold_tips">Value range: 0–99.\nLeft blank or 0 is disabled</string>
|
||||
<string name="retry_interval">Retry Interval</string>
|
||||
|
|
|
@ -341,7 +341,7 @@
|
|||
<string name="sim2_remark" tools:ignore="Typos">SIM2主键/备注</string>
|
||||
<string name="carrier_mobile">序号/运营商_手机号</string>
|
||||
<string name="tip_number_only_error_message">数字必须大于0!</string>
|
||||
<string name="regexp_number_only" tools:ignore="Typos">^[1-9]?\\d+$</string>
|
||||
<string name="regexp_number_only" tools:ignore="TypographyDashes,Typos">^[1-9]?\\d+$</string>
|
||||
<string name="low_power_alarm_threshold">安全电量范围(%)</string>
|
||||
<string name="low_power_alarm_threshold_tips">超出安全范围将发出预警</string>
|
||||
<string name="retry_interval">请求重试机制</string>
|
||||
|
|
Loading…
Reference in New Issue