Initial commit
This commit is contained in:
commit
11f66d334f
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Project exclude paths
|
||||
/.gradle/
|
||||
/build/
|
||||
/.idea/
|
55
build.gradle
Normal file
55
build.gradle
Normal file
@ -0,0 +1,55 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
|
||||
id "com.github.johnrengelman.shadow" version "6.1.0"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('jar') {
|
||||
manifest {
|
||||
attributes('Implementation-Title': project.name,
|
||||
'Implementation-Version': project.version)
|
||||
}
|
||||
}
|
||||
|
||||
group 'dev.w1zzrd'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.fasterxml.jackson.core:jackson-core:2.12.2"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:2.12.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||
compile("net.dv8tion:JDA:4.2.0_168")
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Implementation-Title': project.name,
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': "dev.w1zzrd.swearnt.InitKt"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
2
settings.gradle
Normal file
2
settings.gradle
Normal file
@ -0,0 +1,2 @@
|
||||
rootProject.name = 'Swearnt'
|
||||
|
44
src/main/kotlin/dev/w1zzrd/swearnt/CommandListener.kt
Normal file
44
src/main/kotlin/dev/w1zzrd/swearnt/CommandListener.kt
Normal file
@ -0,0 +1,44 @@
|
||||
package dev.w1zzrd.swearnt
|
||||
|
||||
import net.dv8tion.jda.api.MessageBuilder
|
||||
import net.dv8tion.jda.api.entities.Message
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
|
||||
private fun Message.getMentionedIDs() = mentionedMembers.map(net.dv8tion.jda.api.entities.ISnowflake::getId)
|
||||
private fun MutableCollection<String>.addMentions(message: Message) = addAllNoDups(message.getMentionedIDs())
|
||||
private fun MutableCollection<String>.removeMentions(message: Message) = removeAll(message.getMentionedIDs())
|
||||
|
||||
class CommandListener (
|
||||
private val settings: Settings,
|
||||
private val adminIDs: Config,
|
||||
private val filterList: Config,
|
||||
private val parrotList: Config,
|
||||
private val swearFilter: SwearFilter
|
||||
): ListenerAdapter() {
|
||||
override fun onMessageReceived(event: MessageReceivedEvent) {
|
||||
if (event.author.id in adminIDs) {
|
||||
var check = event.message.contentDisplay.toLowerCase()
|
||||
|
||||
if (!check.startsWith("${settings.commandTrigger} "))
|
||||
return
|
||||
|
||||
check = check.substring(settings.commandTrigger.length + 1)
|
||||
|
||||
when {
|
||||
check.startsWith("filter ") -> filterList.addMentions(event.message)
|
||||
check.startsWith("unfilter ") -> filterList.removeMentions(event.message)
|
||||
check.startsWith("parrot ") -> parrotList.addMentions(event.message)
|
||||
check.startsWith("unparrot ") -> parrotList.removeMentions(event.message)
|
||||
check.startsWith("disallow ") -> swearFilter += check.substring("disallow ".length)
|
||||
check.startsWith("allow ") -> swearFilter -= check.substring("allow ".length)
|
||||
else -> return
|
||||
}
|
||||
|
||||
event.message.delete().submit()
|
||||
event.channel
|
||||
.sendMessage("Cool beans")
|
||||
.submitAndDelete(settings.botCleanupDelay)
|
||||
}
|
||||
}
|
||||
}
|
20
src/main/kotlin/dev/w1zzrd/swearnt/Config.kt
Normal file
20
src/main/kotlin/dev/w1zzrd/swearnt/Config.kt
Normal file
@ -0,0 +1,20 @@
|
||||
package dev.w1zzrd.swearnt
|
||||
|
||||
open class Config(
|
||||
val content: MutableCollection<String> = ArrayList(),
|
||||
private val fileName: String,
|
||||
sort: Boolean = true
|
||||
): MutableCollection<String> by content {
|
||||
init {
|
||||
load()
|
||||
|
||||
if (sort) {
|
||||
val sorted = content.sorted()
|
||||
content.clear()
|
||||
content.addAll(sorted)
|
||||
}
|
||||
}
|
||||
|
||||
fun load() = content.load(fileName)
|
||||
fun save() = content.save(fileName)
|
||||
}
|
29
src/main/kotlin/dev/w1zzrd/swearnt/Extensions.kt
Normal file
29
src/main/kotlin/dev/w1zzrd/swearnt/Extensions.kt
Normal file
@ -0,0 +1,29 @@
|
||||
package dev.w1zzrd.swearnt
|
||||
|
||||
import net.dv8tion.jda.api.entities.Message
|
||||
import net.dv8tion.jda.api.requests.RestAction
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
fun <T> MutableCollection<T>.addAllNoDups(elements: Collection<T>): Boolean =
|
||||
addAll(elements.filterNot(this::contains))
|
||||
|
||||
fun RestAction<Message>.submitAndDelete(
|
||||
delay: Long = BOT_MESSAGE_CLEANUP_DELAY,
|
||||
timeUnit: TimeUnit = TimeUnit.MILLISECONDS
|
||||
) = submit().thenAccept { it.delete().submitAfter(delay, timeUnit) }
|
||||
|
||||
fun <T> T.load(fileName: String): T where T: MutableCollection<String> {
|
||||
val file = File(fileName)
|
||||
if (file.isFile) addAllNoDups(file.readLines())
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun Iterable<String>.save(fileName: String) {
|
||||
val file = File(fileName)
|
||||
if (file.isFile && (!file.delete() || !file.createNewFile()))
|
||||
System.err.println("Failed to save file \"$fileName\"")
|
||||
else
|
||||
file.writeText(reduce { acc, s -> "$acc\n$s" })
|
||||
}
|
28
src/main/kotlin/dev/w1zzrd/swearnt/FilterListener.kt
Normal file
28
src/main/kotlin/dev/w1zzrd/swearnt/FilterListener.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package dev.w1zzrd.swearnt
|
||||
|
||||
import net.dv8tion.jda.api.MessageBuilder
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
|
||||
class FilterListener (
|
||||
private val settings: Settings,
|
||||
private val filter: SwearFilter,
|
||||
private val filterList: Collection<String>,
|
||||
private val parrotExemptionList: Collection<String>
|
||||
): ListenerAdapter() {
|
||||
override fun onMessageReceived(event: MessageReceivedEvent) {
|
||||
if (event.author.id !in parrotExemptionList &&
|
||||
filter shouldFilter event.message.contentDisplay &&
|
||||
event.author.id in filterList) {
|
||||
|
||||
event.message.delete().submit()
|
||||
event.channel.sendMessage(
|
||||
MessageBuilder()
|
||||
.append("You said a naughty word, ")
|
||||
.append(event.author)
|
||||
.append('!')
|
||||
.build()
|
||||
).submitAndDelete(settings.botCleanupDelay)
|
||||
}
|
||||
}
|
||||
}
|
46
src/main/kotlin/dev/w1zzrd/swearnt/Init.kt
Normal file
46
src/main/kotlin/dev/w1zzrd/swearnt/Init.kt
Normal file
@ -0,0 +1,46 @@
|
||||
package dev.w1zzrd.swearnt
|
||||
|
||||
import net.dv8tion.jda.api.JDABuilder
|
||||
import net.dv8tion.jda.api.entities.Activity
|
||||
import net.dv8tion.jda.api.entities.Message
|
||||
import net.dv8tion.jda.api.requests.RestAction
|
||||
import java.util.*
|
||||
|
||||
|
||||
const val BOT_MESSAGE_CLEANUP_DELAY = 5000L
|
||||
|
||||
fun main(vararg args: String) {
|
||||
val settings = loadSettings()
|
||||
val adminIDs = Config(fileName = settings.adminConf)
|
||||
val filteredUIDS = Config(fileName = settings.filterConf)
|
||||
val parrotList = Config(fileName = settings.parrotConf)
|
||||
val swearList = SwearFilter(fileName = settings.swearsConf)
|
||||
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(Thread {
|
||||
println("Saving configs...")
|
||||
settings.save()
|
||||
adminIDs.save()
|
||||
filteredUIDS.save()
|
||||
parrotList.save()
|
||||
swearList.save()
|
||||
println("Configs saved!")
|
||||
})
|
||||
|
||||
val token: String = if (args.isEmpty()) {
|
||||
print("Enter discord bot token: ")
|
||||
Scanner(System.`in`).nextLine()
|
||||
}
|
||||
else args[0]
|
||||
|
||||
|
||||
JDABuilder.createDefault(token)
|
||||
.setBulkDeleteSplittingEnabled(false)
|
||||
.setActivity(Activity.playing("with ur mom"))
|
||||
.addEventListeners(
|
||||
CommandListener(settings, adminIDs, filteredUIDS, parrotList, swearList),
|
||||
ParrotListener(settings, swearList, parrotList.content, RestAction<Message>::submitAndDelete),
|
||||
FilterListener(settings, swearList, filteredUIDS.content, parrotList.content)
|
||||
)
|
||||
.build()
|
||||
}
|
21
src/main/kotlin/dev/w1zzrd/swearnt/ParrotListener.kt
Normal file
21
src/main/kotlin/dev/w1zzrd/swearnt/ParrotListener.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package dev.w1zzrd.swearnt
|
||||
|
||||
import net.dv8tion.jda.api.MessageBuilder
|
||||
import net.dv8tion.jda.api.entities.Message
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
import net.dv8tion.jda.api.requests.RestAction
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ParrotListener(
|
||||
private val settings: Settings,
|
||||
private val filter: SwearFilter,
|
||||
private val parrotList: Collection<String>,
|
||||
private val submissionPolicy: RestAction<Message>.() -> CompletableFuture<*> = { submitAndDelete(settings.botCleanupDelay) }
|
||||
): ListenerAdapter() {
|
||||
override fun onMessageReceived(event: MessageReceivedEvent) {
|
||||
if (event.author.id in parrotList && filter shouldFilter event.message.contentDisplay)
|
||||
event.channel.sendMessage(MessageBuilder(event.message.contentRaw.toUpperCase()).build())
|
||||
.submissionPolicy()
|
||||
}
|
||||
}
|
42
src/main/kotlin/dev/w1zzrd/swearnt/Settings.kt
Normal file
42
src/main/kotlin/dev/w1zzrd/swearnt/Settings.kt
Normal file
@ -0,0 +1,42 @@
|
||||
package dev.w1zzrd.swearnt
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
fun loadSettings(settingsFile: String = "settings.json"): Settings {
|
||||
val file = File(settingsFile)
|
||||
if (file.isFile) return ObjectMapper().readValue(file, Settings::class.java)
|
||||
|
||||
return Settings()
|
||||
}
|
||||
|
||||
class Settings {
|
||||
@JsonProperty
|
||||
var commandTrigger = "bruh"
|
||||
|
||||
@JsonProperty
|
||||
var botCleanupDelay = 5000L
|
||||
|
||||
@JsonProperty
|
||||
var adminConf = "admin.conf"
|
||||
|
||||
@JsonProperty
|
||||
var filterConf = "filter.conf"
|
||||
|
||||
@JsonProperty
|
||||
var parrotConf = "parrot.conf"
|
||||
|
||||
@JsonProperty
|
||||
var swearsConf = "swears.conf"
|
||||
|
||||
fun save(fileName: String = "settings.json") {
|
||||
val file = File(fileName)
|
||||
if (file.isFile && (!file.delete() || !file.createNewFile()))
|
||||
System.err.println("Failed to save settings")
|
||||
else FileOutputStream(file).use {
|
||||
ObjectMapper().writeValue(it, this)
|
||||
}
|
||||
}
|
||||
}
|
61
src/main/kotlin/dev/w1zzrd/swearnt/SwearFilter.kt
Normal file
61
src/main/kotlin/dev/w1zzrd/swearnt/SwearFilter.kt
Normal file
@ -0,0 +1,61 @@
|
||||
package dev.w1zzrd.swearnt
|
||||
|
||||
private fun Char.similarChars(): String =
|
||||
arrayOf(
|
||||
"!1il",
|
||||
"o0ö",
|
||||
"aåä4",
|
||||
"b8",
|
||||
"e3",
|
||||
"t7",
|
||||
"5s"
|
||||
).firstOrNull { it.contains(this.toLowerCase()) } ?: toString()
|
||||
|
||||
private fun String.makeSwearFilter(start: Boolean = false, end: Boolean = false) = Regex (
|
||||
(if (start) "^.*?" else "^") +
|
||||
map(Char::similarChars)
|
||||
.map { if (it.length==1) it else "[$it]" }
|
||||
.reduce(String::plus) +
|
||||
(if (end) ".*?$" else "$")
|
||||
)
|
||||
|
||||
private fun String.makeStartSwearFilter() = makeSwearFilter(start = true, end = false)
|
||||
private fun String.makeEndSwearFilter() = makeSwearFilter(start = false, end = true)
|
||||
|
||||
private fun String.filteredBy(filter: Filter) = this in filter
|
||||
|
||||
data class Filter(val text: String) {
|
||||
private val filterPre = text.makeStartSwearFilter()
|
||||
private val filterPost = text.makeEndSwearFilter()
|
||||
|
||||
operator fun contains(text: String) = this.text == text || filterPre.matches(text) || filterPost.matches(text)
|
||||
}
|
||||
|
||||
class SwearFilter(content: MutableCollection<String> = ArrayList(), fileName: String): Config(content, fileName) {
|
||||
private val filters = map(::Filter).toMutableList()
|
||||
|
||||
infix fun shouldFilter(phrase: String) =
|
||||
phrase.toLowerCase()
|
||||
.replace(Regex("[.,?*;:<>|]"), "") // Remove some punctuation and stuff
|
||||
.split("\n", " ", "\t")
|
||||
.firstOrNull { word -> filters.firstOrNull(word::filteredBy) != null } != null
|
||||
|
||||
override operator fun contains(element: String) = this shouldFilter element
|
||||
override fun add(element: String) =
|
||||
if (element !in this) {
|
||||
filters += Filter(element) // Whack
|
||||
super.add(element)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
override fun remove(element: String) =
|
||||
if (element in this) {
|
||||
filters.removeIf(element::filteredBy)
|
||||
super.remove(element)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
21
src/test/kotlin/dev/w1zzrd/swearnt/test/SwearFilterTest.kt
Normal file
21
src/test/kotlin/dev/w1zzrd/swearnt/test/SwearFilterTest.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package dev.w1zzrd.swearnt.test
|
||||
|
||||
import dev.w1zzrd.swearnt.SwearFilter
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
val acceptableWords = arrayOf("frick", "heck", "darn", "hello", "cringe")
|
||||
val veryBadWords = arrayOf("dang", "cr4p", "stup!d")
|
||||
|
||||
class SwearFilterTest {
|
||||
private val filter = SwearFilter(mutableListOf("dang", "crap", "stupid"), "filter")
|
||||
|
||||
private fun testStrings(vararg words: String, shouldAllow: Boolean) =
|
||||
words.forEach { assert(filter shouldFilter it != shouldAllow) { it } }
|
||||
|
||||
|
||||
@Test
|
||||
fun testAcceptableStrings() = testStrings(*acceptableWords, shouldAllow = true)
|
||||
|
||||
@Test
|
||||
fun testVeryBadStrings() = testStrings(*veryBadWords, shouldAllow = false)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user