This commit is contained in:
LamGC 2024-01-04 17:01:04 +00:00
parent 05cce802f0
commit e4908dfd1e
6 changed files with 193 additions and 17 deletions

View File

@ -20,6 +20,17 @@ dependencies {
implementation("org.slf4j:slf4j-api:2.0.10") implementation("org.slf4j:slf4j-api:2.0.10")
implementation("net.lamgc:scalabot-extension:0.6.1") implementation("net.lamgc:scalabot-extension:0.6.1")
val exposedVersion = "0.45.0"
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-crypt:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:$exposedVersion")
implementation("org.xerial:sqlite-jdbc:3.44.1.0")
implementation("mysql:mysql-connector-java:8.0.33")
implementation("com.zaxxer:HikariCP:5.1.0")
implementation("com.microsoft.graph:microsoft-graph:5.77.0") implementation("com.microsoft.graph:microsoft-graph:5.77.0")
implementation("com.azure:azure-identity:1.11.1") implementation("com.azure:azure-identity:1.11.1")

View File

@ -0,0 +1,63 @@
package net.lamgc.scext.onedrive_transfer
import com.microsoft.aad.msal4j.ITokenCacheAccessAspect
import com.microsoft.aad.msal4j.ITokenCacheAccessContext
import org.jetbrains.exposed.dao.LongEntity
import org.jetbrains.exposed.dao.LongEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.LongIdTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transaction
object MicrosoftAccounts : LongIdTable() {
val telegramUserId = long("tg_user_id").uniqueIndex()
val accountId = varchar("account_id", 128)
val userName = varchar("user_name", 96)
}
class MicrosoftAccount(id: EntityID<Long>) : LongEntity(id) {
var telegramUserId by MicrosoftAccounts.telegramUserId
var accountId by MicrosoftAccounts.accountId
var userName by MicrosoftAccounts.accountId
companion object : LongEntityClass<MicrosoftAccount>(MicrosoftAccounts)
}
object TokenCaches : LongIdTable() {
val accountId = varchar("access_id", 256).uniqueIndex()
val cache = text("cache_data")
}
class TokenCache(id: EntityID<Long>) : LongEntity(id) {
var accountId by TokenCaches.accountId
var cache by TokenCaches.cache
companion object : LongEntityClass<TokenCache>(TokenCaches)
}
class DatabaseTokenCache(private val db: Database) : ITokenCacheAccessAspect {
override fun beforeCacheAccess(context: ITokenCacheAccessContext) {
transaction(db) {
TokenCache.find { TokenCaches.accountId eq context.account().homeAccountId() }.firstOrNull()?.let {
context.tokenCache().deserialize(it.cache)
}
}
}
override fun afterCacheAccess(context: ITokenCacheAccessContext) {
transaction(db) {
val existCache =
TokenCache.find { TokenCaches.accountId eq context.account().homeAccountId() }.firstOrNull()
if (existCache == null) {
TokenCache.new {
accountId = context.account().homeAccountId()
cache = context.tokenCache().serialize()
}
} else {
existCache.cache = context.tokenCache().serialize()
}
}
}
}

View File

@ -3,5 +3,4 @@ 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 authUrl: String?
) )

View File

