新增:钉钉企业内机器人发送通道

This commit is contained in:
pppscn 2022-07-15 01:02:01 +08:00
parent 56d83ed0aa
commit a7746f3b8d
18 changed files with 1110 additions and 34 deletions

View File

@ -10,7 +10,7 @@
短信转发器——不仅只转发短信,备用机必备神器!
监控Android手机短信、来电、APP通知并根据指定规则转发到其他手机钉钉机器人、企业微信群机器人、飞书机器人、企业微信应用消息、邮箱、bark、webhook、Telegram机器人、Server酱、PushPlus、手机短信等。
监控Android手机短信、来电、APP通知并根据指定规则转发到其他手机钉钉群自定义机器人、钉钉企业内机器人、企业微信群机器人、飞书机器人、企业微信应用消息、邮箱、bark、webhook、Telegram机器人、Server酱、PushPlus、手机短信等。
包括主动控制服务端与客户端让你轻松远程发短信、查短信、查通话、查话簿、查电量等。V3.0 新增)

View File

@ -23,7 +23,7 @@ data class Sender(
companion object {
fun getImageId(type: Int): Int = when (type) {
TYPE_DINGDING -> R.drawable.icon_dingtalk
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_EMAIL -> R.drawable.icon_email
TYPE_BARK -> R.drawable.icon_bark
TYPE_WEBHOOK -> R.drawable.icon_webhook
@ -35,6 +35,7 @@ data class Sender(
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
else -> R.drawable.icon_sms
}
@ -42,7 +43,7 @@ data class Sender(
val imageId: Int
get() = when (type) {
TYPE_DINGDING -> R.drawable.icon_dingtalk
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_EMAIL -> R.drawable.icon_email
TYPE_BARK -> R.drawable.icon_bark
TYPE_WEBHOOK -> R.drawable.icon_webhook
@ -54,6 +55,7 @@ data class Sender(
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
else -> R.drawable.icon_sms
}

View File

@ -0,0 +1,13 @@
package com.idormy.sms.forwarder.entity.result
data class DingtalkInnerRobotResult(
//获取access_token返回
var accessToken: String?,
var expireIn: Long?,
//消息id
var processQueryKey: String?,
//无效的用户userid列表
//var invalidStaffIdList: String[],
//被限流的userid列表
//var flowControlledStaffIdList: String[],
)

View File

@ -2,7 +2,7 @@ package com.idormy.sms.forwarder.entity.setting
import java.io.Serializable
data class DingtalkSetting(
data class DingtalkGroupRobotSetting(
var token: String = "",
var secret: String? = "",
var atAll: Boolean? = false,

View File

@ -0,0 +1,37 @@
package com.idormy.sms.forwarder.entity.setting
import com.idormy.sms.forwarder.R
import java.io.Serializable
import java.net.Proxy
data class DingtalkInnerRobotSetting(
val agentID: String = "",
val appKey: String = "",
val appSecret: String = "",
val userIds: String = "",
val msgKey: String = "sampleText",
val titleTemplate: String? = "",
val proxyType: Proxy.Type = Proxy.Type.DIRECT,
val proxyHost: String? = "",
val proxyPort: String? = "",
val proxyAuthenticator: Boolean? = false,
val proxyUsername: String? = "",
val proxyPassword: String? = "",
) : Serializable {
fun getProxyTypeCheckId(): Int {
return when (proxyType) {
Proxy.Type.HTTP -> R.id.rb_proxyHttp
Proxy.Type.SOCKS -> R.id.rb_proxySocks
else -> R.id.rb_proxyNone
}
}
fun getMsgTypeCheckId(): Int {
return if (msgKey == "sampleMarkdown") {
R.id.rb_msg_type_markdown
} else {
R.id.rb_msg_type_text
}
}
}

View File

@ -95,7 +95,7 @@ class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAda
R.id.iv_copy -> {
PageOption.to(
when (item.type) {
TYPE_DINGDING -> DingtalkFragment::class.java
TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java
TYPE_EMAIL -> EmailFragment::class.java
TYPE_BARK -> BarkFragment::class.java
TYPE_WEBHOOK -> WebhookFragment::class.java
@ -107,7 +107,8 @@ class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAda
TYPE_FEISHU -> FeishuFragment::class.java
TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java
else -> DingtalkFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)
.putLong(KEY_SENDER_ID, item.id)
@ -118,7 +119,7 @@ class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAda
R.id.iv_edit -> {
PageOption.to(
when (item.type) {
TYPE_DINGDING -> DingtalkFragment::class.java
TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java
TYPE_EMAIL -> EmailFragment::class.java
TYPE_BARK -> BarkFragment::class.java
TYPE_WEBHOOK -> WebhookFragment::class.java
@ -130,7 +131,8 @@ class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAda
TYPE_FEISHU -> FeishuFragment::class.java
TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java
else -> DingtalkFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)
.putLong(KEY_SENDER_ID, item.id)

View File

@ -15,11 +15,11 @@ import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
import com.idormy.sms.forwarder.databinding.FragmentSendersDingtalkBinding
import com.idormy.sms.forwarder.databinding.FragmentSendersDingtalkGroupRobotBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.DingtalkSetting
import com.idormy.sms.forwarder.entity.setting.DingtalkGroupRobotSetting
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.sender.DingtalkUtils
import com.idormy.sms.forwarder.utils.sender.DingtalkGroupRobotUtils
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
@ -34,11 +34,11 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.*
@Page(name = "钉钉机器人")
@Page(name = "钉钉机器人")
@Suppress("PrivatePropertyName")
class DingtalkFragment : BaseFragment<FragmentSendersDingtalkBinding?>(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
class DingtalkGroupRobotFragment : BaseFragment<FragmentSendersDingtalkGroupRobotBinding?>(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
private val TAG: String = DingtalkFragment::class.java.simpleName
private val TAG: String = DingtalkGroupRobotFragment::class.java.simpleName
var titleBar: TitleBar? = null
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
private var mCountDownHelper: CountDownButtonHelper? = null
@ -62,8 +62,8 @@ class DingtalkFragment : BaseFragment<FragmentSendersDingtalkBinding?>(), View.O
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentSendersDingtalkBinding {
return FragmentSendersDingtalkBinding.inflate(inflater, container, false)
): FragmentSendersDingtalkGroupRobotBinding {
return FragmentSendersDingtalkGroupRobotBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
@ -117,7 +117,7 @@ class DingtalkFragment : BaseFragment<FragmentSendersDingtalkBinding?>(), View.O
}
binding!!.etName.setText(sender.name)
binding!!.sbEnable.isChecked = sender.status == 1
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkSetting::class.java)
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkGroupRobotSetting::class.java)
Log.d(TAG, settingVo.toString())
if (settingVo != null) {
binding!!.etToken.setText(settingVo.token)
@ -164,7 +164,7 @@ class DingtalkFragment : BaseFragment<FragmentSendersDingtalkBinding?>(), View.O
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info))
DingtalkUtils.sendMsg(settingVo, msgInfo)
DingtalkGroupRobotUtils.sendMsg(settingVo, msgInfo)
} catch (e: Exception) {
e.printStackTrace()
if (Looper.myLooper() == null) Looper.prepare()
@ -217,7 +217,7 @@ class DingtalkFragment : BaseFragment<FragmentSendersDingtalkBinding?>(), View.O
}
}
private fun checkSetting(): DingtalkSetting {
private fun checkSetting(): DingtalkGroupRobotSetting {
val token = binding!!.etToken.text.toString().trim()
if (CommonUtils.checkUrl(token, true)) {
throw Exception(getString(R.string.invalid_token))
@ -230,7 +230,7 @@ class DingtalkFragment : BaseFragment<FragmentSendersDingtalkBinding?>(), View.O
throw Exception(getString(R.string.invalid_at_mobiles))
}*/
return DingtalkSetting(token, secret, atAll, atMobiles)
return DingtalkGroupRobotSetting(token, secret, atAll, atMobiles)
}
override fun onDestroyView() {

View File

@ -0,0 +1,299 @@
package com.idormy.sms.forwarder.fragment.senders
import android.annotation.SuppressLint
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.RadioGroup
import androidx.fragment.app.viewModels
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
import com.idormy.sms.forwarder.databinding.FragmentSendersDingtalkInnerRobotBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.DingtalkInnerRobotSetting
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.sender.DingtalkInnerRobotUtils
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.net.Proxy
import java.util.*
@Page(name = "钉钉企业机器人")
@Suppress("PrivatePropertyName")
class DingtalkInnerRobotFragment : BaseFragment<FragmentSendersDingtalkInnerRobotBinding?>(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
private val TAG: String = DingtalkInnerRobotFragment::class.java.simpleName
var titleBar: TitleBar? = null
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
private var mCountDownHelper: CountDownButtonHelper? = null
@JvmField
@AutoWired(name = KEY_SENDER_ID)
var senderId: Long = 0
@JvmField
@AutoWired(name = KEY_SENDER_TYPE)
var senderType: Int = 0
@JvmField
@AutoWired(name = KEY_SENDER_CLONE)
var isClone: Boolean = false
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentSendersDingtalkInnerRobotBinding {
return FragmentSendersDingtalkInnerRobotBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.dingtalk_inner_robot)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, SettingUtils.requestTimeout)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
binding!!.btnTest.text = getString(R.string.test)
}
})
//新增
if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender))
binding!!.btnDel.setText(R.string.discard)
return
}
//编辑
binding!!.btnDel.setText(R.string.del)
AppDatabase.getInstance(requireContext())
.senderDao()
.get(senderId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<Sender> {
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onSuccess(sender: Sender) {
if (isClone) {
titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name)
binding!!.btnDel.setText(R.string.discard)
} else {
titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name)
}
binding!!.etName.setText(sender.name)
binding!!.sbEnable.isChecked = sender.status == 1
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkInnerRobotSetting::class.java)
Log.d(TAG, settingVo.toString())
if (settingVo != null) {
binding!!.etAgentID.setText(settingVo.agentID)
binding!!.etAppKey.setText(settingVo.appKey)
binding!!.etAppSecret.setText(settingVo.appSecret)
binding!!.etUserIds.setText(settingVo.userIds)
binding!!.rgMsgType.check(settingVo.getMsgTypeCheckId())
binding!!.etTitleTemplate.setText(settingVo.titleTemplate)
binding!!.rgProxyType.check(settingVo.getProxyTypeCheckId())
binding!!.etProxyHost.setText(settingVo.proxyHost)
binding!!.etProxyPort.setText(settingVo.proxyPort)
binding!!.sbProxyAuthenticator.isChecked = settingVo.proxyAuthenticator == true
binding!!.etProxyUsername.setText(settingVo.proxyUsername)
binding!!.etProxyPassword.setText(settingVo.proxyPassword)
}
}
})
}
override fun initListeners() {
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
binding!!.sbProxyAuthenticator.setOnCheckedChangeListener(this)
binding!!.rgProxyType.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
if (checkedId == R.id.rb_proxyHttp || checkedId == R.id.rb_proxySocks) {
binding!!.layoutProxyHost.visibility = View.VISIBLE
binding!!.layoutProxyPort.visibility = View.VISIBLE
binding!!.layoutProxyAuthenticator.visibility = if (binding!!.sbProxyAuthenticator.isChecked) View.VISIBLE else View.GONE
} else {
binding!!.layoutProxyHost.visibility = View.GONE
binding!!.layoutProxyPort.visibility = View.GONE
binding!!.layoutProxyAuthenticator.visibility = View.GONE
}
}
binding!!.rgMsgType.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
binding!!.layoutCustomTemplate.visibility = if (checkedId == R.id.rb_msg_type_markdown) View.VISIBLE else View.GONE
}
}
@SuppressLint("SetTextI18n")
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
when (buttonView.id) {
R.id.sb_proxyAuthenticator -> {
binding!!.layoutProxyAuthenticator.visibility = if (isChecked) View.VISIBLE else View.GONE
}
else -> {}
}
}
@SingleClick
override fun onClick(v: View) {
try {
val etTitleTemplate: EditText = binding!!.etTitleTemplate
when (v.id) {
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> {
mCountDownHelper?.start()
Thread {
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info))
DingtalkInnerRobotUtils.sendMsg(settingVo, msgInfo)
} catch (e: Exception) {
e.printStackTrace()
if (Looper.myLooper() == null) Looper.prepare()
XToastUtils.error(e.message.toString())
Looper.loop()
}
}.start()
return
}
R.id.btn_del -> {
if (senderId <= 0 || isClone) {
popToBack()
return
}
MaterialDialog.Builder(requireContext())
.title(R.string.delete_sender_title)
.content(R.string.delete_sender_tips)
.positiveText(R.string.lab_yes)
.negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(senderId)
XToastUtils.success(R.string.delete_sender_toast)
popToBack()
}
.show()
return
}
R.id.btn_save -> {
val name = binding!!.etName.text.toString().trim()
if (TextUtils.isEmpty(name)) {
throw Exception(getString(R.string.invalid_name))
}
val status = if (binding!!.sbEnable.isChecked) 1 else 0
val settingVo = checkSetting()
if (isClone) senderId = 0
val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status)
Log.d(TAG, senderNew.toString())
viewModel.insertOrUpdate(senderNew)
XToastUtils.success(R.string.tipSaveSuccess)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
e.printStackTrace()
}
}
private fun checkSetting(): DingtalkInnerRobotSetting {
val agentID = binding!!.etAgentID.text.toString().trim()
val appKey = binding!!.etAppKey.text.toString().trim()
val appSecret = binding!!.etAppSecret.text.toString().trim()
val userIds = binding!!.etUserIds.text.toString().trim()
if (TextUtils.isEmpty(agentID) || TextUtils.isEmpty(appKey) || TextUtils.isEmpty(appSecret) || TextUtils.isEmpty(userIds)) {
throw Exception(getString(R.string.invalid_dingtalk_inner_robot))
}
val proxyType: Proxy.Type = when (binding!!.rgProxyType.checkedRadioButtonId) {
R.id.rb_proxyHttp -> Proxy.Type.HTTP
R.id.rb_proxySocks -> Proxy.Type.SOCKS
else -> Proxy.Type.DIRECT
}
val proxyHost = binding!!.etProxyHost.text.toString().trim()
val proxyPort = binding!!.etProxyPort.text.toString().trim()
if (proxyType != Proxy.Type.DIRECT && (TextUtils.isEmpty(proxyHost) || TextUtils.isEmpty(proxyPort))) {
throw Exception(getString(R.string.invalid_host_or_port))
}
val proxyAuthenticator = binding!!.sbProxyAuthenticator.isChecked
val proxyUsername = binding!!.etProxyUsername.text.toString().trim()
val proxyPassword = binding!!.etProxyPassword.text.toString().trim()
if (proxyAuthenticator && TextUtils.isEmpty(proxyUsername) && TextUtils.isEmpty(proxyPassword)) {
throw Exception(getString(R.string.invalid_username_or_password))
}
val msgKey = if (binding!!.rgMsgType.checkedRadioButtonId == R.id.rb_msg_type_markdown) "sampleMarkdown" else "sampleText"
val titleTemplate = binding!!.etTitleTemplate.text.toString().trim()
return DingtalkInnerRobotSetting(agentID, appKey, appSecret, userIds, msgKey, titleTemplate, proxyType, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword)
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
}

