Giter VIP home page Giter VIP logo

melijn-bot's Introduction

melijn-bot

A multipurpose discord bot written in kotlin Some libraries we use:

  • jda - java discord api
  • kord-extensions - kord command framework and utils
  • kord-kommons - kord discord bot utilities and exposed SQL code generation utils
  • ktor - kotlin http client/server
  • exposed - kotlin SQL framework from jetbrains (we don't use the DAO's)

general information

This is the third rewrite of Melijn (links to previous and first repositories). These projects serve as a fun hobby project and learning place for me. Over the years we have grown a lot in size (amount of users) and our current codebase was messy and kinda unmaintainable (over 260 top level commands and about 80 database tables). This rewrite will keep scalability and maintainability in mind. Our website will also reside in this repository and use ktor. NO MORE JS FRAMEWORK WOOHOO.

Contributions

Contributions are welcome <3
Please follow the following guidelines to make it a nice experience for everyone.

  1. Check if there is an issue for what you want to add (if not create one)
  2. If there is an issue, check that there is no one working on it already (open draft PRS or replies indicating that someone is working on it).
  3. Don't solve multiple github issues in one PR (bug fixes or typos are acceptable)
  4. Follow our coding style and naming conventions.

Development environment

You will need:

  • a Postgresql server
  • a Redis server
  • a discord user account, bot user and server

If you want to use docker for your dev environment: example-docker-compose.yml

