kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import * as v from "valibot";
2
3export const telegramEventKeys = [
4 "taskCreated",
5 "taskStatusChanged",
6 "taskPriorityChanged",
7 "taskTitleChanged",
8 "taskDescriptionChanged",
9 "taskCommentCreated",
10] as const;
11
12export type TelegramEventKey = (typeof telegramEventKeys)[number];
13
14export const telegramEventsSchema = v.object(
15 Object.fromEntries(
16 telegramEventKeys.map((key) => [key, v.optional(v.boolean())]),
17 ) as Record<
18 TelegramEventKey,
19 v.OptionalSchema<v.BooleanSchema<undefined>, never>
20 >,
21);
22
23const telegramBotTokenSchema = v.pipe(
24 v.string(),
25 v.regex(/^\d{8,10}:[A-Za-z0-9_-]{35}$/, "Enter a valid Telegram bot token"),
26);
27
28const telegramChatIdSchema = v.pipe(
29 v.string(),
30 v.trim(),
31 v.minLength(1, "Chat ID is required"),
32);
33
34export const telegramConfigSchema = v.object({
35 botToken: telegramBotTokenSchema,
36 chatId: telegramChatIdSchema,
37 threadId: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
38 chatLabel: v.optional(v.string()),
39 events: v.optional(telegramEventsSchema),
40});
41
42export type TelegramConfig = v.InferOutput<typeof telegramConfigSchema>;
43
44export const defaultTelegramEvents: Record<TelegramEventKey, boolean> = {
45 taskCreated: true,
46 taskStatusChanged: true,
47 taskPriorityChanged: false,
48 taskTitleChanged: false,
49 taskDescriptionChanged: false,
50 taskCommentCreated: true,
51};
52
53export function normalizeTelegramConfig(
54 config: TelegramConfig,
55): TelegramConfig {
56 return {
57 ...config,
58 threadId:
59 typeof config.threadId === "number" && Number.isFinite(config.threadId)
60 ? config.threadId
61 : undefined,
62 chatLabel: config.chatLabel?.trim() || undefined,
63 events: {
64 ...defaultTelegramEvents,
65 ...(config.events ?? {}),
66 },
67 };
68}
69
70export function validateTelegramConfig(config: unknown): {
71 valid: boolean;
72 errors?: string[];
73} {
74 try {
75 const parsed = v.parse(telegramConfigSchema, config);
76 normalizeTelegramConfig(parsed);
77 return { valid: true };
78 } catch (error) {
79 if (error instanceof v.ValiError) {
80 return {
81 valid: false,
82 errors: error.issues.map((issue) => issue.message),
83 };
84 }
85
86 return {
87 valid: false,
88 errors: [error instanceof Error ? error.message : "Invalid config"],
89 };
90 }
91}