@@ -8,8 +8,8 @@ import io.ktor.client.plugins.compression.*
88import io.ktor.client.request.*
99import io.ktor.client.statement.*
1010import io.ktor.http.*
11+ import io.ktor.util.*
1112import io.ktor.utils.io.core.*
12- import io.ktor.utils.io.errors.*
1313import kotlinx.coroutines.*
1414import net.mamoe.mirai.console.permission.*
1515import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
@@ -25,11 +25,8 @@ import xyz.cssxsh.baidu.disk.*
2525import xyz.cssxsh.baidu.disk.data.*
2626import xyz.cssxsh.baidu.oauth.*
2727import xyz.cssxsh.baidu.oauth.exception.*
28- import java.util.*
29- import kotlin.collections.*
28+ import java.time.*
3029import kotlin.coroutines.*
31- import kotlin.properties.*
32- import kotlin.reflect.*
3330
3431public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig ), ListenerHost, CoroutineScope {
3532
@@ -51,31 +48,24 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
5148 }
5249
5350 @JvmStatic
54- public val SHORT_URL_REGEX : Regex = """ (?:surl=|s/1)([A-z0-9=_-]+)\W+ ([A-z0-9]{4})?""" .toRegex()
51+ public val SHORT_URL_REGEX : Regex = """ (?:surl=|s/1)([A-z0-9=_-]+)\s* ([A-z0-9]{4})?""" .toRegex()
5552
5653 @JvmStatic
5754 public val STAND_CODE_REGEX : Regex = """ [A-z0-9]{32}#[A-z0-9]{32}#\d+#\S+""" .toRegex()
5855
59- private val logger: MiraiLogger by lazy {
56+ @JvmStatic
57+ public val BD_LINK_REGEX : Regex = """ bdlink=(\S+)""" .toRegex()
58+
59+
60+ @PublishedApi
61+ internal val logger: MiraiLogger by lazy {
6062 try {
6163 NetDiskFileSyncPlugin .logger
6264 } catch (_: Exception ) {
6365 MiraiLogger .Factory .create(this ::class , " netdisk" )
6466 }
6567 }
6668
67- private var KClass <out Throwable >.count: Int by object : ReadWriteProperty <KClass <* >, Int > {
68- private val history: MutableMap <KClass <* >, Int > = WeakHashMap ()
69-
70- override fun getValue (thisRef : KClass <* >, property : KProperty <* >): Int {
71- return history[thisRef] ? : 0
72- }
73-
74- override fun setValue (thisRef : KClass <* >, property : KProperty <* >, value : Int ) {
75- history[thisRef] = value
76- }
77- }
78-
7969 private val downloader: HttpClient = HttpClient (OkHttp ) {
8070 ContentEncoding ()
8171 BrowserUserAgent ()
@@ -91,31 +81,15 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
9181 }
9282 }
9383
94- override val apiIgnore: suspend (Throwable ) -> Boolean = { throwable ->
95- when (throwable) {
96- is java.net.UnknownHostException ,
97- is java.net.NoRouteToHostException -> false
98- is IOException -> {
99- val count = ++ throwable::class .count
100- if (count > 10 ) {
101- throwable::class .count = 0
102- false
103- } else {
104- logger.warning { " NetDiskClient Ignore: $throwable " }
105- true
106- }
107- }
108- else -> false
109- }
110- }
84+ override val apiIgnore: suspend (Throwable ) -> Boolean = BaiduNetDiskPool .defaultApiIgnore
11185
11286 override val status: BaiduAuthStatus get() = NetdiskAuthStatus
11387
11488 override suspend fun refreshToken (): String {
11589 return try {
11690 super .refreshToken()
11791 } catch (cause: NotTokenException ) {
118- logger.warning { " 缺少 RefreshToken, 请使用 /baidu- oauth 绑定百度账号" }
92+ logger.warning { " 缺少 RefreshToken, 请使用 ' /baidu oauth' 绑定百度账号" }
11993 throw cause
12094 }
12195 }
@@ -163,34 +137,7 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
163137 launch {
164138 logger.info { " 发现分享链接 ${match.value} 开始转存" }
165139 val (surl, password) = match.destructured
166- val key = if (password.isNotEmpty()) {
167- val verify = rest.verify(surl = surl, password = password)
168- require(verify.errorNo == 0 ) { verify.errorMessage.ifEmpty { surl } }
169- verify.key
170- } else {
171- " "
172- }
173-
174- val root = rest.view(surl = surl, key = key)
175-
176- val dir = rest.mkdir(path = " ${subject.id} /${root.uk} -${root.shareId} " , ondup = OnDupType .NEW_COPY )
177-
178- val info = TransferFileInfo (
179- shareId = root.shareId,
180- from = root.uk,
181- key = key,
182- files = emptyList()
183- )
184-
185- val limit = user().vip.transferLimit
186-
187- for (list in root.list.chunked(limit)) {
188- val part = info.copy(files = list.map { it.id })
189-
190- rest.transfer(info = part, path = dir.path, ondup = OnDupType .NEW_COPY )
191-
192- delay(30_000 )
193- }
140+ sender.netdisk.saveShareLink(surl = surl, password = password)
194141
195142 launch {
196143 NetDiskFileSyncRecorder .record(source = source, surl = surl, password = password)
@@ -203,23 +150,40 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
203150 STAND_CODE_REGEX .findAll(plain.content).forEach { match ->
204151 launch {
205152 logger.info { " 发现秒传码 ${match.value} 开始保存" }
206- mkdir(path = " ${subject.id} " )
207153 val upload = RapidUploadInfo .parse(code = match.value)
208- val path = " ${subject.id} /${upload.path.substringAfterLast(' /' )} "
209-
210- rapid(upload = upload.copy(path = path), ondup = OnDupType .NEW_COPY )
154+ val path = sender.netdisk.saveRapidUpload(upload = upload)
211155
212156 launch {
213157 NetDiskFileSyncRecorder .record(source = source, rapid = upload)
214158 }
215159 if (NetdiskUploadConfig .reply) {
216- subject.sendMessage(message.quote() + " 保存成功" )
160+ subject.sendMessage(message.quote() + " 保存成功 $path " )
161+ }
162+ }
163+ }
164+ BD_LINK_REGEX .findAll(plain.content).forEach { match ->
165+ launch {
166+ logger.info { " 发现秒传链接 ${match.value} 开始保存" }
167+ val (base64) = match.destructured
168+ val paths = mutableListOf<String >()
169+ STAND_CODE_REGEX .findAll(base64.decodeBase64String()).forEach { m ->
170+ val upload = RapidUploadInfo .parse(code = m.value)
171+ val path = sender.netdisk.saveRapidUpload(upload = upload)
172+ paths.add(path)
173+
174+ launch {
175+ NetDiskFileSyncRecorder .record(source = source, rapid = upload)
176+ }
177+ }
178+ if (NetdiskUploadConfig .reply) {
179+ subject.sendMessage(message.quote() + " 保存成功 $paths " )
217180 }
218181 }
219182 }
220183 }
221184
222- private suspend fun uploadAbsoluteFile (file : AbsoluteFile ): RapidUploadInfo {
185+ @PublishedApi
186+ internal suspend fun uploadAbsoluteFile (file : AbsoluteFile ): RapidUploadInfo {
223187 val url = requireNotNull(file.getUrl()) { " 远程文件 URL 获取失败" }
224188 @Suppress(" INVISIBLE_MEMBER" )
225189 val rapid = with (file) {
@@ -234,12 +198,12 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
234198 content = content,
235199 slice = slice,
236200 length = size,
237- path = " ${contact.id}${absolutePath} "
201+ path = " ${LocalDate .now()} / ${ contact.id}${absolutePath}"
238202 )
239203 }
240204
241205 // 用群号做根目录
242- mkdir(path = " ${file.contact.id} " )
206+ mkdir(path = rapid.path.substringBeforeLast( ' / ' ) )
243207 logger.info { " upload ${rapid.format()} " }
244208
245209 // 尝试秒传
@@ -266,7 +230,7 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
266230 } catch (cause: ClientRequestException ) {
267231 logger.info { " 文件 ${file.name} 单文件上传失败, 进入文件上传, ${cause.message} " }
268232 } catch (exception: Exception ) {
269- logger.info ({ " 文件 ${file.name} 单文件上传失败, 进入文件上传" }, exception)
233+ logger.warning ({ " 文件 ${file.name} 单文件上传失败, 进入文件上传" }, exception)
270234 }
271235 }
272236
@@ -277,16 +241,17 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
277241
278242 val blocks = download(urlString = url).execute { response ->
279243 val channel = response.bodyAsChannel()
280- val capacity = ( file.size / limit + 1 ).toInt()
244+ val capacity = file.size.minus( 1 ).div( limit).plus( 1 ).toInt()
281245 List (capacity) { index ->
282246 val packet = channel.readRemaining(limit)
283247 supervisorScope {
284248 async {
285249 val size = packet.remaining.toInt()
286- val temp = pcs.temp(path = rapid.path, id = uploadId, index = index, size = size) {
287- writePacket(packet)
250+ val temp = packet.use {
251+ pcs.temp(path = rapid.path, id = uploadId, index = index, size = size) {
252+ writePacket(packet)
253+ }
288254 }
289- packet.close()
290255
291256 temp.md5
292257 }
@@ -306,6 +271,53 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
306271 return rapid
307272 }
308273
274+ @PublishedApi
275+ internal val Contact .netdisk: BaiduNetDiskClient by BaiduNetDiskPool
276+
277+ @PublishedApi
278+ internal suspend fun BaiduNetDiskClient.saveShareLink (surl : String , password : String ): String {
279+ val key = if (password.isNotEmpty()) {
280+ val verify = rest.verify(surl = surl, password = password)
281+ require(verify.errorNo == 0 ) { verify.errorMessage.ifEmpty { " $surl - $password " } }
282+ verify.key
283+ } else {
284+ " "
285+ }
286+
287+ val root = rest.view(surl = surl, key = key)
288+
289+ val dir = rest.mkdir(path = " ${LocalDate .now()} /${root.uk} _${root.shareId} " , ondup = OnDupType .NEW_COPY )
290+
291+ val info = TransferFileInfo (
292+ shareId = root.shareId,
293+ from = root.uk,
294+ key = key,
295+ files = emptyList()
296+ )
297+
298+ val limit = user().vip.transferLimit
299+
300+ for (list in root.list.chunked(limit)) {
301+ val part = info.copy(files = list.map { it.id })
302+
303+ rest.transfer(info = part, path = dir.path, ondup = OnDupType .NEW_COPY )
304+
305+ delay(30_000 )
306+ }
307+
308+ return dir.path
309+ }
310+
311+ @PublishedApi
312+ internal suspend fun BaiduNetDiskClient.saveRapidUpload (upload : RapidUploadInfo ): String {
313+ val path = " ${LocalDate .now()} /${upload.path} "
314+
315+ mkdir(path = path.substringBeforeLast(' /' ))
316+ rapid(upload = upload.copy(path = path), ondup = OnDupType .NEW_COPY )
317+
318+ return path
319+ }
320+
309321 private suspend fun download (urlString : String , range : LongRange ? = null): HttpStatement {
310322 val fragment = range?.run { " bytes=${start} -${endInclusive} " }
311323 logger.verbose { " download $urlString #$fragment " }
0 commit comments