-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/eventhooks #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f30710d
e8a89c3
cf4ceaa
3268a33
9e60a08
eb5f3c7
c6b873e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import {AvailableActions} from "~~/controller/ActionController"; | ||
| import {ActionInvoker, ActionListener} from "~~/sdk/events/types"; | ||
| import {ActionData} from "~~/drizzle"; | ||
| import {ctx, Context} from "~~/sdk/events/context"; | ||
|
|
||
| export class SDKEvents { | ||
| constructor() { | ||
| } | ||
|
|
||
| onAction = ( | ||
| action: AvailableActions, | ||
| listener: ActionListener, | ||
| ) => { | ||
| const invoke = async (context: Context, ...data: ArgumentTypes<ActionListener>) => { | ||
| return await ctx.callAsync(context, async () => { | ||
| return listener(...data) | ||
| }) | ||
| } | ||
| useFabric<Record<AvailableActions, ActionInvoker>>("actions").on(action, invoke) | ||
| } | ||
m41denx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| emitAction = ( | ||
| action: AvailableActions, | ||
| uid: number, | ||
| targetId: number, | ||
| data: ActionData, | ||
| context: Context, | ||
| ) => { | ||
| useFabric<Record<AvailableActions, ActionInvoker>>("actions").emit(action, context, uid, targetId, data) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import {createContext} from "unctx"; | ||
| import {AsyncLocalStorage} from "node:async_hooks"; | ||
| import {H3EventContext} from "h3"; | ||
|
|
||
|
|
||
| export const ctx = createContext<Context>({ | ||
| asyncContext: true, | ||
| AsyncLocalStorage | ||
| }) | ||
|
|
||
| export const useEventContext = ctx.use | ||
|
|
||
| export type Context = { | ||
| drizzle: Database, | ||
| config: ServerConfig | ||
| } | ||
m41denx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import {ActionData} from "~~/drizzle"; | ||
| import {Context} from "~~/sdk/events/context"; | ||
|
|
||
| export type ActionListener = ( | ||
| uid: number, | ||
| targetId: number, | ||
| data: ActionData | ||
| ) => void | Promise<void> | ||
|
|
||
| export type ActionInvoker = (context: Context, ...data: ArgumentTypes<ActionListener>) => Promise<void> | ||
m41denx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,188 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { LevelController } from "~~/controller/LevelController"; | ||||||||||||||||||||||||||||||||||||||||||
| import type { LevelWithUser } from "~~/controller/Level"; | ||||||||||||||||||||||||||||||||||||||||||
| import type { MaybeUndefined } from "~/utils/types"; | ||||||||||||||||||||||||||||||||||||||||||
| import { ActionData } from "~~/drizzle"; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| type DiscordRateBotModuleConfig = { | ||||||||||||||||||||||||||||||||||||||||||
| webhookUrl: string, | ||||||||||||||||||||||||||||||||||||||||||
| mentionRoleId?: string, | ||||||||||||||||||||||||||||||||||||||||||
| username?: string, | ||||||||||||||||||||||||||||||||||||||||||
| avatarUrl?: string | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| type DifficultyInfo = { | ||||||||||||||||||||||||||||||||||||||||||
| label: string, | ||||||||||||||||||||||||||||||||||||||||||
| color: number | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const difficultyPalette: Record<string, number> = { | ||||||||||||||||||||||||||||||||||||||||||
| unrated: 0x72757a, | ||||||||||||||||||||||||||||||||||||||||||
| auto: 0x00d1ff, | ||||||||||||||||||||||||||||||||||||||||||
| easy: 0x3ee45f, | ||||||||||||||||||||||||||||||||||||||||||
| normal: 0xf7d774, | ||||||||||||||||||||||||||||||||||||||||||
| hard: 0xffa24c, | ||||||||||||||||||||||||||||||||||||||||||
| harder: 0xff6b4c, | ||||||||||||||||||||||||||||||||||||||||||
| insane: 0xc04bff, | ||||||||||||||||||||||||||||||||||||||||||
| demon: 0x8c2eff | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export default defineNitroPlugin(() => { | ||||||||||||||||||||||||||||||||||||||||||
| useSDK().events.onAction("level_rate", async (uid: number, targetId: number, data: ActionData) => { | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| await dispatchDiscordRateWebhook(targetId, uid, data) | ||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||
| useLogger().warn(`[DiscordRateBot] ${(error as Error).message}`) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const dispatchDiscordRateWebhook = async (targetId: number, uid: number, data: ActionData) => { | ||||||||||||||||||||||||||||||||||||||||||
| const actionType = data.type || "" | ||||||||||||||||||||||||||||||||||||||||||
| if (!actionType.startsWith("Rate:")) | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const actionSuffix = actionType.slice(5).toLowerCase() | ||||||||||||||||||||||||||||||||||||||||||
| if (!actionSuffix || actionSuffix === "reset") | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
m41denx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const { config: serverConfig, drizzle } = useEventContext() | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!serverConfig.ServerConfig.EnableModules?.["discord_ratebot"]) | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const moduleConfig = serverConfig.ServerConfig.ModuleConfig?.["discord_ratebot"] as MaybeUndefined<DiscordRateBotModuleConfig> | ||||||||||||||||||||||||||||||||||||||||||
| if (!moduleConfig?.webhookUrl) | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!moduleConfig.webhookUrl.startsWith("http")) | ||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Webhook URL must be absolute") | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const levelController = new LevelController(drizzle) | ||||||||||||||||||||||||||||||||||||||||||
| const level = await levelController.getOneLevel(targetId) | ||||||||||||||||||||||||||||||||||||||||||
| if (!level) | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const webhookBody = createWebhookBody( | ||||||||||||||||||||||||||||||||||||||||||
| moduleConfig, | ||||||||||||||||||||||||||||||||||||||||||
| level.$, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| serverId: serverConfig.ServerConfig.SrvID, | ||||||||||||||||||||||||||||||||||||||||||
| moderator: data.uname || `User #${uid}`, | ||||||||||||||||||||||||||||||||||||||||||
| actionDescriptor: actionType | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| await $fetch(moduleConfig.webhookUrl, { | ||||||||||||||||||||||||||||||||||||||||||
| method: "POST" as any, | ||||||||||||||||||||||||||||||||||||||||||
| body: webhookBody | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||
| useLogger().error(`[DiscordRateBot] Failed to send webhook: ${(error as Error).message}`) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const createWebhookBody = ( | ||||||||||||||||||||||||||||||||||||||||||
| cfg: DiscordRateBotModuleConfig, | ||||||||||||||||||||||||||||||||||||||||||
| level: LevelWithUser, | ||||||||||||||||||||||||||||||||||||||||||
| meta: { | ||||||||||||||||||||||||||||||||||||||||||
| serverId?: string, | ||||||||||||||||||||||||||||||||||||||||||
| moderator: string, | ||||||||||||||||||||||||||||||||||||||||||
| actionDescriptor: string | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||||||
| const rating = Math.max(level.starsGot ?? 0, 0) | ||||||||||||||||||||||||||||||||||||||||||
| const difficulty = resolveDifficulty(rating, level.demonDifficulty ?? -1) | ||||||||||||||||||||||||||||||||||||||||||
| const featureState = level.isFeatured ? "Featured" : "Not featured" | ||||||||||||||||||||||||||||||||||||||||||
| const epicTier = resolveEpic(level.epicness ?? 0) | ||||||||||||||||||||||||||||||||||||||||||
| const creator = level.author?.username || `User #${level.ownerUid}` | ||||||||||||||||||||||||||||||||||||||||||
| const embed = { | ||||||||||||||||||||||||||||||||||||||||||
| title: `${level.name} • ${difficulty.label}`, | ||||||||||||||||||||||||||||||||||||||||||
| description: `**${meta.moderator}** rated this level ${rating ? `${rating}★` : "without stars"}.`, | ||||||||||||||||||||||||||||||||||||||||||
| color: difficulty.color, | ||||||||||||||||||||||||||||||||||||||||||
| fields: [ | ||||||||||||||||||||||||||||||||||||||||||
| { name: "Level ID", value: level.id.toString(), inline: true }, | ||||||||||||||||||||||||||||||||||||||||||
| { name: "Creator", value: creator, inline: true }, | ||||||||||||||||||||||||||||||||||||||||||
| { name: "Difficulty", value: rating ? `${rating}★ • ${difficulty.label}` : difficulty.label, inline: true }, | ||||||||||||||||||||||||||||||||||||||||||
| { name: "Feature", value: featureState, inline: true }, | ||||||||||||||||||||||||||||||||||||||||||
| { name: "Epic Tier", value: epicTier, inline: true }, | ||||||||||||||||||||||||||||||||||||||||||
| { name: "Coins", value: formatCoins(level.coins ?? 0, level.userCoins ?? 0), inline: true }, | ||||||||||||||||||||||||||||||||||||||||||
| { name: "Server", value: meta.serverId || "Unknown", inline: true }, | ||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||
| footer: { | ||||||||||||||||||||||||||||||||||||||||||
| text: `Rated via ${meta.actionDescriptor}` | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| timestamp: new Date().toISOString() | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!level.isFeatured && epicTier === "None") { | ||||||||||||||||||||||||||||||||||||||||||
| embed.fields = embed.fields.filter(field => field.name !== "Epic Tier") | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const body: Record<string, unknown> = { | ||||||||||||||||||||||||||||||||||||||||||
| embeds: [embed] | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (cfg.mentionRoleId) | ||||||||||||||||||||||||||||||||||||||||||
| body.content = `<@&${cfg.mentionRoleId}>` | ||||||||||||||||||||||||||||||||||||||||||
| if (cfg.username) | ||||||||||||||||||||||||||||||||||||||||||
| body.username = cfg.username | ||||||||||||||||||||||||||||||||||||||||||
| if (cfg.avatarUrl) | ||||||||||||||||||||||||||||||||||||||||||
| body.avatar_url = cfg.avatarUrl | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return body | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const resolveDifficulty = (stars: number, demonDifficulty: number): DifficultyInfo => { | ||||||||||||||||||||||||||||||||||||||||||
| if (!stars) | ||||||||||||||||||||||||||||||||||||||||||
| return { label: "Unrated", color: difficultyPalette.unrated } | ||||||||||||||||||||||||||||||||||||||||||
| if (stars === 1) | ||||||||||||||||||||||||||||||||||||||||||
| return { label: "Auto", color: difficultyPalette.auto } | ||||||||||||||||||||||||||||||||||||||||||
| if (stars === 2) | ||||||||||||||||||||||||||||||||||||||||||
| return { label: "Easy", color: difficultyPalette.easy } | ||||||||||||||||||||||||||||||||||||||||||
| if (stars === 3) | ||||||||||||||||||||||||||||||||||||||||||
| return { label: "Normal", color: difficultyPalette.normal } | ||||||||||||||||||||||||||||||||||||||||||
| if (stars === 4 || stars === 5) | ||||||||||||||||||||||||||||||||||||||||||
| return { label: "Hard", color: difficultyPalette.hard } | ||||||||||||||||||||||||||||||||||||||||||
| if (stars === 6 || stars === 7) | ||||||||||||||||||||||||||||||||||||||||||
| return { label: "Harder", color: difficultyPalette.harder } | ||||||||||||||||||||||||||||||||||||||||||
| if (stars === 8 || stars === 9) | ||||||||||||||||||||||||||||||||||||||||||
| return { label: "Insane", color: difficultyPalette.insane } | ||||||||||||||||||||||||||||||||||||||||||
| if (stars >= 10) { | ||||||||||||||||||||||||||||||||||||||||||
| const demonLabel = resolveDemon(demonDifficulty) | ||||||||||||||||||||||||||||||||||||||||||
| return { label: demonLabel, color: difficultyPalette.demon } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| return { label: `${stars}★`, color: difficultyPalette.normal } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const resolveDemon = (value: number) => { | ||||||||||||||||||||||||||||||||||||||||||
| const map: Record<number, string> = { | ||||||||||||||||||||||||||||||||||||||||||
| 3: "Easy Demon", | ||||||||||||||||||||||||||||||||||||||||||
| 4: "Medium Demon", | ||||||||||||||||||||||||||||||||||||||||||
| 0: "Hard Demon", | ||||||||||||||||||||||||||||||||||||||||||
| 5: "Insane Demon", | ||||||||||||||||||||||||||||||||||||||||||
| 6: "Extreme Demon", | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| return map[value] || "Insane Demon" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+158
to
+167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Несогласованное значение по умолчанию для демонов. Согласно const resolveDemon = (value: number) => {
const map: Record<number, string> = {
}
- return map[value] || "Insane Demon"
+ return map[value] || "Hard Demon"
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const resolveEpic = (value: number) => { | ||||||||||||||||||||||||||||||||||||||||||
| switch (value) { | ||||||||||||||||||||||||||||||||||||||||||
| case 1: | ||||||||||||||||||||||||||||||||||||||||||
| return "Epic" | ||||||||||||||||||||||||||||||||||||||||||
| case 2: | ||||||||||||||||||||||||||||||||||||||||||
| return "Legendary" | ||||||||||||||||||||||||||||||||||||||||||
| case 3: | ||||||||||||||||||||||||||||||||||||||||||
| return "Mythic" | ||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||
| return "None" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const formatCoins = (verified: number, userCoins: number) => { | ||||||||||||||||||||||||||||||||||||||||||
| if (!userCoins) | ||||||||||||||||||||||||||||||||||||||||||
| return "No user coins" | ||||||||||||||||||||||||||||||||||||||||||
| if (verified >= userCoins) | ||||||||||||||||||||||||||||||||||||||||||
| return `${verified} verified coins` | ||||||||||||||||||||||||||||||||||||||||||
| return `${verified}/${userCoins} verified` | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.