@ -0,0 +1,72 @@
package net.lamgc.scext.onedrive_transfer
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction
import com.microsoft.aad.msal4j.*
import java.sql.Connection
import java.net.URL
import java.net.URI
class MicrosoftAccountManager(private val authClient: ConfidentialClientApplication, private val db: Database) {
init {
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
}
fun getMicrosoftByTelegramUser(userId: Long): MicrosoftAccount? {
return transaction(db) {
return@transaction MicrosoftAccount.find { MicrosoftAccounts.telegramUserId eq userId }.firstOrNull()
}
}
fun createAuthorizationRequest(userId: Long): URL {
val parameters = AuthorizationRequestUrlParameters
.builder("http://localhost:45678/", OAUTH2_SCOPE)
.responseMode(ResponseMode.QUERY)
.prompt(Prompt.SELECT_ACCOUNT)
.build()
return authClient.getAuthorizationRequestUrl(parameters)
}
fun updateAccount(userId: Long, token: String): MicrosoftAccount {
val future = authClient.acquireToken(
AuthorizationCodeParameters
.builder(token, URI.create("http://localhost:45678/"))
.build()
)
val result = future.get()
return transaction(db) {
val account = MicrosoftAccount.find { MicrosoftAccounts.telegramUserId eq userId }.firstOrNull()
account?.apply {
accountId = result.account().homeAccountId()
userName = result.account().username()
}
?: MicrosoftAccount.new {
telegramUserId = userId
accountId = result.account().homeAccountId()
userName = result.account().username()
}
}
}
companion object {
const val AUTHORITY = "https://login.microsoftonline.com/common"
val OAUTH2_SCOPE = setOf(
"User.Read",
"Files.Read",
"Files.ReadWrite",
"Files.Read.All",
"Files.ReadWrite.All",
"Sites.ReadWrite.All",
"offline_access"
)
fun getTokenFromUrl(url: URL): String {
return url.query.split("&").find { it.startsWith("code=") }?.substring(5) ?: throw IllegalArgumentException(
"Invalid URL."
)
}
}
}

View File

@ -1,17 +1,23 @@
package net.lamgc.scext.onedrive_transfer package net.lamgc.scext.onedrive_transfer
import com.azure.identity.AuthorizationCodeCredentialBuilder
import com.google.gson.Gson import com.google.gson.Gson
import com.microsoft.aad.msal4j.*
import com.microsoft.graph.requests.GraphServiceClient
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.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.util.AbilityExtension import org.telegram.abilitybots.api.util.AbilityExtension
import java.io.File import java.io.File
import java.net.URL
class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) : AbilityExtension { class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) : AbilityExtension {
private val config: ExtensionConfig private val config: ExtensionConfig
private val authClient: ConfidentialClientApplication
private val accountManager: MicrosoftAccountManager
init { init {
val configFile = File(dataFolder, "config.json") val configFile = File(dataFolder, "config.json")
@ -20,25 +26,43 @@ class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) :
configFile.writeText("{}") configFile.writeText("{}")
} }
config = Gson().fromJson(configFile.reader(), ExtensionConfig::class.java) config = Gson().fromJson(configFile.reader(), ExtensionConfig::class.java)
val db = Database.connect("jdbc:sqlite:${File(dataFolder, "./data.db").canonicalPath}", "org.sqlite.JDBC")
val credentialBuilder = AuthorizationCodeCredentialBuilder() authClient = ConfidentialClientApplication.builder(
.clientId(config.clientId) config.clientId,
.clientSecret(config.clientSecret) ClientCredentialFactory.createFromSecret(config.clientSecret),
.redirectUrl("http://localhost:8080") )
.authority(MicrosoftAccountManager.AUTHORITY)
.setTokenCacheAccessAspect(DatabaseTokenCache(db))
.build() .build()
accountManager = MicrosoftAccountManager(authClient, db)
} }
fun loginOneDrive(): Ability { fun loginOneDrive(): Ability = Ability
return Ability .builder()
.builder() .name("odt_login")
.name("odt_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)
ctx.bot().silent().send("""
请使用以下链接进行登录
$url
-------------------------------------------
登录成功后会显示无法访问是正常情况请将地址栏的链接发回给机器人
""".trimIndent(), ctx.chatId())
}
.reply(Reply.of(
{bot, upd ->
val token = MicrosoftAccountManager.getTokenFromUrl(URL(upd.message.text.trim()))
val account = accountManager.updateAccount(upd.message.chat.id, token)
},
{ upd ->
upd.hasMessage()
} }
.build() ))
} .build()
} }

View File

@ -0,0 +1,7 @@
package net.lamgc.scext.onedrive_transfer
class OneDriveTransferService() {
}