Skip to content

Commit fe769ad

Browse files
committed
add: share
1 parent 0a494b8 commit fe769ad

6 files changed

Lines changed: 232 additions & 41 deletions

File tree

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

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ 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.utils.io.charsets.*
1211
import io.ktor.utils.io.core.*
1312
import io.ktor.utils.io.errors.*
1413
import kotlinx.coroutines.*
@@ -51,6 +50,12 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
5150
}
5251
}
5352

53+
@JvmStatic
54+
public val SHORT_URL_REGEX: Regex = """(?:surl=|s/1)([A-z0-9=_-]+)\W+([A-z0-9]{4})?""".toRegex()
55+
56+
@JvmStatic
57+
public val STAND_CODE_REGEX: Regex = """[A-z0-9]{32}#[A-z0-9]{32}#\d+#\S+""".toRegex()
58+
5459
private val logger: MiraiLogger by lazy {
5560
try {
5661
NetDiskFileSyncPlugin.logger
@@ -71,9 +76,23 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
7176
}
7277
}
7378

79+
private val downloader: HttpClient = HttpClient(OkHttp) {
80+
ContentEncoding()
81+
BrowserUserAgent()
82+
// FIXME: MalformedInputException
83+
expectSuccess = NetdiskUploadConfig.https.not()
84+
HttpResponseValidator {
85+
validateResponse { response ->
86+
if (response.headers[HttpHeaders.ContentType] == "text/octet") {
87+
val bytes = response.readBytes()
88+
throw ClientRequestException(response, bytes.joinToString("") { "\\x%02x".format(it) })
89+
}
90+
}
91+
}
92+
}
93+
7494
override val apiIgnore: suspend (Throwable) -> Boolean = { throwable ->
7595
when (throwable) {
76-
is MalformedInputException -> false
7796
is IOException -> {
7897
val count = ++throwable::class.count
7998
if (count > 10) {
@@ -84,7 +103,6 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
84103
true
85104
}
86105
}
87-
88106
else -> false
89107
}
90108
}
@@ -101,15 +119,15 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
101119
}
102120

