NitroCore is a fast and versatile GDPS core that can be easily deployed anywhere.
Pick one of providers below and follow the instructions:
— Free, easiest to deploy. Highly recommended
— Free, requires separate postgres provider, not recommended for Russia
— Not that free, for advanced users
- 🔧 Rich plugin system: Easily extend NitroCore functionality using our SDK
- 🔗 Strict input data validation: Prevent bad data from breaking your server with Zod schemas and
useGeometryDashTooling() - 📁 Clean code: Easy to understand code and best practices allow you start modifying core (or even contributing) in no time
- 🚨 Cheaters detection (v1.0 and newer): We use synthetic and ML tests to verify if your players are legitimate
- 🏭 Full support for hosting services: You can deploy multiple servers using one instance, NitroCore supports distributed configurations natively
- 🤷♂️ It's a decent GDPS Core, what else to ask?
If you want to create custom command for your server, just create new file in server/plugins (ex: server/plugins/mycommand.ts)
// Nitro Plugin will automatically register on boot
export default defineNitroPlugin(() => {
// Getting sdk for commands
const csdk = useSDK().commands
// Registering command
csdk.register(
"level", // Command type
"mycommand", // Command name (Usage will be !mycommand [args])
async (args: string[]) => {
// Here we get command context with everything you might need:
// User, Level, User Role, DB driver
const context = useCommandContext()
if (!context.level!.isOwnedBy(context.user.$.uid))
throw new Error("You should be owner and have cLvlAccess")
await context.level!.delete()
},
// You can specify which privileges role/user should have
{ cLvlAccess: true }
)
})SDK/commands.register and context signature:
function register(
type: "level" | "lists" | "profile" | "global",
command: string,
// (args: string[]) => MaybePromise<any>
handler: SDKCommandHandlerFunction,
// {permission1: true, permission2: true}
permissions?: SDKCommandHandlerPermission,
) {/* ... */}
type Context = {
drizzle: Database,
user: User,
role: Nullable<typeof rolesTable.$inferSelect>,
level?: Level, // Provided only when executing level command
list?: List // Provided only when executing list command
}As you can see, core can register several types of commands:
level: vanilla gd commands executed in level comments by moderators or level ownerslist: commands that are executed using level list commentsprofile: you can execute commands in profile comments too!global: If you want to register your command for all types, use global
⚠️ We don't guarantee that context will have all properties when using global type command as it can be executed everywhere
There is no universal solution to provide a way to use music from any music service. However, you can add your own using Music Providers.
Basically, database stores meta tags instead of direct URL. For example:
http:https://files.catbox.moe/track.mp3— built-in http provideryt:dQw4w9WgXcQ— you can use your own api to get mp3 files from YouTube (don't forget to store these mp3 files to reduce latency!)mycustomtype:ABCDEF0123456789— your custom provider with any data that you want- You can also do processing on the side and just provide plain
httplinks
Our built-in HTTP provider example:
// Nitro Plugin will automatically register on boot
export default defineNitroPlugin(() => {
// Getting sdk for music
const msdk = useSDK().music
// Registering music provider for "http" link type
msdk.registerProvider("http", new HTTPProvider())
})
/*
* Each provider has to implement getMusicById (for single tracks) and
* getBulkMusic (for multiple tracks to optimize requests by processing them together)
*/
class HTTPProvider implements SDKMusicProvider {
getMusicById = async (id: string) => {
const song = useMusicContext().song!
// These values will overwrite DB track metadata
return {
name: song.name,
author: song.artist,
size: song.size,
url: id,
originalUrl: song.url // OriginalUrl is needed to match your metadata with existing tracks
}
}
getBulkMusicById = async (ids: string[]) => {
const songs = useMusicContext().songs
return songs.map(song => ({
name: song.name,
author: song.artist,
size: song.size,
url: song.url.split("::", 1)[1],
originalUrl: song.url
}))
}
}Context type:
type Context = {
drizzle: Database,
song?: typeof songsTable.$inferSelect // provided only when calling getMusicById (for bulk processing use `songs` field)
songs: typeof songsTable.$inferSelect[] // if getMusicById provided, will contain one song entry
}Ever wanted to add ratebot via webhooks or do something on every level upload/rate/etc?
Look no further than Event Hooks:
- 🚀 They can be asynchronous, so they can continue running after request has ended
- 🔥 They can work as function hooks that modify data on the fly
Here is an example of a simple ratebot:
// Nitro Plugin will automatically register on boot
export default defineNitroPlugin(nitro => {
const esdk = useSDK().events
esdk.onAction("level_upload", async (uid, targetId, data) => {
const context = useEventContext()
const serverid = context.config.ServerConfig.SrvID
const moduleSettings = context.config.ServerConfig.ModuleConfig["discord"] as { webhook_url: string }
if (!moduleSettings.webhook_url) return
await discord.send(moduleSettings.webhook_url, ...)
})
})Context type:
type Context = {
drizzle: Database,
config: ServerConfig
}Distributed under the GPLv3 License. See LICENSE for more information.
