新增:`飞书企业应用`发送通道

This commit is contained in:
pppscn 2022-08-18 15:45:36 +08:00
parent 84d321dba8
commit 17df392ae4
15 changed files with 741 additions and 12 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

@ -10,7 +10,7 @@
SmsForwarder - Not only forwarding text messages, but also a must-have for backup devices!
listens to SMS, incoming calls, and App notifications on Android mobile devices, and forward according to user defined rules to another App/device, including DingTalk, WeCom and WeCom Group Bot, Feishi Bot, E-mail, Bark, Webhook, Telegram Bot, ServerChan, PushPlus, SMS, etc.
listens to SMS, incoming calls, and App notifications on Android mobile devices, and forward according to user defined rules to another App/device, including DingTalk, WeCom and WeCom Group Bot, Feishu App and Feishu Group Bot, E-mail, Bark, Webhook, Telegram Bot, ServerChan, PushPlus, SMS, etc.
Including active control of the server and client, allowing you to easily and remotely send text messages, check text messages, check calls, check the phone book, check the battery, etc.

View File

@ -36,6 +36,7 @@ data class Sender(
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
else -> R.drawable.icon_sms
}
@ -56,6 +57,7 @@ data class Sender(
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
else -> R.drawable.icon_sms
}

View File

@ -0,0 +1,11 @@
package com.idormy.sms.forwarder.entity.result
data class FeishuAppResult(
var code: Long,
var msg: String,
//获取access_token返回
var tenant_access_token: String?,
var expire: Long?,
//发送接口返回
var content: String?,
)

View File

@ -0,0 +1,21 @@
package com.idormy.sms.forwarder.entity.setting
import com.idormy.sms.forwarder.R
import java.io.Serializable
data class FeishuAppSetting(
var appId: String = "",
val appSecret: String = "",
val receiveId: String = "",
val msgType: String = "interactive",
val titleTemplate: String = "",
) : Serializable {
fun getMsgTypeCheckId(): Int {
return if (msgType == null || msgType == "interactive") {
R.id.rb_msg_type_interactive
} else {
R.id.rb_msg_type_text
}
}
}

View File

@ -108,6 +108,7 @@ class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAda
TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)
@ -132,6 +133,7 @@ class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAda
TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)

View File

@ -0,0 +1,246 @@
package com.idormy.sms.forwarder.fragment.senders
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.EditText
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.FragmentSendersFeishuAppBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.FeishuAppSetting
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.sender.FeishuAppUtils
import com.jeremyliao.liveeventbus.LiveEventBus
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.util.*
@Page(name = "飞书企业应用")
@Suppress("PrivatePropertyName")
class FeishuAppFragment : BaseFragment<FragmentSendersFeishuAppBinding?>(), View.OnClickListener {
private val TAG: String = FeishuAppFragment::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,
): FragmentSendersFeishuAppBinding {
return FragmentSendersFeishuAppBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.feishu_app)
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, FeishuAppSetting::class.java)
Log.d(TAG, settingVo.toString())
if (settingVo != null) {
binding!!.etAppId.setText(settingVo.appId)
binding!!.etAppSecret.setText(settingVo.appSecret)
binding!!.etUserId.setText(settingVo.receiveId)
binding!!.rgMsgType.check(settingVo.getMsgTypeCheckId())
binding!!.etTitleTemplate.setText(settingVo.titleTemplate)
}
}
})
}
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)
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).observe(this) { mCountDownHelper?.finish() }
}
@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))
FeishuAppUtils.sendMsg(settingVo, msgInfo)
} catch (e: Exception) {
e.printStackTrace()
if (Looper.myLooper() == null) Looper.prepare()
XToastUtils.error(e.message.toString())
Looper.loop()
}
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).post("finish")
}.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(): FeishuAppSetting {
val appId = binding!!.etAppId.text.toString().trim()
val appSecret = binding!!.etAppSecret.text.toString().trim()
val receiveId = binding!!.etUserId.text.toString().trim()
if (TextUtils.isEmpty(appId) || TextUtils.isEmpty(appSecret) || TextUtils.isEmpty(receiveId)) {
throw Exception(getString(R.string.invalid_feishu_app_parameter))
}
val msgType = if (binding!!.rgMsgType.checkedRadioButtonId == R.id.rb_msg_type_interactive) "interactive" else "text"
val title = binding!!.etTitleTemplate.text.toString().trim()
return FeishuAppSetting(appId, appSecret, receiveId, msgType, title)
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
}

View File