103121
@EventHandler
104-
public fun MessageEvent.handle() {
122+
public fun MessageEvent.upload() {
105123
val contact = subject as? Group ?: return
106124
val content = message.findIsInstance<FileMessage>() ?: return
107125
if (permission.testPermission(contact.permitteeId).not() &&
108126
permission.testPermission(sender.permitteeId).not()
109127
) return
110128

111129
launch {
112-
logger.info { "发现文件消息 ${content}开始上传" }
130+
logger.info { "发现文件消息 $content 开始上传" }
113131
val file = withTimeout(10_000) {
114132
content.toAbsoluteFile(contact) ?: throw NoSuchElementException("${content.name} in $contact")
115133
}
@@ -134,15 +152,81 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
134152
}
135153
}
136154

155+
@EventHandler
156+
public fun MessageEvent.save() {
157+
val plain = message.findIsInstance<PlainText>() ?: return
158+
if (permission.testPermission(sender.permitteeId).not()) return
159+
160+
SHORT_URL_REGEX.findAll(plain.content).forEach { match ->
161+
launch {
162+
logger.info { "发现分享链接 ${match.value} 开始转存" }
163+
val (surl, password) = match.destructured
164+
val key = if (password.isNotEmpty()) {
165+
val verify = rest.verify(surl = surl, password = password)
166+
require(verify.errorNo == 0) { verify.errorMessage.ifEmpty { surl } }
167+
verify.key
168+
} else {
169+
""
170+
}
171+
172+
val root = rest.view(surl = surl, key = key)
173+
174+
val dir = rest.mkdir(path = "${subject.id}/${root.uk}-${root.shareId}", ondup = OnDupType.NEW_COPY)
175+
176+
val info = TransferFileInfo(
177+
shareId = root.shareId,
178+
from = root.uk,
179+
key = key,
180+
files = emptyList()
181+
)
182+
183+
val limit = user().vip.transferLimit
184+
185+
for (list in root.list.chunked(limit)) {
186+
val part = info.copy(files = list.map { it.id })
187+
188+
rest.transfer(info = part, path = dir.path, ondup = OnDupType.NEW_COPY)
189+
190+
delay(30_000)
191+
}
192+
193+
launch {
194+
NetDiskFileSyncRecorder.record(source = source, surl = surl, password = password)
195+
}
196+
if (NetdiskUploadConfig.reply) {
197+
subject.sendMessage(message.quote() + "分享 $surl 转存成功")
198+
}
199+
}
200+
}
201+
STAND_CODE_REGEX.findAll(plain.content).forEach { match ->
202+
launch {
203+
logger.info { "发现秒传码 ${match.value} 开始保存" }
204+
mkdir(path = "${subject.id}")
205+
val upload = RapidUploadInfo.parse(code = match.value)
206+
val path = "${subject.id}/${upload.path.substringAfterLast('/')}"
207+
208+
rapid(upload = upload.copy(path = path), ondup = OnDupType.NEW_COPY)
209+
210+
launch {
211+
NetDiskFileSyncRecorder.record(source = source, rapid = upload)
212+
}
213+
if (NetdiskUploadConfig.reply) {
214+
subject.sendMessage(message.quote() + "保存成功")
215+
}
216+
}
217+
}
218+
}
219+
137220
private suspend fun uploadAbsoluteFile(file: AbsoluteFile): RapidUploadInfo {
138221
val url = requireNotNull(file.getUrl()) { "远程文件 URL 获取失败" }
222+
@Suppress("INVISIBLE_MEMBER")
139223
val rapid = with(file) {
140-
val content = file.md5.toUHexString("").lowercase()
224+
val content = file.md5.toHexString()
141225
val slice = if (size <= SLICE_SIZE) {
142226
content
143227
} else {
144228
val slice = download(urlString = url, range = 0L until SLICE_SIZE.toLong()).body<ByteArray>()
145-
slice.md5().toUHexString("").lowercase()
229+
slice.md5().toHexString()
146230
}
147231
RapidUploadInfo(
148232
content = content,
@@ -166,9 +250,9 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
166250
logger.warning({ "文件 ${file.name} 秒传失败, 进入文件上传" }, exception)
167251
}
168252

169-
val user = rest.user()
170-
check(file.size <= user.vip.updateLimit) { "${file.contact}-${file.name} 超过了文件上传极限" }
253+
val user = user()
171254
val limit = user.vip.superLimit.toLong()
255+
check(file.size <= limit) { "${file.contact}-${file.name} 超过了文件上传极限" }
172256

173257
if (file.size < limit) {
174258
try {
@@ -180,15 +264,13 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
180264
} catch (throwable: ClientRequestException) {
181265
logger.info { "文件 ${file.name} 单文件上传失败, 进入文件上传, ${throwable.message}" }
182266
} catch (exception: Throwable) {
183-
logger.info({ "文件 ${file.name} 快速存入失败, 进入文件上传" }, exception)
267+
logger.info({ "文件 ${file.name} 单文件上传失败, 进入文件上传" }, exception)
184268
}
185269
}
186270

187271

188272
val prepare = rest.prepare(upload = rapid, blocks = LAZY_BLOCKS, ondup = OnDupType.NEW_COPY)
189-
if (prepare.type == PrepareReturnType.EXIST) {
190-
return rapid
191-
}
273+
if (prepare.type == PrepareReturnType.EXIST) return rapid
192274
val uploadId = requireNotNull(prepare.uploadId) { prepare }
193275

194276
val blocks = download(urlString = url).execute { response ->
@@ -211,7 +293,7 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
211293
}.awaitAll()
212294

213295
val merge = MergeFileInfo(
214-
blocks = blocks.toMutableList(),
296+
blocks = blocks,
215297
uploadId = uploadId,
216298
size = file.size,
217299
path = rapid.path
@@ -222,21 +304,6 @@ public object NetDisk : BaiduNetDiskClient(config = NetdiskOauthConfig), Listene
222304
return rapid
223305
}
224306

225-
private val downloader: HttpClient = HttpClient(OkHttp) {
226-
ContentEncoding()
227-
BrowserUserAgent()
228-
// FIXME: MalformedInputException
229-
expectSuccess = NetdiskUploadConfig.https.not()
230-
HttpResponseValidator {
231-
validateResponse { response ->
232-
if (response.headers[HttpHeaders.ContentType] == "text/octet") {
233-
val bytes = response.readBytes()
234-
throw ClientRequestException(response, bytes.joinToString("") { "\\x%02x".format(it) })
235-
}
236-
}
237-
}
238-
}
239-
240307
private suspend fun download(urlString: String, range: LongRange? = null): HttpStatement {
241308
val fragment = range?.run { "bytes=${start}-${endInclusive}" }
242309
logger.verbose { "download $urlString#$fragment" }

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

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.github.gnuf0rce.mirai.netdisk
33
import io.github.gnuf0rce.mirai.netdisk.data.*
44
import io.github.gnuf0rce.mirai.netdisk.entry.*
55
import net.mamoe.mirai.contact.file.*
6+
import net.mamoe.mirai.message.data.*
67
import org.hibernate.*
78
import xyz.cssxsh.baidu.disk.*
89
import xyz.cssxsh.hibernate.*
@@ -47,7 +48,50 @@ public object NetDiskFileSyncRecorder {
4748

4849
factory.fromTransaction { session -> session.persist(record) }
4950
} else {
50-
NetdiskSyncHistory.records.add(record)
51+
NetdiskSyncHistory.sync.add(record)
52+
}
53+
}
54+
55+
public fun record(source: MessageSource, rapid: RapidUploadInfo) {
56+
57+
val record = CodeSaveRecord(
58+
targetId = source.targetId,
59+
fromId = source.fromId,
60+
time = source.time,
61+
ids = source.ids.joinToString(","),
62+
code = rapid.format()
63+
)
64+
65+
val factory = factory
66+
67+
if (factory != null) {
68+
factory as SessionFactory
69+
70+
factory.fromTransaction { session -> session.persist(record) }
71+
} else {
72+
NetdiskSyncHistory.code.add(record)
73+
}
74+
}
75+
76+
public fun record(source: MessageSource, surl: String, password: String) {
77+
78+
val record = ShareSaveRecord(
79+
targetId = source.targetId,
80+
fromId = source.fromId,
81+
time = source.time,
82+
ids = source.ids.joinToString(","),
83+
surl = surl,
84+
password = password
85+
)
86+
87+
val factory = factory
88+
89+
if (factory != null) {
90+
factory as SessionFactory
91+
92+
factory.fromTransaction { session -> session.persist(record) }
93+
} else {
94+
NetdiskSyncHistory.share.add(record)
5195
}
5296
}
5397

@@ -70,7 +114,7 @@ public object NetDiskFileSyncRecorder {
70114
}.list()
71115
}
72116
} else {
73-
NetdiskSyncHistory.records
117+
NetdiskSyncHistory.sync
74118
.asSequence()
75119
.filter { it.uploaderId == uploaderId && it.uploadTime in start..end }
76120
.toMutableList()
@@ -94,7 +138,7 @@ public object NetDiskFileSyncRecorder {
94138
}.list()
95139
}
96140
} else {
97-
NetdiskSyncHistory.records
141+
NetdiskSyncHistory.sync
98142
.asSequence()
99143
.filter { it.contactId == contactId && it.uploadTime in start..end }
100144
.toMutableList()

src/main/kotlin/io/github/gnuf0rce/mirai/netdisk/command/BaiduOAuthCommand.kt

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,53 @@ internal object BaiduOAuthCommand : CompositeCommand(
3030
authorize { url ->
3131
sendMessage("请打开连接,然后在十分钟内输入获得的认证码, $url")
3232
read()
33-
} to rest.user()
33+
} to user()
3434
}.onSuccess { (token, user) ->
3535
logger.info { "百度云用户认证成功, ${user.baiduName} by $token" }
3636
sendMessage("百度云用户认证成功, ${user.baiduName} by $token")
37-
}.onFailure {
38-
logger.warning({ "认证失败" }, it)
39-
sendMessage("百度云用户认证失败, ${it.message}")
37+
}.onFailure { cause ->
38+
logger.warning({ "认证失败" }, cause)
39+
sendMessage("百度云用户认证失败, ${cause.message}")
4040
}
4141
}
4242

