thepopebot 1.2.81 → 1.2.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1018,12 +1018,19 @@ export async function regenerateWebhookSecret(key) {
1018
1018
  export async function getTelegramStatus() {
1019
1019
  await requireAuth();
1020
1020
  try {
1021
- const { getConfigSecret } = await import('../db/config.js');
1021
+ const { getConfigSecret, getConfigValue } = await import('../db/config.js');
1022
+ const { getConfig } = await import('../config.js');
1022
1023
  const { validateBotToken, getTelegramWebhookInfo } = await import('../tools/telegram.js');
1023
1024
 
1024
1025
  const botToken = getConfigSecret('TELEGRAM_BOT_TOKEN');
1025
1026
  const webhookSecret = getConfigSecret('TELEGRAM_WEBHOOK_SECRET');
1026
1027
 
1028
+ const appUrl = getConfig('APP_URL');
1029
+ const defaultWebhookUrl = appUrl
1030
+ ? `${appUrl.replace(/\/$/, '')}/api/telegram/webhook`
1031
+ : '';
1032
+ const webhookUrlOverride = getConfigValue('TELEGRAM_WEBHOOK_URL') || null;
1033
+
1027
1034
  let botInfo = null;
1028
1035
  let webhookInfo = null;
1029
1036
  if (botToken) {
@@ -1049,6 +1056,8 @@ export async function getTelegramStatus() {
1049
1056
  botTokenSet: !!botToken,
1050
1057
  webhookSecretSet: !!webhookSecret,
1051
1058
  webhookInfo,
1059
+ defaultWebhookUrl,
1060
+ webhookUrlOverride,
1052
1061
  };
1053
1062
  } catch (err) {
1054
1063
  console.error('Failed to get Telegram status:', err);
@@ -1078,12 +1087,20 @@ export async function validateTelegramToken(token) {
1078
1087
  /**
1079
1088
  * Register the Telegram webhook with the currently saved bot token.
1080
1089
  * Generates a fresh webhook secret, saves it, and calls Telegram's setWebhook.
1081
- * APP_URL must be configured.
1082
- */
1083
- export async function registerTelegramWebhook() {
1090
+ *
1091
+ * URL resolution:
1092
+ * - explicit `webhookUrl` arg (persisted as override if it differs from default)
1093
+ * - stored `TELEGRAM_WEBHOOK_URL` override
1094
+ * - `${APP_URL}/api/telegram/webhook` default
1095
+ *
1096
+ * Pass an empty string for `webhookUrl` to clear the override and fall back
1097
+ * to the APP_URL-derived default.
1098
+ */
1099
+ export async function registerTelegramWebhook(webhookUrl) {
1084
1100
  const user = await requireAdmin();
1085
1101
  try {
1086
- const { getConfigSecret, setConfigSecret } = await import('../db/config.js');
1102
+ const { getConfigSecret, setConfigSecret, getConfigValue, setConfigValue, deleteConfigValue } =
1103
+ await import('../db/config.js');
1087
1104
  const { getConfig } = await import('../config.js');
1088
1105
  const { setTelegramWebhook, generateWebhookSecret } = await import('../tools/telegram.js');
1089
1106
 
@@ -1091,17 +1108,43 @@ export async function registerTelegramWebhook() {
1091
1108
  if (!botToken) return { error: 'Bot token must be set first' };
1092
1109
 
1093
1110
  const appUrl = getConfig('APP_URL');
1094
- if (!appUrl) return { error: 'APP_URL must be configured first' };
1111
+ const defaultUrl = appUrl ? `${appUrl.replace(/\/$/, '')}/api/telegram/webhook` : '';
1112
+
1113
+ // Resolve the URL to register.
1114
+ let urlToRegister;
1115
+ if (typeof webhookUrl === 'string') {
1116
+ const trimmed = webhookUrl.trim();
1117
+ if (!trimmed || trimmed === defaultUrl) {
1118
+ deleteConfigValue('TELEGRAM_WEBHOOK_URL');
1119
+ urlToRegister = trimmed || defaultUrl;
1120
+ } else {
1121
+ setConfigValue('TELEGRAM_WEBHOOK_URL', trimmed, user.id);
1122
+ urlToRegister = trimmed;
1123
+ }
1124
+ } else {
1125
+ urlToRegister = getConfigValue('TELEGRAM_WEBHOOK_URL') || defaultUrl;
1126
+ }
1127
+
1128
+ if (!urlToRegister) {
1129
+ return { error: 'Webhook URL is required (set APP_URL or supply a URL)' };
1130
+ }
1131
+ try {
1132
+ const parsed = new URL(urlToRegister);
1133
+ if (parsed.protocol !== 'https:') {
1134
+ return { error: 'Webhook URL must use https://' };
1135
+ }
1136
+ } catch {
1137
+ return { error: 'Webhook URL is not a valid URL' };
1138
+ }
1095
1139
 
1096
- const webhookUrl = `${appUrl.replace(/\/$/, '')}/api/telegram/webhook`;
1097
1140
  const secret = generateWebhookSecret();
1098
1141
  setConfigSecret('TELEGRAM_WEBHOOK_SECRET', secret, user.id);
1099
1142
 
1100
- const result = await setTelegramWebhook(botToken, webhookUrl, secret);
1143
+ const result = await setTelegramWebhook(botToken, urlToRegister, secret);
1101
1144
  if (!result.ok) {
1102
1145
  return { error: result.description || 'Failed to register webhook' };
1103
1146
  }
1104
- return { success: true, webhookUrl };
1147
+ return { success: true, webhookUrl: urlToRegister };
1105
1148
  } catch (err) {
1106
1149
  console.error('Failed to register Telegram webhook:', err);
1107
1150
  return { error: err.message };
@@ -273,6 +273,8 @@ function ApiKeysTelegramPage() {
273
273
  const [tokenError, setTokenError] = useState(null);
274
274
  const [webhookSaving, setWebhookSaving] = useState(false);
275
275
  const [webhookError, setWebhookError] = useState(null);
276
+ const [webhookEditing, setWebhookEditing] = useState(false);
277
+ const [webhookUrlInput, setWebhookUrlInput] = useState("");
276
278
  const loadStatus = async () => {
277
279
  try {
278
280
  const result = await getTelegramStatus();
@@ -284,6 +286,12 @@ function ApiKeysTelegramPage() {
284
286
  useEffect(() => {
285
287
  loadStatus();
286
288
  }, []);
289
+ useEffect(() => {
290
+ if (!status || webhookEditing) return;
291
+ setWebhookUrlInput(
292
+ status.webhookUrlOverride || status.webhookInfo?.url || status.defaultWebhookUrl || ""
293
+ );
294
+ }, [status, webhookEditing]);
287
295
  if (loading) {
288
296
  return /* @__PURE__ */ jsx("div", { className: "h-48 animate-pulse rounded-md bg-border/50" });
289
297
  }
@@ -318,11 +326,33 @@ function ApiKeysTelegramPage() {
318
326
  const handleRegisterWebhook = async () => {
319
327
  setWebhookSaving(true);
320
328
  setWebhookError(null);
321
- const result = await registerTelegramWebhook();
322
- if (result?.error) setWebhookError(result.error);
329
+ const url = webhookEditing ? webhookUrlInput.trim() : void 0;
330
+ const result = await registerTelegramWebhook(url);
331
+ if (result?.error) {
332
+ setWebhookError(result.error);
333
+ setWebhookSaving(false);
334
+ return;
335
+ }
336
+ setWebhookEditing(false);
337
+ setWebhookUrlInput("");
323
338
  await loadStatus();
324
339
  setWebhookSaving(false);
325
340
  };
341
+ const startWebhookEdit = () => {
342
+ setWebhookError(null);
343
+ setWebhookUrlInput(
344
+ status.webhookUrlOverride || status.webhookInfo?.url || status.defaultWebhookUrl || ""
345
+ );
346
+ setWebhookEditing(true);
347
+ };
348
+ const cancelWebhookEdit = () => {
349
+ setWebhookEditing(false);
350
+ setWebhookUrlInput("");
351
+ setWebhookError(null);
352
+ };
353
+ const resetWebhookToDefault = () => {
354
+ setWebhookUrlInput(status.defaultWebhookUrl || "");
355
+ };
326
356
  return /* @__PURE__ */ jsxs("div", { children: [
327
357
  /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
328
358
  /* @__PURE__ */ jsx("h2", { className: "text-base font-medium", children: "Telegram" }),
@@ -417,33 +447,75 @@ function ApiKeysTelegramPage() {
417
447
  /* @__PURE__ */ jsx("div", { className: `rounded-lg border bg-card p-4 ${!step1Done ? "opacity-50 pointer-events-none" : ""}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
418
448
  /* @__PURE__ */ jsx(StepIndicator, { n: 2, state: step2Done ? "done" : step1Done ? "active" : "pending" }),
419
449
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
420
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between gap-2", children: /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
421
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Webhook" }),
422
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Register your public URL with Telegram so it can deliver messages to your bot." }),
423
- step2Done && /* @__PURE__ */ jsxs("div", { className: "mt-2 text-xs text-muted-foreground truncate", children: [
424
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: status.webhookInfo.url }),
425
- status.webhookInfo.pendingUpdates > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-yellow-500", children: [
426
- "(",
427
- status.webhookInfo.pendingUpdates,
428
- " pending)"
429
- ] }),
430
- status.webhookInfo.lastErrorMessage && /* @__PURE__ */ jsxs("div", { className: "mt-1 text-destructive", children: [
431
- "Last error: ",
432
- status.webhookInfo.lastErrorMessage
450
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
451
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
452
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Webhook" }),
453
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Register a public URL with Telegram so it can deliver messages to your bot." }),
454
+ step2Done && !webhookEditing && /* @__PURE__ */ jsxs("div", { className: "mt-2 text-xs text-muted-foreground truncate", children: [
455
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: status.webhookInfo.url }),
456
+ status.webhookInfo.pendingUpdates > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-yellow-500", children: [
457
+ "(",
458
+ status.webhookInfo.pendingUpdates,
459
+ " pending)"
460
+ ] }),
461
+ status.webhookInfo.lastErrorMessage && /* @__PURE__ */ jsxs("div", { className: "mt-1 text-destructive", children: [
462
+ "Last error: ",
463
+ status.webhookInfo.lastErrorMessage
464
+ ] })
433
465
  ] })
434
- ] })
435
- ] }) }),
436
- /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
437
- webhookError && /* @__PURE__ */ jsx("div", { className: "text-xs text-destructive mb-2", children: webhookError }),
438
- /* @__PURE__ */ jsx(
466
+ ] }),
467
+ step2Done && !webhookEditing && /* @__PURE__ */ jsx(
439
468
  "button",
440
469
  {
441
- onClick: handleRegisterWebhook,
442
- disabled: !step1Done || webhookSaving,
443
- className: "rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors",
444
- children: webhookSaving ? "Registering..." : step2Done ? "Re-register Webhook" : "Register Webhook"
470
+ onClick: startWebhookEdit,
471
+ className: "shrink-0 text-xs text-muted-foreground hover:text-foreground underline transition-colors",
472
+ children: "Change"
445
473
  }
446
474
  )
475
+ ] }),
476
+ (!step2Done || webhookEditing) && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-col gap-2", children: [
477
+ /* @__PURE__ */ jsx(
478
+ "input",
479
+ {
480
+ type: "text",
481
+ value: webhookUrlInput,
482
+ onChange: (e) => {
483
+ if (!webhookEditing) setWebhookEditing(true);
484
+ setWebhookUrlInput(e.target.value);
485
+ },
486
+ placeholder: "https://example.com/api/telegram/webhook",
487
+ spellCheck: false,
488
+ className: "rounded-md border border-border bg-background px-3 py-1.5 text-sm font-mono focus:outline-none focus:ring-1 focus:ring-foreground"
489
+ }
490
+ ),
491
+ webhookError && /* @__PURE__ */ jsx("div", { className: "text-xs text-destructive", children: webhookError }),
492
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
493
+ /* @__PURE__ */ jsx(
494
+ "button",
495
+ {
496
+ onClick: handleRegisterWebhook,
497
+ disabled: !step1Done || webhookSaving || !webhookUrlInput.trim(),
498
+ className: "rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors",
499
+ children: webhookSaving ? "Registering..." : step2Done ? "Re-register Webhook" : "Register Webhook"
500
+ }
501
+ ),
502
+ webhookEditing && step2Done && /* @__PURE__ */ jsx(
503
+ "button",
504
+ {
505
+ onClick: cancelWebhookEdit,
506
+ className: "rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
507
+ children: "Cancel"
508
+ }
509
+ ),
510
+ status.defaultWebhookUrl && webhookUrlInput.trim() !== status.defaultWebhookUrl && /* @__PURE__ */ jsx(
511
+ "button",
512
+ {
513
+ onClick: resetWebhookToDefault,
514
+ className: "ml-auto text-xs text-muted-foreground hover:text-foreground underline transition-colors",
515
+ children: "Reset to default"
516
+ }
517
+ )
518
+ ] })
447
519
  ] })
448
520
  ] })
449
521
  ] }) })
@@ -329,6 +329,8 @@ export function ApiKeysTelegramPage() {
329
329
  // Step 2 — webhook
330
330
  const [webhookSaving, setWebhookSaving] = useState(false);
331
331
  const [webhookError, setWebhookError] = useState(null);
332
+ const [webhookEditing, setWebhookEditing] = useState(false);
333
+ const [webhookUrlInput, setWebhookUrlInput] = useState('');
332
334
 
333
335
  const loadStatus = async () => {
334
336
  try {
@@ -343,6 +345,16 @@ export function ApiKeysTelegramPage() {
343
345
  loadStatus();
344
346
  }, []);
345
347
 
348
+ // Keep the webhook URL input in sync with the saved/effective URL when not
349
+ // actively editing — covers the "unregistered" case where the field needs
350
+ // to be prefilled with the default so the user can just hit Register.
351
+ useEffect(() => {
352
+ if (!status || webhookEditing) return;
353
+ setWebhookUrlInput(
354
+ status.webhookUrlOverride || status.webhookInfo?.url || status.defaultWebhookUrl || ''
355
+ );
356
+ }, [status, webhookEditing]);
357
+
346
358
  if (loading) {
347
359
  return <div className="h-48 animate-pulse rounded-md bg-border/50" />;
348
360
  }
@@ -383,12 +395,37 @@ export function ApiKeysTelegramPage() {
383
395
  const handleRegisterWebhook = async () => {
384
396
  setWebhookSaving(true);
385
397
  setWebhookError(null);
386
- const result = await registerTelegramWebhook();
387
- if (result?.error) setWebhookError(result.error);
398
+ const url = webhookEditing ? webhookUrlInput.trim() : undefined;
399
+ const result = await registerTelegramWebhook(url);
400
+ if (result?.error) {
401
+ setWebhookError(result.error);
402
+ setWebhookSaving(false);
403
+ return;
404
+ }
405
+ setWebhookEditing(false);
406
+ setWebhookUrlInput('');
388
407
  await loadStatus();
389
408
  setWebhookSaving(false);
390
409
  };
391
410
 
411
+ const startWebhookEdit = () => {
412
+ setWebhookError(null);
413
+ setWebhookUrlInput(
414
+ status.webhookUrlOverride || status.webhookInfo?.url || status.defaultWebhookUrl || ''
415
+ );
416
+ setWebhookEditing(true);
417
+ };
418
+
419
+ const cancelWebhookEdit = () => {
420
+ setWebhookEditing(false);
421
+ setWebhookUrlInput('');
422
+ setWebhookError(null);
423
+ };
424
+
425
+ const resetWebhookToDefault = () => {
426
+ setWebhookUrlInput(status.defaultWebhookUrl || '');
427
+ };
428
+
392
429
  return (
393
430
  <div>
394
431
  <div className="mb-4">
@@ -488,9 +525,9 @@ export function ApiKeysTelegramPage() {
488
525
  <div className="min-w-0">
489
526
  <h3 className="text-sm font-medium">Webhook</h3>
490
527
  <p className="text-xs text-muted-foreground mt-0.5">
491
- Register your public URL with Telegram so it can deliver messages to your bot.
528
+ Register a public URL with Telegram so it can deliver messages to your bot.
492
529
  </p>
493
- {step2Done && (
530
+ {step2Done && !webhookEditing && (
494
531
  <div className="mt-2 text-xs text-muted-foreground truncate">
495
532
  <span className="font-mono">{status.webhookInfo.url}</span>
496
533
  {status.webhookInfo.pendingUpdates > 0 && (
@@ -506,24 +543,62 @@ export function ApiKeysTelegramPage() {
506
543
  </div>
507
544
  )}
508
545
  </div>
509
- </div>
510
-
511
- <div className="mt-3">
512
- {webhookError && (
513
- <div className="text-xs text-destructive mb-2">{webhookError}</div>
546
+ {step2Done && !webhookEditing && (
547
+ <button
548
+ onClick={startWebhookEdit}
549
+ className="shrink-0 text-xs text-muted-foreground hover:text-foreground underline transition-colors"
550
+ >
551
+ Change
552
+ </button>
514
553
  )}
515
- <button
516
- onClick={handleRegisterWebhook}
517
- disabled={!step1Done || webhookSaving}
518
- className="rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors"
519
- >
520
- {webhookSaving
521
- ? 'Registering...'
522
- : step2Done
523
- ? 'Re-register Webhook'
524
- : 'Register Webhook'}
525
- </button>
526
554
  </div>
555
+
556
+ {(!step2Done || webhookEditing) && (
557
+ <div className="mt-3 flex flex-col gap-2">
558
+ <input
559
+ type="text"
560
+ value={webhookUrlInput}
561
+ onChange={(e) => {
562
+ if (!webhookEditing) setWebhookEditing(true);
563
+ setWebhookUrlInput(e.target.value);
564
+ }}
565
+ placeholder="https://example.com/api/telegram/webhook"
566
+ spellCheck={false}
567
+ className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-mono focus:outline-none focus:ring-1 focus:ring-foreground"
568
+ />
569
+ {webhookError && <div className="text-xs text-destructive">{webhookError}</div>}
570
+ <div className="flex items-center gap-2">
571
+ <button
572
+ onClick={handleRegisterWebhook}
573
+ disabled={!step1Done || webhookSaving || !webhookUrlInput.trim()}
574
+ className="rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors"
575
+ >
576
+ {webhookSaving
577
+ ? 'Registering...'
578
+ : step2Done
579
+ ? 'Re-register Webhook'
580
+ : 'Register Webhook'}
581
+ </button>
582
+ {webhookEditing && step2Done && (
583
+ <button
584
+ onClick={cancelWebhookEdit}
585
+ className="rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
586
+ >
587
+ Cancel
588
+ </button>
589
+ )}
590
+ {status.defaultWebhookUrl &&
591
+ webhookUrlInput.trim() !== status.defaultWebhookUrl && (
592
+ <button
593
+ onClick={resetWebhookToDefault}
594
+ className="ml-auto text-xs text-muted-foreground hover:text-foreground underline transition-colors"
595
+ >
596
+ Reset to default
597
+ </button>
598
+ )}
599
+ </div>
600
+ </div>
601
+ )}
527
602
  </div>
528
603
  </div>
529
604
  </div>
package/lib/config.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Config resolver. Reads from DB, falls back to defaults.
3
- * In-memory cache, invalidated on config writes.
4
3
  *
5
4
  * Usage:
6
5
  * import { getConfig } from '../config.js';
@@ -63,6 +62,7 @@ const CONFIG_KEYS = new Set([
63
62
  'CODE_MODE_GIT_ACTION',
64
63
  'AGENT_MODE_AUTO_RUN',
65
64
  'CODE_MODE_AUTO_RUN',
65
+ 'TELEGRAM_WEBHOOK_URL',
66
66
  ]);
67
67
 
68
68
  // Default values
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.81",
3
+ "version": "1.2.82",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {