All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xyz.cssxsh.mirai.admin.MiraiAdministrator.kt Maven / Gradle / Ivy

The newest version!
package xyz.cssxsh.mirai.admin

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import net.mamoe.mirai.*
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
import net.mamoe.mirai.console.permission.*
import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel
import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
import net.mamoe.mirai.console.util.ContactUtils.render
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.code.*
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.utils.*
import xyz.cssxsh.mirai.admin.command.*
import xyz.cssxsh.mirai.admin.data.*
import xyz.cssxsh.mirai.admin.mail.*
import xyz.cssxsh.mirai.spi.*
import kotlin.collections.*
import kotlin.coroutines.*
import kotlin.io.path.*

/**
 * 事件监听及定时器
 */
public object MiraiAdministrator : SimpleListenerHost() {

    override fun handleException(context: CoroutineContext, exception: Throwable) {
        when (exception) {
            is CancellationException -> {
                // ...
            }
            is ExceptionInEventHandlerException -> {
                logger.warning({ "MiraiAdministrator with ${exception.event}" }, exception.cause)
            }
            else -> {
                logger.warning({ "MiraiAdministrator" }, exception)
            }
        }
    }

    // region Approver

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun MemberJoinRequestEvent.handle() {
        if ((group?.botPermission ?: MemberPermission.MEMBER) < MemberPermission.ADMINISTRATOR) return
        for (approver in ComparableService()) {
            try {
                when (val status = approver.approve(event = this)) {
                    ApproveResult.Accept -> accept()
                    is ApproveResult.Reject -> reject(blackList = status.black, message = status.message)
                    ApproveResult.Ignore -> continue
                }
                intercept()
                break
            } catch (cause: IllegalStateException) {
                logger.warning({ "${approver.id} 审核 $this 失败" }, cause)
                continue
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun MemberJoinEvent.handle() {
        if (group.botPermission < MemberPermission.ADMINISTRATOR) return
        for (approver in ComparableService()) {
            try {
                when (val status = approver.approve(event = this)) {
                    ApproveResult.Accept -> Unit
                    is ApproveResult.Reject -> member.kick(message = status.message, block = status.black)
                    ApproveResult.Ignore -> continue
                }
                intercept()
                break
            } catch (cause: IllegalStateException) {
                logger.warning({ "${approver.id} 审核 $this 失败" }, cause)
                continue
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun NewFriendRequestEvent.handle() {
        for (approver in ComparableService()) {
            try {
                when (val status = approver.approve(event = this)) {
                    ApproveResult.Accept -> accept()
                    is ApproveResult.Reject -> reject(blackList = status.black)
                    ApproveResult.Ignore -> continue
                }
                intercept()
                break
            } catch (cause: IllegalStateException) {
                logger.warning({ "${approver.id} 审核 $this 失败" }, cause)
                continue
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun FriendAddEvent.handle() {
        for (approver in ComparableService()) {
            try {
                when (val status = approver.approve(event = this)) {
                    ApproveResult.Accept -> Unit
                    is ApproveResult.Reject -> {
                        friend.sendMessage(message = status.message)
                        friend.delete()
                    }
                    ApproveResult.Ignore -> continue
                }
                intercept()
                break
            } catch (cause: IllegalStateException) {
                logger.warning({ "${approver.id} 审核 $this 失败" }, cause)
                continue
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun BotInvitedJoinGroupRequestEvent.handle() {
        for (approver in ComparableService()) {
            try {
                when (val status = approver.approve(event = this)) {
                    ApproveResult.Accept -> accept()
                    is ApproveResult.Reject -> {
                        invitor?.sendMessage(message = status.message)
                        ignore()
                    }
                    ApproveResult.Ignore -> continue
                }
                intercept()
                break
            } catch (cause: IllegalStateException) {
                logger.warning({ "${approver.id} 审核 $this 失败" }, cause)
                continue
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun BotJoinGroupEvent.handle() {
        for (approver in ComparableService()) {
            try {
                when (val status = approver.approve(event = this)) {
                    ApproveResult.Accept -> Unit
                    is ApproveResult.Reject -> {
                        @OptIn(MiraiExperimentalApi::class)
                        if (this is BotJoinGroupEvent.Invite) {
                            invitor.sendMessage(message = status.message)
                        }
                        group.quit()
                    }
                    ApproveResult.Ignore -> continue
                }
                intercept()
                break
            } catch (cause: IllegalStateException) {
                logger.warning({ "${approver.id} 审核 $this 失败" }, cause)
                continue
            }
        }
    }

    // XXX: AdminContactCommand ...
    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun FriendMessageEvent.approve() {
        if (sender.id != AdminSetting.owner) return
        if (message.anyIsInstance().not()) return
        val original = (quote(event = this) ?: return).originalMessage.contentToString()
        val id = ("""(?<=with <)\d+""".toRegex().find(original)?.value ?: return).toLong()

        val content = message.contentToString()
        val accept = MiraiAutoApprover.replyAccept.toRegex() in content
        val reject = MiraiAutoApprover.replyReject.toRegex() in content
        val black = MiraiAutoApprover.replyBlack.toRegex() in content
        val reply = content.substringAfter('\n')

        AdminContactCommand.runCatching {
            when {
                accept -> toCommandSender().handle(id = id, accept = true, black = false, message = "")
                reject -> toCommandSender().handle(id = id, accept = false, black = false, message = reply)
                black -> toCommandSender().handle(id = id, accept = false, black = true, message = reply)
            }
        }.onFailure { cause ->
            logger.error({ "handle contact request failure." }, cause)
        }
        intercept()
    }

    // endregion

    // region Timer

    /**
     * 启动一个群定时服务
     */
    @PublishedApi
    internal fun GroupTimerService<*>.start(target: Group) {
        records.remove(target.id)?.cancel()
        val job = launch(target.coroutineContext) service@{
            while (isActive) {
                when (val millis = wait(contact = target)) {
                    null -> break
                    else -> {
                        logger.debug { "${target.render()} timer after ${millis}ms" }
                        delay(millis)
                    }
                }

                when (this@start) {
                    is GroupAllowTimer -> launch {
                        for ((id, permit) in run(target)) {
                            try {
                                if (permit) {
                                    AbstractPermitteeId.AnyMember(target.id).permit(permissionId = id)
                                } else {
                                    AbstractPermitteeId.AnyMember(target.id).cancel(permissionId = id, false)
                                }
                            } catch (cause: NoSuchElementException) {
                                logger.error({ "$id set failure with $id" }, cause)
                            }
                        }
                    }
                    is GroupCurfewTimer -> launch curfew@{
                        try {
                            val moment = run(target) ?: return@curfew
                            if (!target.settings.isMuteAll) {
                                target.settings.isMuteAll = true
                            }
                            delay(moment)
                            if (target.settings.isMuteAll) {
                                target.settings.isMuteAll = false
                            }
                        } catch (cause: PermissionDeniedException) {
                            logger.error({ "${target.render()} mute set failure with $id" }, cause)
                        }
                    }
                    is MemberCleaner -> launch {
                        for ((member, reason) in run(target)) {
                            try {
                                member.kick(message = reason)
                            } catch (cause: PermissionDeniedException) {
                                logger.error({ "${member.render()} clean failure with $id" }, cause)
                            }
                            // XXX: Operation too fast
                            delay(60_000L)
                        }
                    }
                    is MemberNickCensor -> launch {
                        val censor = run(target)
                        for (member in target.members) {
                            try {
                                member.nameCard = censor(member) ?: continue
                            } catch (cause: PermissionDeniedException) {
                                logger.error({ "${target.render()} nick set failure with $id" }, cause)
                            }
                        }
                    }
                    is MemberTitleCensor -> launch {
                        val censor = run(target)
                        for (member in target.members) {
                            try {
                                member.specialTitle = censor(member) ?: continue
                            } catch (cause: PermissionDeniedException) {
                                logger.error({ "${target.render()} title set failure with $id" }, cause)
                            }
                        }
                    }
                }.invokeOnCompletion { cause ->
                    if (cause != null) {
                        logger.error({ "${target.render()} timer run failure with $id" }, cause)
                    } else {
                        logger.verbose { "${target.render()} timer run success with $id" }
                    }
                }
                // XXX: 保险, 避免 corn 输入错误,导致疯狂执行
                delay(60_000L)
            }
        }
        job.invokeOnCompletion {
            records.remove(target.id, job)
        }
        records[target.id] = job
    }

    /**
     * 启动一个定时消息服务
     */
    @PublishedApi
    internal fun BotTimingMessage.start(from: Bot) {
        records.remove(from.id)?.cancel()
        val job = launch(from.coroutineContext) {
            while (isActive) {
                when (val millis = wait(contact = from)) {
                    null -> break
                    else -> {
                        logger.debug { "${from.render()} timing message after ${millis}ms" }
                        delay(millis)
                    }
                }

                run(contact = from).onCompletion { cause ->
                    if (cause != null) {
                        logger.error({ "${from.render()} timer run failure with $id" }, cause)
                    }
                }.collect { receipt ->
                    logger.info { "${from.render()} timer ${receipt.target.render()} success with $id" }
                }
                // XXX: 保险, 避免 corn 输入错误,导致疯狂执行
                delay(60_000L)
            }
        }
        job.invokeOnCompletion {
            records.remove(from.id, job)
        }
        records[from.id] = job
    }

    /**
     * 启动一个初始化服务
     */
    @PublishedApi
    internal fun BotOnlineAction.start(from: Bot) {
        launch(from.coroutineContext) {
            run(bot = from)
        }.invokeOnCompletion { cause ->
            if (cause != null) {
                logger.error({ "${from.render()} online action run failure with $id" }, cause)
            }
        }
    }

    @EventHandler
    internal fun BotOnlineEvent.mark() {
        for (timer in ComparableService<GroupTimerService<*>>()) {
            for (group in bot.groups) {
                timer.start(target = group)
            }
        }
        for (timer in ComparableService<BotTimingMessage>()) {
            timer.start(from = bot)
        }
        for (action in ComparableService<BotOnlineAction>()) {
            action.start(from = bot)
        }
    }

    @EventHandler
    internal fun BotGroupPermissionChangeEvent.mark() {
        if (new < MemberPermission.ADMINISTRATOR) return
        for (timer in ComparableService<GroupTimerService<*>>()) {
            timer.start(target = group)
        }
    }

    // endregion

    // region Censor

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun GroupMessageEvent.mark() {
        if (group.botPermission <= sender.permission) return
        for (censor in ComparableService<ContentCensor>()) {
            if (censor.handle(event = this)) {
                intercept()
                break
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun NudgeEvent.mark() {
        if (from !is Member) return
        if ((subject as Group).botPermission <= (from as Member).permission) return
        for (censor in ComparableService<ContentCensor>()) {
            if (censor.handle(event = this)) {
                intercept()
                break
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun MessageEvent.check() {
        for (blacklist in ComparableService<BlackListService>()) {
            if (blacklist.check(sender)) {
                intercept()
                break
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    internal suspend fun NudgeEvent.check() {
        val sender = from as? User ?: return
        for (blacklist in ComparableService<BlackListService>()) {
            if (blacklist.check(sender)) {
                intercept()
                break
            }
        }
    }

    // endregion

    // region Comment

    private val comments: MutableMap<Long, Long> = HashMap()

    @EventHandler(concurrency = ConcurrencyKind.LOCKED)
    internal suspend fun MessageEvent.comment() {
        when {
            sender.id == AdminSetting.owner -> return
            this is UserMessageEvent && AdminCommentConfig.user &&
                message.findIsInstance<PlainText>()?.content?.getOrNull(0) != '/' -> Unit
            message.findIsInstance<QuoteReply>()?.source?.fromId == bot.id && AdminCommentConfig.quote -> Unit
            message.findIsInstance<At>()?.target == bot.id && AdminCommentConfig.at -> Unit
            else -> return
        }

        if (toCommandSender().hasPermission(AdminCommentConfig.permission).not()) return
        if ((comments[sender.id] ?: 0) + AdminCommentConfig.interval > System.currentTimeMillis()) return

        comments[sender.id] = System.currentTimeMillis()

        launch {
            val forward = buildForwardMessage(subject) {
                displayStrategy = object : ForwardMessage.DisplayStrategy {
                    override fun generateTitle(forward: RawForwardMessage): String {
                        return "来自 ${sender.render()} 的留言"
                    }
                }

                try {
                    quote(this@comment)?.let {
                        it.fromId at it.time says it.originalMessage
                    }
                } catch (cause: Exception) {
                    logger.warning({ "回溯评论上文失败" }, cause)
                }

                add(this@comment)
            }

            forward.sendTo(contact = bot.owner())

            if (AdminCommentConfig.reply.isEmpty()) return@launch
            MiraiCode.deserializeMiraiCode(code = AdminCommentConfig.reply, contact = sender)
                .plus(message.quote())
                .sendTo(contact = subject)
        }
    }

    // endregion

    // region Auto

    @EventHandler
    internal suspend fun BotMuteEvent.handle() {
        with(AdminAutoQuitConfig) {
            if (!admin && group.botPermission.isAdministrator()) return
            if (!owner && operator.id == AdminSetting.owner) return
            if (durationSeconds < limit) return

            delay(duration)

            // 检查是否还是禁言状态,如果是就退群
            if (group.isBotMuted) {
                launch {
                    bot.owner().sendMessage("因为禁言,将自动退群 ${group.render()}")
                }
                group.quit()
            }
        }
    }

    @EventHandler
    internal fun BotOfflineEvent.handle() {
        if (AdminMailConfig.notify.not()) return
        if (AdminMailConfig.close.not() && this is BotOfflineEvent.Active) return
        val session = buildMailSession {
            AdminMailConfig.properties.inputStream().use {
                load(it)
            }
        }
        val offline = this

        launch {
            val mail = buildMailContent(session) {
                to = AdminMailConfig.offline.ifEmpty { "${AdminSetting.owner}@qq.com" }
                title = if (reconnect) "机器人掉线通知" else "机器人下线通知"
                text {
                    append(bot)
                    @OptIn(MiraiInternalApi::class)
                    when (offline) {
                        is BotOfflineEvent.Active -> {
                            append("主动离线")
                        }
                        is BotOfflineEvent.Dropped -> {
                            append("因网络问题而掉线")
                            if (offline.cause != null) {
                                append('\n')
                                append("cause:\n")
                                append(offline.cause!!.stackTraceToString())
                            }
                        }
                        is BotOfflineEvent.Force -> {
                            append("被挤下线.")
                        }
                        is BotOfflineEvent.MsfOffline -> {
                            append("被服务器断开.")
                            if (offline.cause != null) {
                                append('\n')
                                append("cause:\n")
                                append(offline.cause!!.stackTraceToString())
                            }
                        }
                        is BotOfflineEvent.RequireReconnect -> {
                            append("服务器主动要求更换另一个服务器.")
                            if (offline.cause != null) {
                                append('\n')
                                append("cause:\n")
                                append(offline.cause!!.stackTraceToString())
                            }
                        }
                    }

                    var start = 0
                    while (isActive) {
                        val index = indexOf("\t", start)
                        if (index == -1) break
                        replace(index, index + 1, "    ")
                        start = index + 4
                    }
                }
                file("console.log") {
                    val logs = java.io.File("logs")
                    logs.listFiles()?.maxByOrNull { it.lastModified() }

                }
                file("network.log") {
                    val logs = java.io.File("bots/${bot.id}/logs")
                    logs.listFiles()?.maxByOrNull { it.lastModified() }
                }
            }

            val current = Thread.currentThread()
            val oc = current.contextClassLoader
            try {
                current.contextClassLoader = AdminMailConfig::class.java.classLoader
                jakarta.mail.Transport.send(mail)
            } catch (cause: jakarta.mail.MessagingException) {
                logger.error({ "邮件发送失败" }, cause)
            } finally {
                current.contextClassLoader = oc
            }
        }
    }

    // endregion
}
</code></pre>    <br/>
    <br/>
    <div id="right-banner">
            </div>
    <div id="left-banner">
            </div>
<div class='clear'></div>
    <aside class="related-items">
        <section>
            <div class="panel panel-primary">
                <div class="panel-heading margin-bottom">Related Artifacts</div>
                <div class="">
                    <a title='This artifact is from the group mysql' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/mysql/mysql-connector-java' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> mysql-connector-java <small class='group-info' >mysql</small></a><br/><a title='This artifact is from the group com.github.codedrinker' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.github.codedrinker/facebook-messenger' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> facebook-messenger <small class='group-info' >com.github.codedrinker</small></a><br/><a title='This artifact is from the group org.seleniumhq.selenium' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.seleniumhq.selenium/selenium-java' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> selenium-java <small class='group-info' >org.seleniumhq.selenium</small></a><br/><a title='This artifact is from the group com.github.sola92' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.github.sola92/instagram-java' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> instagram-java <small class='group-info' >com.github.sola92</small></a><br/><a title='This artifact is from the group com.google.code.gson' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.google.code.gson/gson' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> gson <small class='group-info' >com.google.code.gson</small></a><br/><a title='This artifact is from the group org.apache.poi' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.apache.poi/poi' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> poi <small class='group-info' >org.apache.poi</small></a><br/><a title='This artifact is from the group org.apache.httpcomponents' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.apache.httpcomponents/httpclient' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> httpclient <small class='group-info' >org.apache.httpcomponents</small></a><br/><a title='This artifact is from the group org.json' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.json/json' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> json <small class='group-info' >org.json</small></a><br/><a title='This artifact is from the group com.google.code.facebook-java-api' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.google.code.facebook-java-api/facebook-java-api' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> facebook-java-api <small class='group-info' >com.google.code.facebook-java-api</small></a><br/><a title='This artifact is from the group org.apache.poi' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.apache.poi/poi-ooxml' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> poi-ooxml <small class='group-info' >org.apache.poi</small></a><br/><a title='This artifact is from the group com.fasterxml.jackson.core' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.fasterxml.jackson.core/jackson-databind' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> jackson-databind <small class='group-info' >com.fasterxml.jackson.core</small></a><br/><a title='This artifact is from the group junit' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/junit/junit' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> junit <small class='group-info' >junit</small></a><br/><a title='This artifact is from the group org.primefaces' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.primefaces/primefaces' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> primefaces <small class='group-info' >org.primefaces</small></a><br/><a title='This artifact is from the group com.github.noraui' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.github.noraui/ojdbc7' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> ojdbc7 <small class='group-info' >com.github.noraui</small></a><br/><a title='This artifact is from the group com.jfoenix' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.jfoenix/jfoenix' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> jfoenix <small class='group-info' >com.jfoenix</small></a><br/><a title='This artifact is from the group org.testng' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.testng/testng' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> testng <small class='group-info' >org.testng</small></a><br/><a title='This artifact is from the group com.googlecode.json-simple' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.googlecode.json-simple/json-simple' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> json-simple <small class='group-info' >com.googlecode.json-simple</small></a><br/><a title='This artifact is from the group org.seleniumhq.selenium' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.seleniumhq.selenium/selenium-server' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> selenium-server <small class='group-info' >org.seleniumhq.selenium</small></a><br/><a title='This artifact is from the group com.itextpdf' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.itextpdf/itextpdf' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> itextpdf <small class='group-info' >com.itextpdf</small></a><br/><a title='This artifact is from the group org.springframework' class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.springframework/spring-core' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> spring-core <small class='group-info' >org.springframework</small></a><br/>                </div>
            </div>
        </section>
        <section>
            <div class="panel panel-primary">
                <div class="panel-heading margin-bottom">Related Groups</div>
                <div class="">
                    <a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.springframework' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.springframework</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.apache.poi' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.apache.poi</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.hibernate' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.hibernate</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.springframework.boot' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.springframework.boot</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.fasterxml.jackson.core' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> com.fasterxml.jackson.core</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.itextpdf' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> com.itextpdf</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.seleniumhq.selenium' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.seleniumhq.selenium</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/mysql' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> mysql</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.finos.legend.engine' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.finos.legend.engine</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.apache.httpcomponents' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.apache.httpcomponents</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.apache.logging.log4j' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.apache.logging.log4j</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.openjfx' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.openjfx</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.apache.commons' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.apache.commons</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/org.json' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> org.json</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.google.guava' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> com.google.guava</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.google.zxing' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> com.google.zxing</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/net.sf.jasperreports' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> net.sf.jasperreports</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/javax.xml.bind' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> javax.xml.bind</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/ojdbc' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> ojdbc</a><br/><a class='btn btn-default btn-xs small-margin-bottom ellipsis sidebar-btn' href='/artifacts/com.google.code.facebook-java-api' ><i class="fa fa-arrow-circle-right" aria-hidden="true"></i> com.google.code.facebook-java-api</a><br/>                </div>
            </div>
        </section>
    </aside>
    <div class='clear'></div>
</main>
</div>
<br/><br/>
    <div class="align-center">&copy; 2015 - 2024 <a href="/legal-notice.php">Weber Informatics LLC</a>&nbsp;|&nbsp;<a href="/data-protection.php">Privacy Policy</a></div>
<br/><br/><br/><br/><br/><br/>
</body>
</html>