4343
@SubCommand
4444
suspend fun CommandSender.refresh(token: String) {
4545
NetdiskUserData.refreshTokenValue = token
4646
NetDisk.runCatching {
47-
refresh() to rest.user()
47+
refresh() to user()
4848
}.onSuccess { (token, user) ->
4949
logger.info { "百度云用户认证成功, ${user.baiduName} by $token" }
5050
sendMessage("百度云用户认证成功, ${user.baiduName} by $token")
51-
}.onFailure {
52-
logger.warning({ "认证失败" }, it)
53-
sendMessage("百度云用户认证失败, ${it.message}")
51+
}.onFailure { cause ->
52+
logger.warning({ "认证失败" }, cause)
53+
sendMessage("百度云用户认证失败, ${cause.message}")
54+
}
55+
}
56+
57+
@SubCommand
58+
suspend fun CommandSender.host() {
59+
NetDisk.runCatching {
60+
host()
61+
}.onSuccess { host ->
62+
logger.info { "host 刷新成功 $host" }
63+
sendMessage("host 刷新成功,共 ${host.server.size} 个服务器")
64+
}.onFailure { cause ->
65+
logger.warning({ "刷新失败" }, cause)
66+
sendMessage("host 刷新失败, ${cause.message}")
67+
}
68+
}
69+
70+
@SubCommand
71+
suspend fun CommandSender.user() {
72+
NetDisk.runCatching {
73+
user()
74+
}.onSuccess { user ->
75+
logger.info { "百度云用户刷新成功, ${user.baiduName} - ${user.vip}" }
76+
sendMessage("百度云用户刷新成功, ${user.baiduName} - ${user.vip}")
77+
}.onFailure { cause ->
78+
logger.warning({ "刷新失败" }, cause)
79+
sendMessage("百度云用户刷新失败, ${cause.message}")
5480
}
5581
}
5682
}