Optional (related features won't work unless supplied ofcourse):

  • Lavalink server (for music)
  • Spotify developer application tokens
  • Other API keys

Make sure intellij is using UTF-8 for property files, otherwise unicode might replaced with ? in messages. KSP code generation may sometimes print random errors (e.g. -1), just recompile will fix them normally :/

Starting the bot is done using the main function in Melijn.kt at the bottom.

An example configuration can be found here: ./example.env
Copy it to .env and place it in the root (same level as example.env)

Selfhosting

You will need:

  • a Postgresql server
  • a Redis server
  • a discord user account, bot user and server

and optionally: - [Lavalink](https://github.com/freyacodes/Lavalink) server (for music) - Spotify developer application tokens - Other API keys

It is recommended to run the bot inside docker using the example-docker-compose.yml.
To update the version you can use the :latest version tag OR you can manually update the version hash (you can find the latest version hashes here).

docker compose pull; docker compose down; docker compose up -d pulls the latest version if using :latest and restarts the containers.

For installing docker and docker-compose follow these instructions:

Inside the .env file you can point to the service name of the other containers inside this file as host (e.g. DB_HOST=melijn-postgres), docker will route it to the correct container

melijn-bot's People

Contributors

hetyoshiteam avatar pajamaman18 avatar toxicmushroom avatar zeroeightysix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

melijn-bot's Issues

Docker image crashes on startup

I cloned the repo and edited the env files and docker-compose.yml to fit my configuration, however, after starting the server, Melijn gets in a crash loop with this error:

melijn             | Invalid maximum heap size: -Xmx
melijn             | Error: Could not create the Java Virtual Machine.
melijn             | Error: A fatal exception has occurred. Program will exit.

I'm guessing I have to set the maximum allowed JVM heap size somewhere, but I can't find any ENV variable nor bootup string/arguments for it.

Persistant cooldown system

We need a persistant cooldown and ratelimit system so that long cooldowns are retained accross bot instances and restarts.

Proxy host and port getting parsed even if disabled

Stack trace:

melijn             | 2022-11-13 18:16:11  INFO    me.melijn.bot.services.StartupService | Done recovering music for shard #0
melijn             | 2022-11-13 18:17:19  INFO         me.melijn.bot.music.MusicManager | New trackManager for 958807531017367632
melijn             | 2022-11-13 18:17:19 ERROR                                   [Koin] | Instance creation error : could not create instance for [Singleton:'me.melijn.bot.web.api.WebManager',binds:me.melijn.bot.web.api.WebManager]: java.lang.NumberFormatException: For input string: "host"
melijn             | 	java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
melijn             | 	java.base/java.lang.Integer.parseInt(Integer.java:660)
melijn             | 	java.base/java.lang.Integer.parseInt(Integer.java:778)
melijn             | 	me.melijn.kordkommons.environment.BotSettings$int$1$1.invoke(BotSettings.kt:53)
melijn             | 	me.melijn.kordkommons.environment.BotSettings$int$1$1.invoke(BotSettings.kt:53)
melijn             | 	me.melijn.kordkommons.environment.BotSettings.getValue(BotSettings.kt:83)
melijn             | 	me.melijn.kordkommons.environment.BotSettings.int$lambda-7(BotSettings.kt:53)
melijn             | 	me.melijn.gen.Settings$HttpProxy.getPort(Settings.kt:87)
melijn             | 	me.melijn.bot.web.api.WebManager$proxiedHttpClient$1$1.invoke(WebManager.kt:45)
melijn             | 	me.melijn.bot.web.api.WebManager$proxiedHttpClient$1$1.invoke(WebManager.kt:43)
melijn             | 	io.ktor.client.HttpClientConfig$engine$1.invoke(HttpClientConfig.kt:32)
melijn             | 	io.ktor.client.HttpClientConfig$engine$1.invoke(HttpClientConfig.kt:30)
melijn             | 	io.ktor.client.engine.okhttp.OkHttp.create(OkHttp.kt:31)
melijn             | 	io.ktor.client.HttpClientKt.HttpClient(HttpClient.kt:41)
melijn             | 	me.melijn.bot.web.api.WebManager.<init>(WebManager.kt:41)
melijn             | 	me.melijn.gen.InjectionKoinModule1$module$1$23.invoke(InjectionKoinModule1.kt:55)
melijn             | 	me.melijn.gen.InjectionKoinModule1$module$1$23.invoke(InjectionKoinModule1.kt:55)
melijn             | 	org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:54)
melijn             | 	org.koin.core.instance.SingleInstanceFactory.create(SingleInstanceFactory.kt:46)
melijn             | 	org.koin.core.instance.SingleInstanceFactory$get$1.invoke(SingleInstanceFactory.kt:53)
melijn             | 	org.koin.core.instance.SingleInstanceFactory$get$1.invoke(SingleInstanceFactory.kt:51)
melijn             | 	org.koin.mp.KoinPlatformTools.synchronized(KoinPlatformTools.kt:20)
melijn             | 	org.koin.core.instance.SingleInstanceFactory.get(SingleInstanceFactory.kt:51)
melijn             | 	org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:111)
melijn             | 	org.koin.core.scope.Scope.resolveValue(Scope.kt:255)
melijn             | 	org.koin.core.scope.Scope.resolveInstance(Scope.kt:242)
melijn             | 	org.koin.core.scope.Scope.get(Scope.kt:205)
melijn             | 	me.melijn.bot.music.TrackLoader$special$$inlined$inject$default$2.invoke(KoinComponent.kt:74)
melijn             | 	kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
melijn             | 	me.melijn.bot.music.TrackLoader.getWebManager(TrackLoader.kt:18)
melijn             | 	me.melijn.bot.music.TrackLoader.searchTracks(TrackLoader.kt:37)
melijn             | 	me.melijn.bot.music.TrackLoader.searchTracks$default(TrackLoader.kt:31)
melijn             | 	me.melijn.bot.commands.music.MusicExtension$setup$22$1.invokeSuspend(MusicExtension.kt:587)
melijn             | 	me.melijn.bot.commands.music.MusicExtension$setup$22$1.invoke(MusicExtension.kt)
melijn             | 	me.melijn.bot.commands.music.MusicExtension$setup$22$1.invoke(MusicExtension.kt)
melijn             | 	com.kotlindiscord.kord.extensions.commands.application.slash.PublicSlashCommand.run(PublicSlashCommand.kt:108)
melijn             | 	com.kotlindiscord.kord.extensions.commands.application.slash.PublicSlashCommand$run$1.invokeSuspend(PublicSlashCommand.kt)
melijn             | 	kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
melijn             | 	kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
melijn             | 	kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
melijn             | 	kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
melijn             | 	kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
melijn             | 	kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
melijn             | 	io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
melijn             | 	kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
melijn             | 	kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
melijn             | 	kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
melijn             | 	kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
melijn             | 	kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
melijn             | 	kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)

