Skip to content

Commit 3225a3c

Browse files
committed
feat: user bind
1 parent 673866f commit 3225a3c

5 files changed

Lines changed: 264 additions & 89 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.github.gnuf0rce.mirai.netdisk
2+
3+
import io.github.gnuf0rce.mirai.netdisk.data.*
4+
import io.ktor.utils.io.errors.*
5+
import net.mamoe.mirai.contact.*
6+
import net.mamoe.mirai.utils.*
7+
import xyz.cssxsh.baidu.disk.*
8+
import xyz.cssxsh.baidu.oauth.*
9+
import xyz.cssxsh.baidu.oauth.exception.*
10+
import java.util.*
11+
import kotlin.properties.*
12+
import kotlin.reflect.*
13+
14+
@PublishedApi
15+
internal object BaiduNetDiskPool : ReadOnlyProperty<Contact?, BaiduNetDiskClient> {
16+
val cache: MutableMap<Long, BaiduNetDiskClient> = WeakHashMap()
17+
18+
private val logger: MiraiLogger by lazy {
19+
try {
20+
NetDiskFileSyncPlugin.logger
21+
} catch (_: Exception) {
22+
MiraiLogger.Factory.create(this::class, "netdisk")
23+
}
24+
}
25+
26+
override fun getValue(thisRef: Contact?, property: KProperty<*>): BaiduNetDiskClient {
27+
if (thisRef == null) return NetDisk
28+
val user = cache[thisRef.id]
29+
if (user != null) return user
30+
if (thisRef is Member) {
31+
val group = cache[thisRef.group.id]
32+
if (group != null) return group
33+
}
34+
return NetDisk
35+
}
36+
37+
private var KClass<out Throwable>.count: Int by object : ReadWriteProperty<KClass<*>, Int> {
38+
private val history: MutableMap<KClass<*>, Int> = WeakHashMap()
39+
40+
override fun getValue(thisRef: KClass<*>, property: KProperty<*>): Int {
41+
return history[thisRef] ?: 0
42+
}
43+
44+
override fun setValue(thisRef: KClass<*>, property: KProperty<*>, value: Int) {
45+
history[thisRef] = value
46+
}
47+
}
48+
49+
val defaultApiIgnore: suspend (Throwable) -> Boolean = { throwable ->
50+
when (throwable) {
51+
is java.net.UnknownHostException,
52+
is java.net.NoRouteToHostException -> false
53+
is IOException -> {
54+
val count = ++throwable::class.count
55+
if (count > 10) {
56+
throwable::class.count = 0
57+
false
58+
} else {
59+
logger.warning { "NetDiskClient Ignore: $throwable" }
60+
true
61+
}
62+
}
63+
else -> false
64+
}
65+
}
66+
67+
fun create(id: Long): BaiduNetDiskClient {
68+
return object : BaiduNetDiskClient(config = NetdiskOauthConfig) {
69+
override val status: BaiduAuthStatus = NetdiskAuthPool.status(id)
70+
override val apiIgnore: suspend (Throwable) -> Boolean = defaultApiIgnore
71+
override suspend fun refreshToken(): String {
72+
return try {
73+
super.refreshToken()
74+
} catch (cause: NotTokenException) {
75+
NetDisk.logger.warning { "缺少 RefreshToken, 请使用 '/baidu bind' 绑定百度账号" }
76+
throw cause
77+
}
78+
}
79+
}
80+
}
81+
}

src/main/kotlin/io/github/gnuf0rce/mirai/netdisk/NetDisk.kt

Lines changed: 90 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import io.ktor.client.plugins.compression.*
88
import io.ktor.client.request.*
99
import io.ktor.client.statement.*
1010
import io.ktor.http.*
11+
import io.ktor.util.*
1112
import io.ktor.utils.io.core.*
12-
import io.ktor.utils.io.errors.*
1313
import kotlinx.coroutines.*
1414
import net.mamoe.mirai.console.permission.*
1515
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
@@ -25,11 +25,8 @@ import xyz.cssxsh.baidu.disk.*
2525
import xyz.cssxsh.baidu.disk.data.*
2626
import xyz.cssxsh.baidu.oauth.*
2727
import xyz.cssxsh.baidu.oauth.exception.*
28-
import java.util.*
29-
import kotlin.collections.*
28+
import java.time.*
3029
import kotlin.coroutines.*
31-
import kotlin.properties.*
32-
import kotlin.reflect.*
3330

3431
public 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" }

src/main/kotlin/io/github/gnuf0rce/mirai/netdisk/NetDiskFileSyncPlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public object NetDiskFileSyncPlugin : KotlinPlugin(
3535
NetdiskOauthConfig.reload()
3636
NetdiskUploadConfig.reload()
3737
NetdiskAuthStatus.reload()
38+
NetdiskAuthPool.reload()
3839

3940
check(NetdiskOauthConfig.appKey.isNotBlank()) {
4041
"插件需要百度网盘API支持,请到 https://pan.baidu.com/union/main/application/personal 申请应用,并填入oauth.yml"

0 commit comments

Comments
 (0)