src/main/kotlin/io/github/gnuf0rce/mirai/netdisk/data/NetdiskSyncHistory.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,11 @@ import net.mamoe.mirai.console.data.*
55

66
internal object NetdiskSyncHistory : AutoSavePluginData("history") {
77
@ValueName("sync_upload_records")
8-
val records: MutableList<SyncUploadRecord> by value()
8+
val sync: MutableList<SyncUploadRecord> by value()
9+
10+
@ValueName("code_save_records")
11+
val code: MutableList<CodeSaveRecord> by value()
12+
13+
@ValueName("share_save_records")
14+
val share: MutableList<ShareSaveRecord> by value()
915
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.github.gnuf0rce.mirai.netdisk.entry
2+
3+
import jakarta.persistence.*
4+
5+
@Entity
6+
@Table(name = "code_save_record")
7+
@kotlinx.serialization.Serializable
8+
public data class CodeSaveRecord(
9+
@Id
10+
@Column(name = "id", nullable = false, updatable = false)
11+
@GeneratedValue(strategy = GenerationType.IDENTITY)
12+
val id: Long = 0,
13+
@Column(name = "target_id", nullable = false, updatable = false)
14+
val targetId: Long = 0,
15+
@Column(name = "from_id", nullable = false, updatable = false)
16+
val fromId: Long = 0,
17+
@Column(name = "time", nullable = false, updatable = false)
18+
val time: Int = 0,
19+
@Column(name = "ids", nullable = true, updatable = false)
20+
val ids: String = "",
21+
@Column(name = "code", nullable = false, updatable = false)
22+
val code: String = "",
23+
) : java.io.Serializable

0 commit comments

Comments
 (0)