Melijn tries to parse the

# tested with squid proxy, squid proxy can have an ip whitelist, idk if any docker password proxies exist
# if they do pls create and issue for it xd
# Proxy is used for fetching user supplied urls (may be ip loggers trying to start a DOS/DDOS)
PROXY_ENABLED=FALSE
PROXY_HOST=host
PROXY_PORT=host

variables even if PROXY_ENABLED is set to false. Setting them to 127.0.0.1 and 80 made the error disappear, even though I'm not running any proxy locally - which I suppose is correct because the proxy setting is not enabled and thus no connection is attempted. I think it should also avoid parsing, or at the very least have acceptable values by default, since host:host is not valid and causes this issue.

Thanks again!

Port economy

Feel free to create a draft pr if you want to start porting something so no 2 people are trying to do the same thing.

  • daily
  • balance
  • flip
  • pay
  • flipx (as subcommand for flip perhaps) #63
  • leaderboard #61
  • beg #29
  • slots
  • poker
  • rock-paper-scissors (not in dms, use interactive buttons where executed instead)
  • tic-tac-toe (not in dms, use interactive buttons where executed instead) [taken]

Anilist commands

Allow linking your anilist profile to your discord account
Commands for viewing information about

  • animes
  • mangas
  • characters
  • user profiles by name or discord user (and then lookup the linked user)
  • search versions that show the first 5-10 matches of the above types ^ (useful when media or characters have similar names)

/Open to additions\

Custom kordex chatCommandRegistry

This catcommandregistry implementation should provide support for:

  • user specific prefixes
  • guild specific prefixes
  • bot mentions as prefix
  • user cmd aliases
  • guild cmd aliases

Because the database implementation doesn't exist yet you may hardcode some examples in with comments that indicate where the database calls should go, user and guild prefixes should be string sets, user and guild aliases would be a Map<String, String>

Command Settings

  • private and server embed colors
  • disabling commands like in the current melijn bot, but it serves both as permission system and disable list just with a silent option. discord now has a working permission system.
  • cooldowns (might need to pr for better efficiency kordex)

prefixes support is partly done and is tracked by issue #4

Remove invoke and response will be dropped, this can be replaced with an automatic channel purger in the future
Aliases will be dropped

Music framework

  • add music framework around lavalink either as dependency or custom implementation it
  • a couple testing commands like /play /queue /nowplaying /stop

Customizable messages

Website ui and command interface to manage these, depends on adding some scripting lang to bot-kommons

Database interfaces and implementation for users and server prefixes

The bot needs a database implementation of some sort.
Preferably using some library so we don't need to maintain some raw SQL language. (eg. jetbrains exposed)

Database instances should be shared via Koin dependency injection.
We might also need an annotation processor to generate the koin stuff.

There should be a caching layer somehow that sets and gets from redis without having to manually do these kind of operations as it's a lot of repetitive error-prone work

Add settings/modes to name normalization

It would be preferable for users to be able to opt-in to harsher (or less harsh) filtering methods for the name normalization feature.

For example, an english-speaking community may want to restrict names to only ASCII.

This feature calls for a proper settings system though, as we likely don't want to shove everything in the guild_settings table when more and more options are added for the diverse set of guild-level features.

Latex command broke