@ -35,7 +35,7 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.*
@Page(name = "飞书机器人")
@Page(name = "飞书机器人")
@Suppress("PrivatePropertyName")
class FeishuFragment : BaseFragment<FragmentSendersFeishuBinding?>(), View.OnClickListener {

View File

@ -161,6 +161,7 @@ const val TYPE_FEISHU = 9
const val TYPE_PUSHPLUS = 10
const val TYPE_GOTIFY = 11
const val TYPE_DINGTALK_INNER_ROBOT = 12
const val TYPE_FEISHU_APP = 13
var SENDER_FRAGMENT_LIST = listOf(
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),
@ -175,6 +176,7 @@ var SENDER_FRAGMENT_LIST = listOf(
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),
PageInfo(getString(R.string.feishu_app), "com.idormy.sms.forwarder.fragment.senders.FeishuAppFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_feishu_app),
)
//前台服务

View File

@ -123,6 +123,10 @@ object SendUtils {
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkInnerRobotSetting::class.java)
DingtalkInnerRobotUtils.sendMsg(settingVo, msgInfo, rule, logId)
}
TYPE_FEISHU_APP -> {
val settingVo = Gson().fromJson(sender.jsonSetting, FeishuAppSetting::class.java)
FeishuAppUtils.sendMsg(settingVo, msgInfo, rule, logId)
}
else -> {
updateLogs(logId, 0, "未知发送通道")
}

View File

