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