feat: 推了很多进度。
This commit is contained in:
parent
aa84499d62
commit
4e07309066
@ -2,6 +2,7 @@ import java.net.URI
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.9.21"
|
kotlin("jvm") version "1.9.21"
|
||||||
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "net.lamgc.scext"
|
group = "net.lamgc.scext"
|
||||||
@ -19,8 +20,14 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("org.slf4j:slf4j-api:2.0.10")
|
compileOnly("org.slf4j:slf4j-api:2.0.10")
|
||||||
compileOnly("io.github.microutils:kotlin-logging:3.0.5")
|
compileOnly("io.github.microutils:kotlin-logging:3.0.5")
|
||||||
|
implementation("ch.qos.logback:logback-classic:1.4.14")
|
||||||
compileOnly("net.lamgc:scalabot-extension:0.6.1")
|
compileOnly("net.lamgc:scalabot-extension:0.6.1")
|
||||||
|
|
||||||
|
implementation("com.fasterxml.jackson.core:jackson-core:2.16.1")
|
||||||
|
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
|
||||||
|
implementation("com.fasterxml.jackson.core:jackson-annotations:2.16.1")
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1")
|
||||||
|
|
||||||
val exposedVersion = "0.45.0"
|
val exposedVersion = "0.45.0"
|
||||||
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
|
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
|
||||||
implementation("org.jetbrains.exposed:exposed-crypt:$exposedVersion")
|
implementation("org.jetbrains.exposed:exposed-crypt:$exposedVersion")
|
||||||
@ -44,3 +51,27 @@ tasks.test {
|
|||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(17)
|
jvmToolchain(17)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
// maven("https://git.lamgc.me/api/packages/LamGC/maven") {
|
||||||
|
// credentials {
|
||||||
|
// username = project.properties["repo.credentials.self-git.username"].toString()
|
||||||
|
// password = project.properties["repo.credentials.self-git.password"].toString()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("maven") {
|
||||||
|
from(components["java"])
|
||||||
|
|
||||||
|
pom {
|
||||||
|
name.set("ScalaExt-OneDriveTransfer")
|
||||||
|
description.set("将 Telegram 中的文件转存至 OneDrive.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -9,19 +9,22 @@ import org.jetbrains.exposed.dao.id.LongIdTable
|
|||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
object MicrosoftAccounts : LongIdTable() {
|
object OneDriveTransferSettings : LongIdTable() {
|
||||||
val telegramUserId = long("tg_user_id").uniqueIndex()
|
val telegramUserId = long("tg_user_id").uniqueIndex()
|
||||||
val accountId = varchar("account_id", 128)
|
val accountId = varchar("account_id", 128)
|
||||||
val userName = varchar("user_name", 96)
|
val userName = varchar("user_name", 96)
|
||||||
|
val driveId = varchar("drive_id", 256)
|
||||||
|
val storagePath = varchar("storage_path", 512)
|
||||||
}
|
}
|
||||||
|
|
||||||
class MicrosoftAccount(id: EntityID<Long>) : LongEntity(id) {
|
class OneDriveTransferSetting(id: EntityID<Long>) : LongEntity(id) {
|
||||||
var telegramUserId by MicrosoftAccounts.telegramUserId
|
var telegramUserId by OneDriveTransferSettings.telegramUserId
|
||||||
var accountId by MicrosoftAccounts.accountId
|
var accountId by OneDriveTransferSettings.accountId
|
||||||
var userName by MicrosoftAccounts.accountId
|
var userName by OneDriveTransferSettings.userName
|
||||||
|
var driveId by OneDriveTransferSettings.driveId
|
||||||
|
var storagePath by OneDriveTransferSettings.storagePath
|
||||||
|
|
||||||
companion object : LongEntityClass<MicrosoftAccount>(MicrosoftAccounts)
|
companion object : LongEntityClass<OneDriveTransferSetting>(OneDriveTransferSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
object TokenCaches : LongIdTable() {
|
object TokenCaches : LongIdTable() {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package net.lamgc.scext.onedrive_transfer
|
package net.lamgc.scext.onedrive_transfer
|
||||||
|
|
||||||
data class ExtensionConfig(
|
data class ExtensionConfig(
|
||||||
val clientId: String,
|
val clientId: String = "",
|
||||||
val clientSecret: String,
|
val clientSecret: String = "",
|
||||||
val useCommandPrefix: Boolean
|
val useCommandPrefix: Boolean = true,
|
||||||
|
val maxFileSize: Long = 1024L * 1024 * 1024 * 4,
|
||||||
|
val maxTransferSize: Long = 1024L * 1024 * 1024 * 20,
|
||||||
)
|
)
|
||||||
|
177
src/main/kotlin/OneDriveTransferCenter.kt
Normal file
177
src/main/kotlin/OneDriveTransferCenter.kt
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package net.lamgc.scext.onedrive_transfer
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.AtomicDouble
|
||||||
|
import com.microsoft.graph.models.DriveItem
|
||||||
|
import com.microsoft.graph.models.DriveItemCreateUploadSessionParameterSet
|
||||||
|
import com.microsoft.graph.models.DriveItemUploadableProperties
|
||||||
|
import com.microsoft.graph.requests.GraphServiceClient
|
||||||
|
import com.microsoft.graph.tasks.IProgressCallback
|
||||||
|
import com.microsoft.graph.tasks.LargeFileUploadTask
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.telegram.abilitybots.api.bot.BaseAbilityBot
|
||||||
|
import org.telegram.telegrambots.meta.api.methods.GetFile
|
||||||
|
import org.telegram.telegrambots.meta.api.objects.Document
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.*
|
||||||
|
import kotlin.math.log
|
||||||
|
|
||||||
|
object OneDriveTransferCenter {
|
||||||
|
|
||||||
|
private val queue = PriorityBlockingQueue(100,
|
||||||
|
compareBy<OneDriveTransferTask> { it.document.fileSize }.thenBy { it.createdAt.time })
|
||||||
|
|
||||||
|
fun submitUploadTask(task: OneDriveTransferTask) {
|
||||||
|
queue.put(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object DefaultOneDriveTransferCallback : OneDriveTransferCallback {
|
||||||
|
override fun onProgress(progress: OneDriveTransferWorkerProgress) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTransferFailure(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress) {
|
||||||
|
task.bot.silent().send("""
|
||||||
|
OneDrive 中转任务执行失败
|
||||||
|
文件名:${task.document.fileName}
|
||||||
|
""".trimIndent(), task.tgUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTransferSuccess(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress) {
|
||||||
|
task.bot.silent().send("""
|
||||||
|
OneDrive 中转任务执行成功
|
||||||
|
文件名:${task.document.fileName}
|
||||||
|
""".trimIndent(), task.tgUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OneDriveTransferCallback {
|
||||||
|
fun onProgress(progress: OneDriveTransferWorkerProgress)
|
||||||
|
fun onTransferFailure(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress)
|
||||||
|
fun onTransferSuccess(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneDriveTransferTaskExecutor(
|
||||||
|
threadNum: Int,
|
||||||
|
val callback: OneDriveTransferCallback,
|
||||||
|
val taskQueue: PriorityBlockingQueue<OneDriveTransferTask>
|
||||||
|
) : ThreadPoolExecutor(threadNum, threadNum, 0, TimeUnit.SECONDS, ArrayBlockingQueue(50)) {
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger { }
|
||||||
|
|
||||||
|
val threadStatusMap = ConcurrentHashMap<Int, OneDriveTransferWorkerProgress>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (i in 0 until threadNum) {
|
||||||
|
submit(createWorker(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createWorker(id: Int): Runnable = Runnable {
|
||||||
|
while (Thread.interrupted()) {
|
||||||
|
val task = taskQueue.take()
|
||||||
|
val progress = OneDriveTransferWorkerProgress(task)
|
||||||
|
threadStatusMap[id] = progress
|
||||||
|
try {
|
||||||
|
doTransferFile(task, progress)
|
||||||
|
callback.onTransferSuccess(task, progress)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warn(e) { "OneDrive 中转任务执行失败: ${e.message}" }
|
||||||
|
callback.onTransferFailure(task, progress.apply {
|
||||||
|
this.exception = e
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
threadStatusMap.remove(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doTransferFile(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress) {
|
||||||
|
val file = task.bot.execute(
|
||||||
|
GetFile.builder()
|
||||||
|
.fileId(task.document.fileId)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
val graphClient = task.service.createGraphClient(task.tgUserId) ?: throw IllegalStateException("未登录 OneDrive.")
|
||||||
|
val drive = graphClient.drives(task.onedriveId).buildRequest().get() ?: throw IllegalStateException("无法获取 OneDrive 驱动器.")
|
||||||
|
if (file.fileSize > drive.quota!!.remaining!!) {
|
||||||
|
throw IllegalStateException("OneDrive 剩余空间不足.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 需要完善一下文件夹位置,文件名冲突处理的问题.
|
||||||
|
graphClient.drives(task.onedriveId).root().itemWithPath(task.storagePath).buildRequest().get()
|
||||||
|
val filePath = checkAndGetPath(graphClient, task.storagePath, task.document.fileName)
|
||||||
|
|
||||||
|
if (file.fileSize < 4 * 1024 * 1024) {
|
||||||
|
val fileBytes = task.bot.downloadFileAsStream(file).readAllBytes()
|
||||||
|
val driveItem = graphClient.drives(task.onedriveId).root().itemWithPath(filePath).content()
|
||||||
|
.buildRequest()
|
||||||
|
.put(fileBytes)
|
||||||
|
progress.driveItem = driveItem
|
||||||
|
progress.progress.set(1.0)
|
||||||
|
} else {
|
||||||
|
val uploadSession = graphClient.drives(task.onedriveId).root().itemWithPath(filePath)
|
||||||
|
.createUploadSession(DriveItemCreateUploadSessionParameterSet().apply {
|
||||||
|
this.item = DriveItemUploadableProperties().apply {
|
||||||
|
this.fileSize = file.fileSize
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.buildRequest()
|
||||||
|
.post() ?: throw IllegalStateException("无法创建 OneDrive 上传会话.")
|
||||||
|
val progressCallback = IProgressCallback { current, max ->
|
||||||
|
progress.progress.set(current.toDouble() / max.toDouble())
|
||||||
|
try {
|
||||||
|
callback.onProgress(progress)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warn(e) { "OneDrive 中转任务进度回调失败: ${e.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileStream = task.bot.downloadFileAsStream(file)
|
||||||
|
|
||||||
|
val largeFileUploadTask = LargeFileUploadTask(
|
||||||
|
uploadSession,
|
||||||
|
graphClient,
|
||||||
|
fileStream,
|
||||||
|
file.fileSize,
|
||||||
|
DriveItem::class.java
|
||||||
|
)
|
||||||
|
val uploadResult = largeFileUploadTask.upload(4 * 1024 * 1024, null, progressCallback)
|
||||||
|
if (progress.progress.get() == 1.0 && progress.exception != null) {
|
||||||
|
progress.driveItem = uploadResult.responseBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAndGetPath(graphClient: GraphServiceClient<Request>, storagePath: String, fileName: String): String {
|
||||||
|
val path = if (storagePath.endsWith("/")) {
|
||||||
|
storagePath
|
||||||
|
} else {
|
||||||
|
"$storagePath/"
|
||||||
|
}
|
||||||
|
return "$path$fileName"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class OneDriveTransferWorkerProgress(
|
||||||
|
val currentTask: OneDriveTransferTask,
|
||||||
|
val progress: AtomicDouble = AtomicDouble(),
|
||||||
|
var driveItem: DriveItem? = null,
|
||||||
|
var exception: Exception? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class OneDriveTransferTask(
|
||||||
|
val tgUserId: Long,
|
||||||
|
val bot: BaseAbilityBot,
|
||||||
|
val service: OneDriveTransferService,
|
||||||
|
val document: Document,
|
||||||
|
val onedriveId: String,
|
||||||
|
val storagePath: String,
|
||||||
|
val extra: MutableMap<String, Any> = mutableMapOf(),
|
||||||
|
val createdAt: Date = Date()
|
||||||
|
)
|
@ -1,11 +1,12 @@
|
|||||||
package net.lamgc.scext.onedrive_transfer
|
package net.lamgc.scext.onedrive_transfer
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.microsoft.aad.msal4j.*
|
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.telegram.abilitybots.api.bot.BaseAbilityBot
|
import org.telegram.abilitybots.api.bot.BaseAbilityBot
|
||||||
import org.telegram.abilitybots.api.objects.Ability
|
import org.telegram.abilitybots.api.objects.Ability
|
||||||
|
import org.telegram.abilitybots.api.objects.Ability.AbilityBuilder
|
||||||
import org.telegram.abilitybots.api.objects.Locality
|
import org.telegram.abilitybots.api.objects.Locality
|
||||||
import org.telegram.abilitybots.api.objects.Privacy
|
import org.telegram.abilitybots.api.objects.Privacy
|
||||||
import org.telegram.abilitybots.api.objects.Reply
|
import org.telegram.abilitybots.api.objects.Reply
|
||||||
@ -18,37 +19,33 @@ class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) :
|
|||||||
private val logger = KotlinLogging.logger { }
|
private val logger = KotlinLogging.logger { }
|
||||||
|
|
||||||
private val config: ExtensionConfig
|
private val config: ExtensionConfig
|
||||||
private val authClient: ConfidentialClientApplication
|
|
||||||
private val accountManager: MicrosoftAccountManager
|
|
||||||
private val onedriveService: OneDriveTransferService
|
private val onedriveService: OneDriveTransferService
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val configFile = File(dataFolder, "config.json")
|
config = loadConfiguration()
|
||||||
|
val db = Database.connect("jdbc:sqlite:${File(dataFolder, "./data.db").canonicalPath}", "org.sqlite.JDBC")
|
||||||
|
onedriveService = OneDriveTransferService(bot, config, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadConfiguration(): ExtensionConfig {
|
||||||
|
val configFile = File(this.dataFolder, "config.json")
|
||||||
|
val objectMapper = ObjectMapper().registerKotlinModule()
|
||||||
if (!configFile.exists()) {
|
if (!configFile.exists()) {
|
||||||
configFile.createNewFile()
|
configFile.createNewFile()
|
||||||
configFile.writeText("{}")
|
objectMapper.writeValue(configFile, ExtensionConfig())
|
||||||
|
return ExtensionConfig()
|
||||||
}
|
}
|
||||||
config = Gson().fromJson(configFile.reader(), ExtensionConfig::class.java)
|
return objectMapper.readValue(configFile, ExtensionConfig::class.java)
|
||||||
val db = Database.connect("jdbc:sqlite:${File(dataFolder, "./data.db").canonicalPath}", "org.sqlite.JDBC")
|
|
||||||
authClient = ConfidentialClientApplication.builder(
|
|
||||||
config.clientId,
|
|
||||||
ClientCredentialFactory.createFromSecret(config.clientSecret),
|
|
||||||
)
|
|
||||||
.authority(MicrosoftAccountManager.AUTHORITY)
|
|
||||||
.setTokenCacheAccessAspect(DatabaseTokenCache(db))
|
|
||||||
.build()
|
|
||||||
accountManager = MicrosoftAccountManager(authClient, db)
|
|
||||||
onedriveService = OneDriveTransferService(config, accountManager, authClient)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loginOneDrive(): Ability = Ability
|
fun loginOneDrive(): Ability = Ability
|
||||||
.builder()
|
.builder()
|
||||||
.name("odt_login")
|
.named("login")
|
||||||
.info("登录 OneDrive 账户.")
|
.info("登录 OneDrive 账户.")
|
||||||
.locality(Locality.USER)
|
.locality(Locality.USER)
|
||||||
.privacy(Privacy.PUBLIC)
|
.privacy(Privacy.PUBLIC)
|
||||||
.action { ctx ->
|
.action { ctx ->
|
||||||
val url = accountManager.createAuthorizationRequest(ctx.user().id)
|
val url = onedriveService.createLoginUrl(ctx.chatId())
|
||||||
ctx.bot().silent().send("""
|
ctx.bot().silent().send("""
|
||||||
请使用以下链接进行登录:
|
请使用以下链接进行登录:
|
||||||
$url
|
$url
|
||||||
@ -59,9 +56,8 @@ class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) :
|
|||||||
}
|
}
|
||||||
.reply(Reply.of(
|
.reply(Reply.of(
|
||||||
{bot, upd ->
|
{bot, upd ->
|
||||||
val token = MicrosoftAccountManager.getTokenFromUrl(URL(upd.message.text.trim()))
|
|
||||||
try {
|
try {
|
||||||
val account = accountManager.updateAccount(upd.message.chat.id, token)
|
val account = onedriveService.updateAccount(upd.message.chat.id, URL(upd.message.text.trim()))
|
||||||
bot.silent().send("""
|
bot.silent().send("""
|
||||||
登录成功!
|
登录成功!
|
||||||
Microsoft 账号:${account.userName}
|
Microsoft 账号:${account.userName}
|
||||||
@ -77,9 +73,10 @@ class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) :
|
|||||||
return@of false
|
return@of false
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
URL(upd.message.text)
|
URL(upd.message.text.trim())
|
||||||
return@of true
|
return@of true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
bot.silent().send("链接格式错误,请重新发送。", upd.message.chatId)
|
||||||
return@of false
|
return@of false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,14 +84,45 @@ class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) :
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun status(): Ability = Ability.builder()
|
fun status(): Ability = Ability.builder()
|
||||||
.name("")
|
.named("my")
|
||||||
|
.info("查看当前 OneDrive 设定信息.")
|
||||||
|
.locality(Locality.USER)
|
||||||
|
.privacy(Privacy.PUBLIC)
|
||||||
|
.action {
|
||||||
|
val account = onedriveService.accountManager.getAccountByTgUserId(it.chatId())
|
||||||
|
if (account == null) {
|
||||||
|
it.bot().silent().send("当前账户未登录 OneDrive.", it.chatId())
|
||||||
|
return@action
|
||||||
|
}
|
||||||
|
it.bot().silent().send("""
|
||||||
|
当前账户已登录 OneDrive.
|
||||||
|
Microsoft 账号:${account.userName}
|
||||||
|
""".trimIndent(), it.chatId())
|
||||||
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun setCommandName(name: String): String {
|
fun selectDrive(): Ability = Ability.builder()
|
||||||
if (config.useCommandPrefix) {
|
.named("select_drive")
|
||||||
return "odt_$name"
|
.info("选择 OneDrive 驱动器.")
|
||||||
}
|
.locality(Locality.USER)
|
||||||
return name
|
.privacy(Privacy.PUBLIC)
|
||||||
|
.action {
|
||||||
|
val currentDrive = onedriveService.getCurrentDrive(it.chatId())
|
||||||
|
val drives = onedriveService.listDriversByUserId(it.chatId())
|
||||||
|
if (drives.isEmpty()) {
|
||||||
|
it.bot().silent().send("当前账户没有 OneDrive 驱动器.", it.chatId())
|
||||||
|
return@action
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun AbilityBuilder.named(name: String): AbilityBuilder {
|
||||||
|
return if (config.useCommandPrefix) {
|
||||||
|
name("odt_$name")
|
||||||
|
} else {
|
||||||
|
name(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,16 @@ import java.net.URL
|
|||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
|
||||||
class MicrosoftAccountManager(private val authClient: ConfidentialClientApplication, private val db: Database) {
|
class OneDriveTransferSettingManager(private val authClient: ConfidentialClientApplication, private val db: Database) {
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
|
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMicrosoftByTelegramUser(userId: Long): MicrosoftAccount? {
|
fun getTransferSetting(userId: Long): OneDriveTransferSetting? {
|
||||||
return transaction(db) {
|
return transaction(db) {
|
||||||
return@transaction MicrosoftAccount.find { MicrosoftAccounts.telegramUserId eq userId }.firstOrNull()
|
return@transaction OneDriveTransferSetting.find { OneDriveTransferSettings.telegramUserId eq userId }.firstOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,45 +27,41 @@ class MicrosoftAccountManager(private val authClient: ConfidentialClientApplicat
|
|||||||
.builder("http://localhost:45678/", OAUTH2_SCOPE)
|
.builder("http://localhost:45678/", OAUTH2_SCOPE)
|
||||||
.responseMode(ResponseMode.QUERY)
|
.responseMode(ResponseMode.QUERY)
|
||||||
.prompt(Prompt.SELECT_ACCOUNT)
|
.prompt(Prompt.SELECT_ACCOUNT)
|
||||||
|
.state(userId.toString())
|
||||||
.build()
|
.build()
|
||||||
return authClient.getAuthorizationRequestUrl(parameters)
|
return authClient.getAuthorizationRequestUrl(parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAccount(userId: Long, token: String): MicrosoftAccount {
|
fun updateAccount(userId: Long, redirectUrl: URL, checkUserId: Boolean = true): OneDriveTransferSetting {
|
||||||
|
val queries = redirectUrl.getQueryMap()
|
||||||
|
if (checkUserId && userId != queries["state"]?.toLong()) {
|
||||||
|
throw IllegalArgumentException("State 不等于 userId.")
|
||||||
|
}
|
||||||
val future = authClient.acquireToken(
|
val future = authClient.acquireToken(
|
||||||
AuthorizationCodeParameters
|
AuthorizationCodeParameters
|
||||||
.builder(token, URI.create("http://localhost:45678/"))
|
.builder(queries["code"], URI.create("http://localhost:45678/"))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
val result = future.get()
|
val result = future.get()
|
||||||
return transaction(db) {
|
return transaction(db) {
|
||||||
val account = MicrosoftAccount.find { MicrosoftAccounts.telegramUserId eq userId }.firstOrNull()
|
val account = OneDriveTransferSetting.find { OneDriveTransferSettings.telegramUserId eq userId }.firstOrNull()
|
||||||
account?.apply {
|
account?.apply {
|
||||||
accountId = result.account().homeAccountId()
|
accountId = result.account().homeAccountId()
|
||||||
userName = result.account().username()
|
userName = result.account().username()
|
||||||
}
|
}
|
||||||
?: MicrosoftAccount.new {
|
?: OneDriveTransferSetting.new {
|
||||||
telegramUserId = userId
|
telegramUserId = userId
|
||||||
accountId = result.account().homeAccountId()
|
accountId = result.account().homeAccountId()
|
||||||
userName = result.account().username()
|
userName = result.account().username()
|
||||||
|
driveId = ""
|
||||||
|
storagePath = "Telegram Files/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAccountByTgUserId(userId: Long): MicrosoftAccount? {
|
fun getAccountByTgUserId(userId: Long): OneDriveTransferSetting? {
|
||||||
return transaction(db) {
|
return transaction(db) {
|
||||||
MicrosoftAccount.find { MicrosoftAccounts.telegramUserId eq userId }.firstOrNull()
|
OneDriveTransferSetting.find { OneDriveTransferSettings.telegramUserId eq userId }.firstOrNull()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAccessToken(account: MicrosoftAccount): IAuthenticationResult? {
|
|
||||||
val iAccount = authClient.accounts.get().find { it.homeAccountId() == account.accountId }
|
|
||||||
val silentParameters = SilentParameters.builder(OAUTH2_SCOPE, iAccount).build()
|
|
||||||
val future = authClient.acquireTokenSilently(silentParameters)
|
|
||||||
try {
|
|
||||||
return future.get()
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
throw e.cause ?: e
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,14 +73,9 @@ class MicrosoftAccountManager(private val authClient: ConfidentialClientApplicat
|
|||||||
"Files.ReadWrite",
|
"Files.ReadWrite",
|
||||||
"Files.Read.All",
|
"Files.Read.All",
|
||||||
"Files.ReadWrite.All",
|
"Files.ReadWrite.All",
|
||||||
|
"Sites.Read.All",
|
||||||
"Sites.ReadWrite.All",
|
"Sites.ReadWrite.All",
|
||||||
"offline_access"
|
"offline_access"
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getTokenFromUrl(url: URL): String {
|
|
||||||
return url.query.split("&").find { it.startsWith("code=") }?.substring(5) ?: throw IllegalArgumentException(
|
|
||||||
"Invalid URL."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,101 @@
|
|||||||
package net.lamgc.scext.onedrive_transfer
|
package net.lamgc.scext.onedrive_transfer
|
||||||
|
|
||||||
|
import com.microsoft.aad.msal4j.ClientCredentialFactory
|
||||||
import com.microsoft.aad.msal4j.ConfidentialClientApplication
|
import com.microsoft.aad.msal4j.ConfidentialClientApplication
|
||||||
|
import com.microsoft.aad.msal4j.IAccount
|
||||||
|
import com.microsoft.aad.msal4j.SilentParameters
|
||||||
|
import com.microsoft.graph.authentication.IAuthenticationProvider
|
||||||
|
import com.microsoft.graph.httpcore.HttpClients
|
||||||
|
import com.microsoft.graph.models.Drive
|
||||||
|
import com.microsoft.graph.requests.GraphServiceClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.telegram.abilitybots.api.bot.BaseAbilityBot
|
||||||
|
import org.telegram.telegrambots.meta.api.objects.Document
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
|
|
||||||
class OneDriveTransferService(
|
class OneDriveTransferService(
|
||||||
|
private val bot: BaseAbilityBot,
|
||||||
private val config: ExtensionConfig,
|
private val config: ExtensionConfig,
|
||||||
private val accountManager: MicrosoftAccountManager,
|
private val db: Database
|
||||||
private val authClient: ConfidentialClientApplication
|
|
||||||
) {
|
) {
|
||||||
|
val accountManager: OneDriveTransferSettingManager
|
||||||
|
val authClient: ConfidentialClientApplication = ConfidentialClientApplication.builder(
|
||||||
|
config.clientId,
|
||||||
|
ClientCredentialFactory.createFromSecret(config.clientSecret),
|
||||||
|
)
|
||||||
|
.authority(OneDriveTransferSettingManager.AUTHORITY)
|
||||||
|
.setTokenCacheAccessAspect(DatabaseTokenCache(db))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
init {
|
||||||
|
accountManager = OneDriveTransferSettingManager(authClient, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createGraphClient(userId: Long): GraphServiceClient<Request>? {
|
||||||
|
val cache = THREAD_CURRENT_GRAPH_CLIENT.get()
|
||||||
|
if (cache?.tgUserId == userId) {
|
||||||
|
return cache.client
|
||||||
|
}
|
||||||
|
val serviceClient = accountManager.getTransferSetting(userId)?.let {
|
||||||
|
authClient.accounts.get().firstOrNull { account ->
|
||||||
|
account.homeAccountId() == it.accountId
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
|
GraphServiceClient.builder()
|
||||||
|
.httpClient(HttpClients.createDefault(MsalAuthorizationProvider(authClient, it)))
|
||||||
|
.buildClient()
|
||||||
|
}
|
||||||
|
if (serviceClient == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
THREAD_CURRENT_GRAPH_CLIENT.set(ClientCache(userId, serviceClient))
|
||||||
|
return serviceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createLoginUrl(userId: Long) =
|
||||||
|
accountManager.createAuthorizationRequest(userId)
|
||||||
|
|
||||||
|
fun updateAccount(userId: Long, redirectUrl: URL) =
|
||||||
|
accountManager.updateAccount(userId, redirectUrl)
|
||||||
|
|
||||||
|
fun listDriversByUserId(userId: Long, page: Int = 1, size: Int = 10): List<Drive> {
|
||||||
|
return createGraphClient(userId)!!.drives()
|
||||||
|
.buildRequest()
|
||||||
|
.skip(page * size)
|
||||||
|
.get()?.currentPage ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentDrive(userId: Long): Drive {
|
||||||
|
val transferSetting =
|
||||||
|
accountManager.getTransferSetting(userId) ?: throw IllegalStateException("未登录 OneDrive.")
|
||||||
|
val graphClient = createGraphClient(userId) ?: throw IllegalStateException("未登录 OneDrive.")
|
||||||
|
return graphClient.drives(transferSetting.driveId)
|
||||||
|
.buildRequest()
|
||||||
|
.get() ?: throw IllegalStateException("无法获取当前 OneDrive 驱动器.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitUploadDocumentTask(userId: Long, document: Document) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val THREAD_CURRENT_GRAPH_CLIENT = ThreadLocal<ClientCache>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ClientCache(
|
||||||
|
val tgUserId: Long,
|
||||||
|
val client: GraphServiceClient<Request>,
|
||||||
|
)
|
||||||
|
|
||||||
|
class MsalAuthorizationProvider(private val authClientApplication: ConfidentialClientApplication, private val iAccount: IAccount) : IAuthenticationProvider {
|
||||||
|
override fun getAuthorizationTokenAsync(requestUrl: URL): CompletableFuture<String> {
|
||||||
|
return authClientApplication.acquireTokenSilently(
|
||||||
|
SilentParameters.builder(OneDriveTransferSettingManager.OAUTH2_SCOPE, iAccount).build()
|
||||||
|
).thenApply { it.accessToken() }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
13
src/main/kotlin/Utils.kt
Normal file
13
src/main/kotlin/Utils.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package net.lamgc.scext.onedrive_transfer
|
||||||
|
|
||||||
|
import java.net.URL
|
||||||
|
import java.net.URLDecoder
|
||||||
|
|
||||||
|
fun URL.getQueryMap(): Map<String, String> {
|
||||||
|
val queryMap = mutableMapOf<String, String>()
|
||||||
|
query.split("&").forEach {
|
||||||
|
val pair = it.split("=")
|
||||||
|
queryMap[pair[0]] = URLDecoder.decode(pair[1], "UTF-8")
|
||||||
|
}
|
||||||
|
return queryMap
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user