2023-08-26 13:11:36 ERROR        c.k.k.e.commands.chat.ChatCommand | Error during execution of latex command (MessageReceivedEvent)
org.scilab.forge.jlatexmath.XMLResourceParseException: DefaultTeXFont.xml: error reading font 'fonts/latin/jlm_cmr10.ttf'. Error message: Problem reading font data.
	at org.scilab.forge.jlatexmath.DefaultTeXFontParser.createFont(DefaultTeXFontParser.java:393)
	at org.scilab.forge.jlatexmath.DefaultTeXFontParser.createFont(DefaultTeXFontParser.java:363)
	at org.scilab.forge.jlatexmath.FontInfo.getFont(FontInfo.java:309)
	at org.scilab.forge.jlatexmath.DefaultTeXFont.getChar(DefaultTeXFont.java:324)
	at org.scilab.forge.jlatexmath.DefaultTeXFont.getChar(DefaultTeXFont.java:284)
	at org.scilab.forge.jlatexmath.DefaultTeXFont.getChar(DefaultTeXFont.java:292)
	at org.scilab.forge.jlatexmath.DefaultTeXFont.getDefaultChar(DefaultTeXFont.java:341)
	at org.scilab.forge.jlatexmath.CharAtom.getChar(CharAtom.java:116)
	at org.scilab.forge.jlatexmath.CharAtom.createBox(CharAtom.java:91)
	at org.scilab.forge.jlatexmath.TeXFormula.createBox(TeXFormula.java:582)
	at org.scilab.forge.jlatexmath.TeXFormula.access$100(TeXFormula.java:94)
	at org.scilab.forge.jlatexmath.TeXFormula$TeXIconBuilder.build(TeXFormula.java:762)
	at org.scilab.forge.jlatexmath.TeXFormula.createTeXIcon(TeXFormula.java:796)
	at org.scilab.forge.jlatexmath.TeXFormula.createBufferedImage(TeXFormula.java:901)
	at me.melijn.bot.commands.MathExtension$setup$14$1.invokeSuspend(MathExtension.kt:197)
	at me.melijn.bot.commands.MathExtension$setup$14$1.invoke(MathExtension.kt)
	at me.melijn.bot.commands.MathExtension$setup$14$1.invoke(MathExtension.kt)
	at com.kotlindiscord.kord.extensions.commands.chat.ChatCommand$call$2.invokeSuspend(ChatCommand.kt:467)
	at com.kotlindiscord.kord.extensions.commands.chat.ChatCommand$call$2.invoke(ChatCommand.kt)
	at com.kotlindiscord.kord.extensions.commands.chat.ChatCommand$call$2.invoke(ChatCommand.kt)
	at com.kotlindiscord.kord.extensions.types.Lockable$DefaultImpls.withLock(Lockable.kt:24)
	at com.kotlindiscord.kord.extensions.commands.Command.withLock(Command.kt:46)
	at com.kotlindiscord.kord.extensions.commands.chat.ChatCommand.call$suspendImpl(ChatCommand.kt:385)
	at com.kotlindiscord.kord.extensions.commands.chat.ChatCommand.call(ChatCommand.kt)
	at com.kotlindiscord.kord.extensions.commands.chat.ChatCommand.call$default(ChatCommand.kt:378)
	at com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry.handleEvent$suspendImpl(ChatCommandRegistry.kt:229)
	at com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry$handleEvent$1.invokeSuspend(ChatCommandRegistry.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)

validate within `KordExUtils` Arguments.function not run

If you add a validate block to a KordExUtils Arguments.function, it won't get run. It still runs the validity check in the KordExUtils function, but will not check whether the Valid block added to the function is correct.
Example:

        val amount by availableCurrency("triedBettingNothing", "triedOverBetting") {
            name = "bet"
            description = "amount to bet"
            validate {
                val totalBet = this.value * xtimes
                val balance = balanceManager.get(this.context.user).balance
                failIf(tr("triedOverBetting", xtimes, this.value)) { true }
            }
        }

this will run perfectly fine even though it should always fail.

Leaving PROCESS_TESTINGSERVERID env variable empty crashes Melijn

