Skip to content
This repository was archived by the owner on Jun 23, 2026. It is now read-only.

Commit 702c631

Browse files
committed
feat: 完善剪切板解析
1 parent 054b898 commit 702c631

12 files changed

Lines changed: 292 additions & 43 deletions

File tree

app/src/main/java/com/lanzou/cloud/MainActivity.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ import com.lanzou.cloud.databinding.ActivityMainBinding
4040
import com.lanzou.cloud.network.Repository
4141
import com.lanzou.cloud.service.DownloadService
4242
import com.lanzou.cloud.service.UploadService
43-
import com.lanzou.cloud.ui.activity.ResolveFileActivity
4443
import com.lanzou.cloud.ui.cloud.file.FileFragment
4544
import com.lanzou.cloud.ui.cloud.selector.FileSelectorActivity
4645
import com.lanzou.cloud.ui.cloud.selector.PhoneFileActivity
46+
import com.lanzou.cloud.ui.dialog.FileResolveDialog
4747
import com.lanzou.cloud.ui.dialog.FileResolveViewModel
4848
import com.lanzou.cloud.utils.SpJavaUtils
4949
import com.lanzou.cloud.utils.UpdateUtils
@@ -151,12 +151,17 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
151151
}
152152

153153
lifecycleScope.launch {
154-
fileResolveViewModel.fileShareUrl.collect { url ->
155-
if (url.isBlank()) {
156-
return@collect
154+
fileResolveViewModel.lanzouResolveFile.collect { result ->
155+
result ?: return@collect
156+
val fragment = supportFragmentManager.findFragmentByTag("FileResolveDialog")
157+
if (fragment == null) {
158+
if (result.fileName.isEmpty()) {
159+
toast("解析文件失败,可能密码错误,请修改密码后解析")
160+
} else {
161+
toast("发现文件分享链接: ${result.url}")
162+
}
163+
FileResolveDialog(downloadService).show(supportFragmentManager, "FileResolveDialog")
157164
}
158-
toast("发现文件分享链接: $url")
159-
ResolveFileActivity.actionStart(url)
160165
}
161166
}
162167
}

app/src/main/java/com/lanzou/cloud/config/Api.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ object Api {
1212
*/
1313
const val DISK_PHP = "/mydisk.php"
1414

15+
const val AJAX_PHP = "ajaxm.php"
16+
1517
/**
1618
* 获取分享文件夹
1719
*/

app/src/main/java/com/lanzou/cloud/data/LanzouDownloadResponse.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import com.google.gson.annotations.SerializedName;
66

7+
import kotlinx.serialization.Serializable;
8+
9+
@Serializable
710
public class LanzouDownloadResponse {
811

912
@SerializedName("zt")
@@ -13,7 +16,7 @@ public class LanzouDownloadResponse {
1316

1417
private String url;
1518

16-
private String info;
19+
private String inf;
1720

1821
public int getStatus() {
1922
return status;
@@ -39,12 +42,12 @@ public void setUrl(String url) {
3942
this.url = url;
4043
}
4144

42-
public String getInfo() {
43-
return info;
45+
public String getInf() {
46+
return inf;
4447
}
4548

46-
public void setInfo(String info) {
47-
this.info = info;
49+
public void setInf(String inf) {
50+
this.inf = inf;
4851
}
4952

5053
@NonNull
@@ -54,7 +57,7 @@ public String toString() {
5457
"status=" + status +
5558
", dom='" + dom + '\'' +
5659
", url='" + url + '\'' +
57-
", info='" + info + '\'' +
60+
", inf='" + inf + '\'' +
5861
'}';
5962
}
6063
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.lanzou.cloud.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class LanzouDownloadModel(
8+
@SerialName("zt")
9+
val status: Int = 0,
10+
val dom: String = "",
11+
val url: String = "",
12+
val inf: String = ""
13+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.lanzou.cloud.model
2+
3+
import androidx.databinding.BaseObservable
4+
5+
data class LanzouResolveFileModel(
6+
var url: String = "",
7+
var pwd: String? = null,
8+
var downloadUrl: String = "",
9+
var fileName: String = "",
10+
var fileSize: String = "",
11+
var shareTime: String = "",
12+
var remark: String = "",
13+
) : BaseObservable()

app/src/main/java/com/lanzou/cloud/network/LanzouRepository.kt

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.lanzou.cloud.network
22

3+
import android.util.Log
34
import com.drake.net.Get
45
import com.drake.net.Post
56
import com.lanzou.cloud.LanzouApplication
@@ -8,6 +9,8 @@ import com.lanzou.cloud.config.Api
89
import com.lanzou.cloud.config.SPConfig
910
import com.lanzou.cloud.model.BaseLanzouResponse
1011
import com.lanzou.cloud.model.FileInfoModel
12+
import com.lanzou.cloud.model.LanzouDownloadModel
13+
import com.lanzou.cloud.model.LanzouResolveFileModel
1114
import com.lanzou.cloud.model.LanzouShareFolderModel
1215
import com.lanzou.cloud.model.LanzouUrlModel
1316
import com.lanzou.cloud.model.ProfileModel
@@ -257,9 +260,63 @@ object LanzouRepository {
257260
*
258261
* @param url 文件地址
259262
*/
260-
fun parseFile(url: String) {
261-
val document = Jsoup.connect(url).userAgent(Repository.USER_AGENT).get()
262-
263+
suspend fun parseFile(url: String, pwd: String? = null): Result<LanzouResolveFileModel> {
264+
return coroutineScope {
265+
runCatching {
266+
// TODO: 解析文件夹待完成
267+
val document = Jsoup.connect(url).userAgent(Repository.USER_AGENT).get()
268+
val passwordDiv = document.selectFirst("#file")
269+
if (passwordDiv != null) {
270+
// 这是有密码的文件
271+
val file = document.selectFirst("#file") ?: throw NullPointerException("解析文件失败")
272+
val href = file.selectFirst("a.n_login")?.attr("href")
273+
?: throw NullPointerException("获取文件id失败")
274+
val idRegex = "f=(\\d+)".toRegex()
275+
val fileId = idRegex.find(href)?.destructured?.component1()
276+
?: throw NullPointerException("获取文件id失败")
277+
val signRegex = "'sign':'(.*?)',".toRegex()
278+
val sign =
279+
signRegex.findAll(document.html()).toList().getOrNull(1)?.destructured?.component1()
280+
?: throw NullPointerException("获取 sign 失败")
281+
val result =
282+
Post<LanzouDownloadModel>(LanzouApplication.LANZOU_SHARE_BASE_URL + Api.AJAX_PHP) {
283+
converter = SerializationConverter(rawData = true)
284+
addHeader("Referer", url)
285+
addQuery("file", fileId)
286+
param("action", "downprocess")
287+
param("sign", sign)
288+
param("kd", 1)
289+
param("p", pwd)
290+
}.await()
291+
if (result.status != 1) {
292+
throw kotlin.IllegalStateException("解析文件失败")
293+
}
294+
val shareTime = file.selectFirst(".n_file_info .n_file_infos")?.text() ?: ""
295+
val fileSize = file.selectFirst(".n_filesize")?.text() ?: ""
296+
LanzouResolveFileModel(
297+
url = url,
298+
pwd = pwd,
299+
downloadUrl = result.dom + "/file/" + result.url,
300+
fileName = result.inf,
301+
fileSize = fileSize.replace("大小:", ""),
302+
shareTime = shareTime
303+
)
304+
} else {
305+
// 没有密码的文件
306+
val fileName = document.selectFirst(".d>div")?.text()
307+
val nodes = document.selectFirst(".d2 table td")?.textNodes()
308+
Log.i("jdy", "nodes: $nodes")
309+
LanzouResolveFileModel(
310+
url,
311+
pwd,
312+
fileName = fileName ?: "",
313+
fileSize = nodes?.getOrNull(1)?.toString() ?: "",
314+
shareTime = nodes?.getOrNull(3)?.toString() ?: "",
315+
remark = nodes?.getOrNull(7)?.toString()?.replace("\n", "") ?: "",
316+
)
317+
}
318+
}
319+
}
263320
}
264321

265322
private fun getFileRealName(fileInfoModel: FileInfoModel) {

app/src/main/java/com/lanzou/cloud/service/DownloadService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ public void addDownload(String url, @Nullable String name,
344344
return;
345345
}
346346

347-
String showName = name == null ? url : name;
347+
String showName = TextUtils.isEmpty(name) ? url : name;
348348

349349
downloadMap.put(url, executorService.submit(() -> {
350350
Download download = createDownload(url, showName, pwd, path, true);

app/src/main/java/com/lanzou/cloud/ui/dialog/FileResolveDialog.kt

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import android.view.ViewGroup
77
import androidx.fragment.app.activityViewModels
88
import androidx.lifecycle.lifecycleScope
99
import com.drake.engine.base.EngineBottomSheetDialogFragment
10+
import com.drake.tooltip.toast
11+
import com.google.android.material.bottomsheet.BottomSheetBehavior
1012
import com.lanzou.cloud.R
1113
import com.lanzou.cloud.databinding.DialogFileResolveBinding
14+
import com.lanzou.cloud.service.DownloadService
1215
import kotlinx.coroutines.launch
1316

14-
class FileResolveDialog : EngineBottomSheetDialogFragment<DialogFileResolveBinding>() {
17+
class FileResolveDialog(private val downloadService: DownloadService? = null) :
18+
EngineBottomSheetDialogFragment<DialogFileResolveBinding>() {
1519

1620
private val fileResolveViewModel by activityViewModels<FileResolveViewModel>()
1721

@@ -24,12 +28,38 @@ class FileResolveDialog : EngineBottomSheetDialogFragment<DialogFileResolveBindi
2428
}
2529

2630
override fun initData() {
27-
binding.m = fileResolveViewModel
2831
binding.lifecycleOwner = this
32+
behavior.state = BottomSheetBehavior.STATE_EXPANDED
33+
2934
lifecycleScope.launch {
30-
fileResolveViewModel.fileShareUrl.collect {
35+
fileResolveViewModel.lanzouResolveFile.collect {
36+
binding.m = it
37+
}
38+
}
39+
40+
binding.btnResolve.setOnClickListener {
41+
fileResolveViewModel.updatePwd()
42+
}
3143

44+
binding.btnClose.setOnClickListener {
45+
dismiss()
46+
}
47+
48+
binding.btnFavorite.setOnClickListener {
49+
// TODO: 收藏文件
50+
}
51+
52+
binding.btnDownload.setOnClickListener {
53+
val file = fileResolveViewModel.lanzouResolveFile.value
54+
if (file == null) {
55+
toast("请先解析文件完成")
56+
return@setOnClickListener
57+
}
58+
if (file.url.isEmpty()) {
59+
toast("文件分享链接不能为空")
60+
return@setOnClickListener
3261
}
62+
downloadService?.addDownload(file.url, file.fileName, file.pwd)
3363
}
3464
}
3565

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package com.lanzou.cloud.ui.dialog
22

3+
import android.util.Log
34
import androidx.lifecycle.ViewModel
45
import androidx.lifecycle.viewModelScope
6+
import com.drake.net.utils.withIO
7+
import com.drake.tooltip.toast
58
import com.lanzou.cloud.LanzouApplication
9+
import com.lanzou.cloud.model.LanzouResolveFileModel
10+
import com.lanzou.cloud.network.LanzouRepository
611
import kotlinx.coroutines.ExperimentalCoroutinesApi
712
import kotlinx.coroutines.flow.MutableStateFlow
813
import kotlinx.coroutines.flow.SharingStarted
@@ -15,24 +20,58 @@ import kotlinx.coroutines.flow.stateIn
1520

1621
class FileResolveViewModel : ViewModel() {
1722

18-
private val _clipData = MutableStateFlow<CharSequence>("")
23+
private val _clipData = MutableStateFlow<CharSequence?>(null)
1924

25+
/**
26+
* 解析后的文件信息
27+
*/
2028
@OptIn(ExperimentalCoroutinesApi::class)
21-
val fileShareUrl = _clipData.map { text ->
22-
val fileRegex = "lanzou[a-z]*\\.com/(\\w+)".toRegex()
23-
fileRegex.find(text)
24-
}
25-
.filter { it != null }
26-
.flatMapLatest { matchResult ->
27-
val fileId = matchResult!!.destructured.component1()
28-
// val shareUrl = Repository.getInstance().getDownloadUrl()
29-
flowOf("${LanzouApplication.LANZOU_SHARE_BASE_URL}$fileId")
29+
val lanzouResolveFile = _clipData
30+
.filter { !it.isNullOrBlank() }
31+
.map { text ->
32+
val fileIdRegex = "lanzou[a-z]*\\.com/(\\w+)".toRegex()
33+
val pwdRegex = "密码[::]*\\x20*(\\w+)".toRegex()
34+
arrayOf(fileIdRegex.find(text!!), pwdRegex.find(text))
35+
}
36+
.filter { it.first() != null }
37+
.flatMapLatest { matchResults ->
38+
val fileId = matchResults.first()!!.destructured.component1()
39+
val pwd = matchResults[1]?.destructured?.component1()
40+
Log.i("FileResolveViewModel", "fileId: $fileId, pwd: $pwd")
41+
val url = "${LanzouApplication.LANZOU_SHARE_BASE_URL}$fileId"
42+
// 获取文件的基本信息
43+
flowOf(getFileInfo(url, pwd))
3044
}.catch {
31-
emit("")
45+
Log.e("FileResolveViewModel", it.toString())
46+
toast("解析文件失败: ${it.message}")
3247
}
33-
.stateIn(viewModelScope, SharingStarted.Lazily, "")
48+
.stateIn(viewModelScope, SharingStarted.Lazily, null)
49+
50+
suspend fun getFileInfo(url: String, pwd: String? = null): LanzouResolveFileModel {
51+
return withIO {
52+
LanzouRepository.parseFile(url, pwd)
53+
}.getOrDefault(LanzouResolveFileModel(url = url, pwd = pwd))
54+
}
3455

35-
fun setClipData(data: CharSequence) {
56+
/**
57+
* 设置剪切板数据
58+
*/
59+
fun setClipData(data: CharSequence?) {
3660
_clipData.value = data
3761
}
62+
63+
fun updatePwd() {
64+
_clipData.value ?: return
65+
val pwdRegex = "密码[::]*\\x20*(\\w)*".toRegex()
66+
val pwd = lanzouResolveFile.value?.pwd ?: ""
67+
if (pwd.isEmpty()) {
68+
_clipData.value = _clipData.value.toString() + " "
69+
return
70+
}
71+
if (pwdRegex.containsMatchIn(_clipData.value!!)) {
72+
_clipData.value = _clipData.value!!.replace(pwdRegex, "密码:${pwd}")
73+
} else {
74+
_clipData.value = _clipData.value!!.toString() + " 密码:${pwd}"
75+
}
76+
}
3877
}

app/src/main/java/com/lanzou/cloud/utils/converter/SerializationConverter.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class SerializationConverter(
2727
val success: Array<String> = arrayOf("1", "2"), // 成功的状态码
2828
val code: String = "zt", // code 字段名
2929
val message: String = "info", // message 字段名
30-
val data: String = "text"
30+
val data: String = "text",
31+
val rawData: Boolean = false,
3132
) : NetConverter {
3233

3334
companion object {
@@ -50,6 +51,9 @@ class SerializationConverter(
5051
val srvCode = json.getString(this.code)
5152
when {
5253
success.contains(srvCode) -> {
54+
if (rawData) {
55+
return bodyString.parseBody(kType)
56+
}
5357
if (!json.has(data)) {
5458
return null
5559
}

0 commit comments

Comments
 (0)