View File

@ -148,7 +148,7 @@ val BARK_LEVEL_MAP = mapOf(
)
//发送通道
const val TYPE_DINGDING = 0
const val TYPE_DINGTALK_GROUP_ROBOT = 0
const val TYPE_EMAIL = 1
const val TYPE_BARK = 2
const val TYPE_WEBHOOK = 3
@ -160,8 +160,9 @@ const val TYPE_SMS = 8
const val TYPE_FEISHU = 9
const val TYPE_PUSHPLUS = 10
const val TYPE_GOTIFY = 11
const val TYPE_DINGTALK_INNER_ROBOT = 12
var SENDER_FRAGMENT_LIST = listOf(
PageInfo(getString(R.string.dingtalk_robot), "com.idormy.sms.forwarder.fragment.senders.DingtalkFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_dingtalk),
PageInfo(getString(R.string.dingtalk_robot), "com.idormy.sms.forwarder.fragment.senders.DingtalkGroupRobotFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_dingtalk),
PageInfo(getString(R.string.email), "com.idormy.sms.forwarder.fragment.senders.EmailFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_email),
PageInfo(getString(R.string.bark), "com.idormy.sms.forwarder.fragment.senders.BarkFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_bark),
PageInfo(getString(R.string.webhook), "com.idormy.sms.forwarder.fragment.senders.WebhookFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_webhook),
@ -173,6 +174,7 @@ var SENDER_FRAGMENT_LIST = listOf(
PageInfo(getString(R.string.feishu), "com.idormy.sms.forwarder.fragment.senders.FeishuFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_feishu),
PageInfo(getString(R.string.pushplus), "com.idormy.sms.forwarder.fragment.senders.PushplusFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_pushplus),
PageInfo(getString(R.string.gotify), "com.idormy.sms.forwarder.fragment.senders.GotifyFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_gotify),
PageInfo(getString(R.string.dingtalk_inner_robot), "com.idormy.sms.forwarder.fragment.senders.DingtalkInnerRobotFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_dingtalk_inner),
)
//前台服务

View File

@ -3,6 +3,7 @@ package com.idormy.sms.forwarder.utils
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.Core
@ -136,7 +137,11 @@ class HttpServerUtils private constructor() {
}
val sign = calcSign(req.timestamp.toString(), signSecret.toString())
if (sign != req.sign) throw HttpException(500, getString(R.string.sign_verify_failed))
if (sign != req.sign) {
Log.e("calcSign", sign)
Log.e("reqSign", req.sign.toString())
throw HttpException(500, getString(R.string.sign_verify_failed))
}
}
//判断版本是否一致

View File

@ -71,9 +71,9 @@ object SendUtils {
fun sendMsgSender(msgInfo: MsgInfo, rule: Rule, sender: Sender, logId: Long) {
try {
when (sender.type) {
TYPE_DINGDING -> {
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkSetting::class.java)
DingtalkUtils.sendMsg(settingVo, msgInfo, rule, logId)
TYPE_DINGTALK_GROUP_ROBOT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkGroupRobotSetting::class.java)
DingtalkGroupRobotUtils.sendMsg(settingVo, msgInfo, rule, logId)
}
TYPE_EMAIL -> {
val settingVo = Gson().fromJson(sender.jsonSetting, EmailSetting::class.java)

View File

@ -7,7 +7,7 @@ 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.DingtalkResult
import com.idormy.sms.forwarder.entity.setting.DingtalkSetting
import com.idormy.sms.forwarder.entity.setting.DingtalkGroupRobotSetting
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.xuexiang.xhttp2.XHttp
@ -19,14 +19,15 @@ import java.nio.charset.StandardCharsets
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
//钉钉群自定义机器人
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER")
class DingtalkUtils private constructor() {
class DingtalkGroupRobotUtils private constructor() {
companion object {
private val TAG: String = DingtalkUtils::class.java.simpleName
private val TAG: String = DingtalkGroupRobotUtils::class.java.simpleName
fun sendMsg(
setting: DingtalkSetting,
setting: DingtalkGroupRobotSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
@ -114,7 +115,7 @@ class DingtalkUtils private constructor() {
}
fun sendMsg(setting: DingtalkSetting, msgInfo: MsgInfo) {
fun sendMsg(setting: DingtalkGroupRobotSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null)
}
}

View File

@ -0,0 +1,242 @@
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.R
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.result.DingtalkInnerRobotResult
import com.idormy.sms.forwarder.entity.setting.DingtalkInnerRobotSetting
import com.idormy.sms.forwarder.utils.MMKVUtils
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.xui.utils.ResUtils.getString
import com.xuexiang.xutil.net.NetworkUtils
import okhttp3.Credentials
import okhttp3.Response
import okhttp3.Route
import java.net.Authenticator
import java.net.InetSocketAddress
import java.net.PasswordAuthentication
import java.net.Proxy
//钉钉企业内机器人
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER")
class DingtalkInnerRobotUtils private constructor() {
companion object {
private val TAG: String = DingtalkInnerRobotUtils::class.java.simpleName
fun sendMsg(
setting: DingtalkInnerRobotSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
) {
val accessToken: String? = MMKVUtils.getString("accessToken_" + setting.agentID, "")
val expiresIn: Long = MMKVUtils.getLong("expiresIn_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId)
}
val requestUrl = "https://api.dingtalk.com/v1.0/oauth2/accessToken"
Log.d(TAG, "requestUrl$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf()
msgMap["appKey"] = setting.appKey
msgMap["appSecret"] = setting.appSecret
val requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
val request = XHttp.post(requestUrl)
//设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS)
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
//代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890
Log.d(TAG, "proxyHost = $proxyHost, proxyPort = $proxyPort")
request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort)))
//代理的鉴权账号密码
if (setting.proxyAuthenticator == true
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
} else {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(setting.proxyUsername.toString(), setting.proxyPassword?.toCharArray())
}
})
}
}
}
request.upJson(requestMsg)
.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.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, DingtalkInnerRobotResult::class.java)
if (!TextUtils.isEmpty(resp.accessToken)) {
MMKVUtils.put("accessToken_" + setting.agentID, resp.accessToken)
MMKVUtils.put("expiresIn_" + setting.agentID, System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L) //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId)
} else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
}
}
})
}
//发送文本消息
private fun sendTextMsg(
setting: DingtalkInnerRobotSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
) {
val requestUrl = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend"
Log.d(TAG, "requestUrl$requestUrl")
val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
} else {
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
}
val msgParam: MutableMap<String, Any> = mutableMapOf()
if ("sampleMarkdown" == setting.msgKey) {
msgParam["title"] = if (rule != null) {
msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace)
} else {
msgInfo.getTitleForSend(setting.titleTemplate.toString())
}
msgParam["text"] = content
} else {
msgParam["content"] = content
}
val textMsgMap: MutableMap<String, Any> = mutableMapOf()
textMsgMap["robotCode"] = setting.appKey
textMsgMap["userIds"] = setting.userIds.split('|').toTypedArray()
textMsgMap["msgKey"] = setting.msgKey
textMsgMap["msgParam"] = Gson().toJson(msgParam)
val requestMsg: String = Gson().toJson(textMsgMap)
Log.i(TAG, "requestMsg:$requestMsg")
val request = XHttp.post(requestUrl)
//设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS)
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
//代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890
Log.d(TAG, "proxyHost = $proxyHost, proxyPort = $proxyPort")
request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort)))
//代理的鉴权账号密码
if (setting.proxyAuthenticator == true
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
} else {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(setting.proxyUsername.toString(), setting.proxyPassword?.toCharArray())
}
})
}
}
}
request.upJson(requestMsg)
.headers("x-acs-dingtalk-access-token", MMKVUtils.getString("accessToken_" + setting.agentID, ""))
.keepJson(true)
.ignoreHttpsCert()
.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, DingtalkInnerRobotResult::class.java)
if (!TextUtils.isEmpty(resp.processQueryKey)) {
SendUtils.updateLogs(logId, 2, response)
} else {
SendUtils.updateLogs(logId, 0, response)
}
}
})
}
fun sendMsg(setting: DingtalkInnerRobotSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,459 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/xui_config_color_background"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical">
<LinearLayout
style="@style/senderBarStyleWithSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sender_name_status"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_clearButton="true" />
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true" />
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="end"
android:maxLines="1"
android:text="@string/Agent_ID"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_agentID"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_clearButton="true" />
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/appkey"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_appKey"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_passWordButton="true" />
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/appsecret"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_appSecret"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_passWordButton="true" />
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/specified_member"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/specified_member_tips2"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_userIds"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
app:met_clearButton="true" />
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/feishu_msg_type"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_msg_type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_msg_type_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:text="@string/sampleText" />
<RadioButton
android:id="@+id/rb_msg_type_markdown"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sampleMarkdown" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_custom_template"
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_template"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/custom_template_tips"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_title_template"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
app:met_clearButton="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/bt_insert_sender"
style="@style/insertButtonStyle"
android:text="@string/insert_sender" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/bt_insert_extra"
style="@style/insertButtonStyle"
android:layout_marginStart="5dp"
android:text="@string/insert_extra" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/bt_insert_time"
style="@style/insertButtonStyle"
android:layout_marginStart="5dp"
android:text="@string/insert_time" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/bt_insert_device_name"
style="@style/insertButtonStyle"
android:layout_marginStart="5dp"
android:text="@string/insert_device_name" />
</LinearLayout>
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/proxy_settings"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_proxyType"
style="@style/rg_style"
android:layout_marginStart="5dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_proxyNone"
style="@style/rg_rb_style"
android:checked="true"
android:text="@string/proxy_none" />
<RadioButton
android:id="@+id/rb_proxyHttp"
style="@style/rg_rb_style"
android:text="@string/proxy_http" />
<RadioButton
android:id="@+id/rb_proxySocks"
style="@style/rg_rb_style"
android:text="@string/proxy_socks" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutProxyHost"
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hostname" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_proxyHost"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_clearButton="true" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutProxyPort"
style="@style/senderBarStyleWithSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/port" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_proxyPort"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:digits="0123456789"
android:inputType="number"
android:maxLength="5"
android:singleLine="true"
app:met_clearButton="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="@string/proxy_authenticator" />
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_proxyAuthenticator"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutProxyAuthenticator"
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/username" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_proxyUsername"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_clearButton="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/password" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_proxyPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_passWordButton="true" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp">
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_del"
style="@style/SuperButton.Gray.Icon"
android:drawableStart="@drawable/icon_delete"
android:paddingStart="15dp"
android:text="@string/del"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_save"
style="@style/SuperButton.Blue.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_save"
android:paddingStart="15dp"
android:text="@string/save"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_test"
style="@style/SuperButton.Green.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_test"
android:paddingStart="15dp"
android:text="@string/test"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</LinearLayout>

View File

@ -286,6 +286,7 @@
<string name="invalid_webhook">WebHook is empty or not a valid URL</string>
<string name="invalid_at_mobiles">Specified member cannot be empty or select @all</string>
<string name="invalid_wework_agent">CoreID, AgentID, and Secret cannot be empty</string>
<string name="invalid_dingtalk_inner_robot">AgentId, AppKey, AppSecret, and UserIds cannot be empty</string>
<string name="invalid_phone_num">The receiving phone number cannot be empty</string>
<string name="invalid_multi_match">Malformed multiple match rule line %s</string>
<string name="invalid_regex_replace">Incorrect format on line %s of regex replacement</string>
@ -314,6 +315,7 @@
<string name="specified_member">Specified Member</string>
<string name="at_all">\@all</string>
<string name="specified_member_tips">Tip: List of member IDs that receive messages (multiple recipients are separated by \'|\', up to 1000)</string>
<string name="specified_member_tips2">Tip: The userid of the user who receives the message, up to 20 at a time (separated by \'|\')</string>
<string name="server_chan_send_key">SendKey</string>
<string name="server_chan_channel">Message channel</string>
<string name="server_chan_channel_tips">Tip: Dynamically specified, supports up to two channels, separated by a vertical bar |</string>
@ -465,7 +467,8 @@
<string name="priority">Priority1 9</string>
<string name="_5">5</string>
<string name="sb_rule_status">Enable this rule</string>
<string name="dingtalk_robot">Dingtalk Bot</string>
<string name="dingtalk_robot">Dingtalk Group Bot</string>
<string name="dingtalk_inner_robot">Dingtalk Inner Bot</string>
<string name="email">Email</string>
<string name="bark">Bark</string>
<string name="webhook">Webhook</string>
@ -874,4 +877,8 @@
<string name="silent_time_period">Silent (disable forwarding) time period</string>
<string name="silent_time_period_tips">If the end time is less than the start time, it will span days; if it is equal, it will be disabled</string>
<string name="download_frpc_tips">Do you want to download and restart to load!</string>
<string name="appkey">AppKey</string>
<string name="appsecret">AppSecret</string>
<string name="sampleText">Sample Text</string>
<string name="sampleMarkdown">Sample Markdown</string>
</resources>

View File

@ -256,7 +256,7 @@
<string name="delete_sender_toast">该条发送通道已经删除!</string>
<string name="add_sender_title">选择发送通道类型</string>
<string name="not_supported">暂不支持这种转发</string>
<string name="setdingdingtitle">设置钉钉机器人</string>
<string name="setdingdingtitle">设置钉钉机器人</string>
<string name="setemailtitle">设置邮箱</string>
<string name="setwebnotifytitle">设置Webhook</string>
<string name="setqywxgrouprobottitle">设置企业微信群机器人</string>
@ -287,6 +287,7 @@
<string name="invalid_webhook">WebHook为空 或 不是有效URL</string>
<string name="invalid_at_mobiles">指定成员 不能为空 或者 选择@all</string>
<string name="invalid_wework_agent">企业ID、AgentID、Secret都不能为空</string>
<string name="invalid_dingtalk_inner_robot">AgentId、AppKey、AppSecret、UserIds都不能为空</string>
<string name="invalid_phone_num">接收手机号不能为空</string>
<string name="invalid_multi_match">多重匹配规则的第 %s 行格式有误</string>
<string name="invalid_regex_replace">正则替换内容的第 %s 行格式有误</string>
@ -315,6 +316,7 @@
<string name="specified_member">指定成员</string>
<string name="at_all">\@all</string>
<string name="specified_member_tips">Tip接收消息的成员ID列表多个接收者用|分隔最多支持1000个</string>
<string name="specified_member_tips2">Tip接收用户的userid每次最多传20个|’分隔)</string>
<string name="server_chan_send_key">SendKey</string>
<string name="server_chan_channel">消息通道</string>
<string name="server_chan_channel_tips">提示:动态指定,支持最多两个通道,用竖线|隔开</string>
@ -466,7 +468,8 @@
<string name="priority">优先级1 9</string>
<string name="_5">5</string>
<string name="sb_rule_status">启用该条转发规则</string>
<string name="dingtalk_robot">钉钉机器人</string>
<string name="dingtalk_robot">钉钉群机器人</string>
<string name="dingtalk_inner_robot">钉钉企业机器人</string>
<string name="email">电子邮箱</string>
<string name="bark">Bark</string>
<string name="webhook">Webhook</string>
@ -875,4 +878,8 @@
<string name="silent_time_period">免打扰(禁用转发)时间段</string>
<string name="silent_time_period_tips">结束时间小于开始时间则跨天;相等则禁用</string>
<string name="download_frpc_tips">是否立即下载,并重启加载?</string>
<string name="appkey">AppKey</string>
<string name="appsecret">AppSecret</string>
<string name="sampleText">文本类型</string>
<string name="sampleMarkdown">Markdown类型</string>
</resources>