@ -0,0 +1,163 @@
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.FeishuAppResult
import com.idormy.sms.forwarder.entity.setting.FeishuAppSetting
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
//飞书企业应用
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER")
class FeishuAppUtils private constructor() {
companion object {
private val TAG: String = FeishuAppUtils::class.java.simpleName
fun sendMsg(
setting: FeishuAppSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
) {
val accessToken: String? = MMKVUtils.getString("feishu_access_token_" + setting.appId, "")
val expiresIn: Long = MMKVUtils.getLong("feishu_expires_in_" + setting.appId, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId)
}
val requestUrl = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
Log.d(TAG, "requestUrl$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf()
msgMap["app_id"] = setting.appId
msgMap["app_secret"] = setting.appSecret
val requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.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, FeishuAppResult::class.java)
if (!TextUtils.isEmpty(resp.tenant_access_token)) {
MMKVUtils.put("feishu_access_token_" + setting.appId, resp.tenant_access_token)
MMKVUtils.put("feishu_expires_in_" + setting.appId, System.currentTimeMillis() + ((resp.expire ?: 7010) - 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: FeishuAppSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
) {
val requestUrl = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id"
Log.d(TAG, "requestUrl$requestUrl")
val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
} else {
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
}
val msgContent = if ("interactive" == setting.msgType) {
val title = if (rule != null) {
msgInfo.getTitleForSend(setting.titleTemplate, rule.regexReplace)
} else {
msgInfo.getTitleForSend(setting.titleTemplate)
}
"{\"elements\":[{\"tag\":\"markdown\",\"content\":\"**[{{MSG_TITLE}}]({{MSG_URL}})**\\n --------------\\n{{MSG_CONTENT}}\"}]}".trimIndent().replace("{{MSG_TITLE}}", jsonInnerStr(title))
.replace("{{MSG_URL}}", jsonInnerStr("https://github.com/pppscn/SmsForwarder"))
.replace("{{MSG_CONTENT}}", jsonInnerStr(content))
} else {
"{\"text\":\"{{MSG_CONTENT}}\"}".trimIndent().replace("{{MSG_CONTENT}}", jsonInnerStr(content))
}
val textMsgMap: MutableMap<String, Any> = mutableMapOf()
textMsgMap["receive_id"] = setting.receiveId
textMsgMap["msg_type"] = setting.msgType
textMsgMap["content"] = msgContent
val requestMsg: String = Gson().toJson(textMsgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
.headers("Authorization", "Bearer " + MMKVUtils.getString("feishu_access_token_" + setting.appId, ""))
.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)
//Log.d(TAG, "tlsVersion=" + response.handshake().tlsVersion())
//Log.d(TAG, "cipherSuite=" + response.handshake().cipherSuite().toString())
val resp = Gson().fromJson(response, FeishuAppResult::class.java)
if (resp.code == 0L) {
SendUtils.updateLogs(logId, 2, response)
} else {
SendUtils.updateLogs(logId, 0, response)
}
}
})
}
fun sendMsg(setting: FeishuAppSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null)
}
private fun jsonInnerStr(string: String?): String {
if (string == null) return "null"
val jsonStr: String = Gson().toJson(string)
return if (jsonStr.length >= 2) jsonStr.substring(1, jsonStr.length - 1) else jsonStr
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,268 @@
<?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="75dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/app_id"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_app_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
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="75dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/app_secret"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_app_secret"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
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="75dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/user_id"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_user_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
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="75dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/feishu_msg_type"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_msg_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_msg_type_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/feishu_msg_type_text" />
<RadioButton
android:id="@+id/rb_msg_type_interactive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/feishu_msg_type_interactive" />
</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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
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>
</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

@ -485,6 +485,7 @@
<string name="telegram">Telegram Bot</string>
<string name="sms_menu">SMS</string>
<string name="feishu">FeiShu Bot</string>
<string name="feishu_app">FeiShu App</string>
<string name="pushplus">PushPlus</string>
<string name="gotify">Gotify</string>
<string name="_0">0</string>
@ -892,20 +893,24 @@
<string name="sampleMarkdown">Markdown</string>
<string name="ip_hint">IP address/broadcast address, eg. 192.168.1.255</string>
<string name="ip_error">Malformed IP address, eg. 192.168.168.168</string>
<string name="ip_regex">^((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])$</string>
<string name="ip_regex" tools:ignore="TypographyDashes">^((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])$</string>
<string name="mac_hint">Network card mac, eg. AA:BB:CC:DD:EE:FF</string>
<string name="mac_error">The network card mac format is incorrect, eg. AA:BB:CC:DD:EE:FF</string>
<string name="mac_regex">^((([a-fA-F0-9]{2}:){5})|(([a-fA-F0-9]{2}-){5}))[a-fA-F0-9]{2}$</string>
<string name="mac_regex" tools:ignore="TypographyDashes">^((([a-fA-F0-9]{2}:){5})|(([a-fA-F0-9]{2}-){5}))[a-fA-F0-9]{2}$</string>
<string name="ip">IP</string>
<string name="mac">MAC</string>
<string name="no_wol_history">There is no history record, WOL will be added automatically after successful sending</string>
<string name="wol_port_hint">WOL is generally sent over port 7 or port 9</string>
<string name="wol_port_error">Port number value range: 1~65535</string>
<string name="wol_port_regex">^([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$</string>
<string name="wol_port_regex" tools:ignore="TypographyDashes">^([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$</string>
<string name="select_directory">Select Dir</string>
<string name="web_client">Web Client</string>
<string name="restarting_httpserver">Restarting HttpServer</string>
<string name="download_first">Download and unzip to:\n%s</string>
<string name="root_directory">Root Directory:\n%s</string>
<string name="select_web_client_directory">Select WebClient Directory</string>
<string name="invalid_feishu_app_parameter">AppId/AppSecret/UserId cannot be empty</string>
<string name="app_id">App ID</string>
<string name="app_secret">App Secret</string>
<string name="user_id">User ID</string>
</resources>

View File

@ -266,7 +266,7 @@
<string name="setserverchantitle">设置Server酱·Turbo版</string>
<string name="settelegramtitle">设置Telegram机器人</string>
<string name="setsmstitle">设置SMS</string>
<string name="setfeishutitle">设置飞书机器人</string>
<string name="setfeishutitle">设置飞书机器人</string>
<string name="setpushplustitle">设置PushPlus</string>
<string name="setgotifytitle">设置Gotify</string>
<string name="test_phone_num">19999999999</string>
@ -485,7 +485,8 @@
<string name="server_chan">Server酱·Turbo版</string>
<string name="telegram">Telegram机器人</string>
<string name="sms_menu">手机短信</string>
<string name="feishu">飞书机器人</string>
<string name="feishu">飞书群机器人</string>
<string name="feishu_app">飞书企业应用</string>
<string name="pushplus">PushPlus</string>
<string name="gotify">Gotify</string>
<string name="_0">0</string>
@ -771,7 +772,7 @@
<string name="http_server_stopped">HttpServer已停止</string>
<string name="server_settings">服务端设置</string>
<string name="server_settings_tips">建议启用签名设置、点击“随机”自动生成并复制到剪贴板</string>
<string name="copy">复制</string>
<string name="copy" tools:ignore="PrivateResource">复制</string>
<string name="random">随机生成</string>
<string name="sign_key">签名密钥</string>
<string name="sign_key_tips">已生成密钥,并复制到剪贴板</string>
@ -893,20 +894,24 @@
<string name="sampleMarkdown">Markdown类型</string>
<string name="ip_hint">可选主机IP/广播地址例如192.168.1.255</string>
<string name="ip_error">IP地址格式错误例如192.168.168.168</string>
<string name="ip_regex">^((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])$</string>
<string name="ip_regex" tools:ignore="TypographyDashes">^((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])$</string>
<string name="mac_hint">必填网卡mac例如AA:BB:CC:DD:EE:FF</string>
<string name="mac_error">网卡mac格式错误例如AA:BB:CC:DD:EE:FF</string>
<string name="mac_regex">^((([a-fA-F0-9]{2}:){5})|(([a-fA-F0-9]{2}-){5}))[a-fA-F0-9]{2}$</string>
<string name="mac_regex" tools:ignore="TypographyDashes">^((([a-fA-F0-9]{2}:){5})|(([a-fA-F0-9]{2}-){5}))[a-fA-F0-9]{2}$</string>
<string name="ip">主机IP/广播地址</string>
<string name="mac">网卡MAC</string>
<string name="no_wol_history">暂无历史记录WOL发送成功后自动加入</string>
<string name="wol_port_hint">可选WOL一般透过端口7或端口9进行发送</string>
<string name="wol_port_error">端口号取值范围1~65535</string>
<string name="wol_port_regex">^([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$</string>
<string name="wol_port_regex" tools:ignore="TypographyDashes">^([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$</string>
<string name="select_directory">选择目录</string>
<string name="web_client">Web客户端</string>
<string name="restarting_httpserver">正在重启HttpServer</string>
<string name="download_first">请先下载Web客户端并解压到\n%s</string>
<string name="root_directory">根目录:\n%s</string>
<string name="select_web_client_directory">选择Web客户端目录</string>
<string name="invalid_feishu_app_parameter">AppId/AppSecret/UserId都不能为空</string>
<string name="app_id">App ID</string>
<string name="app_secret">App Secret</string>
<string name="user_id">User ID</string>
</resources>