While looking at the env file, I saw this entry:

# If empty, the bot will register global slash commands, otherwise guild commands to the configured server
PROCESS_TESTINGSERVERID=

I decided to leave it empty because I wanted to try having it register global slash commands. However, this error comes up and Melijn just crashes:

melijn             | 2022-11-13 17:48:06  INFO                     me.melijn.bot.Melijn | Starting Melijn..
melijn             | 2022-11-13 17:48:06  INFO                     me.melijn.bot.Melijn | [hostName] localhost
melijn             | Exception in thread "main" java.lang.NumberFormatException: For input string: ""
melijn             | 	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
melijn             | 	at java.base/java.lang.Long.parseLong(Long.java:724)
melijn             | 	at java.base/java.lang.Long.parseLong(Long.java:839)
melijn             | 	at me.melijn.kordkommons.environment.BotSettings$long$1$1.invoke(BotSettings.kt:49)
melijn             | 	at me.melijn.kordkommons.environment.BotSettings$long$1$1.invoke(BotSettings.kt:49)
melijn             | 	at me.melijn.kordkommons.environment.BotSettings.getValue(BotSettings.kt:83)
melijn             | 	at me.melijn.kordkommons.environment.BotSettings.long$lambda-6(BotSettings.kt:49)
melijn             | 	at me.melijn.gen.Settings$Process.getTestingServerId(Settings.kt:24)
melijn             | 	at me.melijn.bot.Melijn$susInit$botInstance$1$5.invokeSuspend(Melijn.kt:136)
melijn             | 	at me.melijn.bot.Melijn$susInit$botInstance$1$5.invoke(Melijn.kt)
melijn             | 	at me.melijn.bot.Melijn$susInit$botInstance$1$5.invoke(Melijn.kt)
melijn             | 	at com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder.applicationCommands(ExtensibleBotBuilder.kt:266)
melijn             | 	at me.melijn.bot.Melijn$susInit$botInstance$1.invokeSuspend(Melijn.kt:132)
melijn             | 	at me.melijn.bot.Melijn$susInit$botInstance$1.invoke(Melijn.kt)
melijn             | 	at me.melijn.bot.Melijn$susInit$botInstance$1.invoke(Melijn.kt)
melijn             | 	at com.kotlindiscord.kord.extensions.ExtensibleBotKt.ExtensibleBot(ExtensibleBot.kt:469)
melijn             | 	at me.melijn.bot.Melijn.susInit(Melijn.kt:57)
melijn             | 	at me.melijn.bot.MelijnKt.main(Melijn.kt:210)
melijn             | 	at me.melijn.bot.MelijnKt$main$2.invoke(Melijn.kt)
melijn             | 	at me.melijn.bot.MelijnKt$main$2.invoke(Melijn.kt)
melijn             | 	at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1.invokeSuspend(IntrinsicsJvm.kt:205)
melijn             | 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
melijn             | 	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:115)
melijn             | 	at kotlin.coroutines.jvm.internal.RunSuspendKt.runSuspend(RunSuspend.kt:19)
melijn             | 	at me.melijn.bot.MelijnKt.main(Melijn.kt)
melijn exited with code 1

From this line, especially, I saw that it still tries to parse the testingServerId string even if it's empty:

melijn             | 	at me.melijn.gen.Settings$Process.getTestingServerId(Settings.kt:24)

Anyways, thanks for the awesome work! Melijn is great :)

Moderation

depends on customizable messages, logchannels and permissions

OSU! commands

Porting the osu command from melijn to the rewrite, this should include the following functionality:

  • support osu, taiko, mania, catch modes
  • User profiles
  • Recent plays (paginated maybe, and maybe a command argument to exclude Failed plays)
  • Top plays (paginated)
  • Link your osu user to your discord account
    ~~ - [ ] We (should) use the new oauth api (will be a pain because you would need to touch the site module), but this allows to fetch more detailed information and for example draw performance graphs of a play ~~ this isn't a thing

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.