Skip to content

feat: add /scheduleorder and /cancelschedule for recurring auto-republishing orders#849

Draft
Matobi98 wants to merge 4 commits into
lnp2pBot:mainfrom
Matobi98:feat/scheduleorder
Draft

feat: add /scheduleorder and /cancelschedule for recurring auto-republishing orders#849
Matobi98 wants to merge 4 commits into
lnp2pBot:mainfrom
Matobi98:feat/scheduleorder

Conversation

@Matobi98

@Matobi98 Matobi98 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

feat: add /scheduleorder and /cancelschedule for recurring auto-republishing orders

Closes #769.

Adds /scheduleorder — creates an order that automatically republishes on a recurring schedule (e.g. every day, weekdays, or specific days at a chosen hour) — and /cancelschedule <schedule_id> to stop it.

Design

Following the maintainer feedback on the original PR (mutating an order's republish_count / status was too invasive and let an order migrate between states), this implementation introduces a dedicated ScheduledOrder model that acts as a mold:

  • The order's lifecycle is never mutated. Each publication is a brand-new Order with its own id.
  • The ScheduledOrder stores the trade config + recurrence (days, hour in UTC) + a republish_count cap + last_order_id (mold → order link).
  • models/order.ts is left completely untouched — no new fields on Order.

How it works

  • /scheduleorder <buy|sell> <sats> <fiat_amount> <fiat_code> <payment_method> [premium] parses the order with the existing buy/sell validators, then opens an interactive wizard (/exit to leave at any time):
    1. Days — inline buttons: 📅 Every day · 💼 Mon–Fri · 🌴 Weekend · ⚙️ Custom (custom accepts a comma-separated list of day names, accent/case tolerant).
    2. Hour — a number 0–23 (UTC).
    3. Confirmation — summary + ✅ / ❌.
  • A cron job (jobs/scheduled_orders.ts) runs hourly and, for each active schedule whose day/hour (UTC) match, publishes a fresh order from the mold and decrements republish_count. When the counter hits 0 the schedule deactivates itself (self-cleaning, as the issue requested). The cap defaults to REPUBLISH_ORDER_DAYS (10).
  • Reset on take — when a scheduled order is taken, the schedule's counter is reset to the top and a new order is immediately published from the mold, so the trader's offer stays live without manual recreation.
  • /cancelschedule <schedule_id> deactivates a schedule by its own id.

Implementation built as a WizardScene

The multi-step flow is a Scenes.WizardScene registered in stageMiddleware, exactly like the /buy and /sell order wizard. This means:

  • /exit works at any step (inherited from the generic scene commands).
  • State lives in the per-private-chat scene session with the standard 20-minute TTL — no global bot.on('text') / bot.on('callback_query') listeners, no cross-chat interference, no duplicated middleware.

⚠️ Note on order accumulation

Even though republish_count caps the total number of republications as the issue asked, scheduled orders do not pile up on the channel. Each published order is a normal order and expires after ORDER_PUBLISHED_EXPIRATION_WINDOW (23h by default), removed by the existing delete_published_orders job. Since 23h is shorter than any schedule interval (daily/weekly), at most one scheduled order is live at a time — a weekly order expires ~23h after publishing, days before the next one.

New config

REPUBLISH_ORDER_DAYS=10 # max republication cycles before a schedule self-deactivates

Testing

  • Test suite green against the existing baseline (updated the recurring-jobs count assertion for the new cron job).
  • /help updated with both commands; i18n strings added.

Summary by CodeRabbit

  • New Features
    • Added scheduled recurring orders with new commands to create and cancel them.
    • Users can choose preset days or enter custom days, then confirm a UTC hour before creating the schedule.
    • Scheduled orders are now automatically republished on the configured cadence and can be stopped anytime.
  • Bug Fixes
    • Improved handling of recurring orders so successful purchases can refresh the associated schedule.
  • Documentation
    • Expanded help text and localized messaging across multiple languages for the new scheduling flow.

Matobi98 added 4 commits June 29, 2026 17:50
…rders

Creates a new ScheduledOrder model that acts as a mold: each cron trigger
publishes a brand-new Order with its own id. No order lifecycle is mutated.
Taking a scheduled order resets the cycle counter and immediately republishes
from the mold so the trader's offer stays live without manual intervention.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a full recurring order scheduling feature: a ScheduledOrder Mongoose model, a 4-step Telegraf wizard scene, /scheduleorder and /cancelschedule command handlers, an hourly cron job that publishes matching schedules, a refresh-on-take helper, bot wiring, and i18n strings across 10 locales.

Changes

Scheduled Recurring Orders

Layer / File(s) Summary
ScheduledOrder Mongoose model
models/scheduled_order.ts, models/index.ts
Defines IScheduledOrder interface and scheduledOrderSchema with fields for creator, type, amounts, days/hour schedule, republish_count, last_order_id, and active state; exports model via models/index.ts.
Schedule helpers and message prompts
bot/modules/schedule/helpers.ts, bot/modules/schedule/messages.ts
Adds getRepublishCount(), parseCustomDays(), PRESET_DAYS, DAY_ALIASES, formatDays(), and async prompt helpers (askScheduleDays, askCustomDays, askScheduleHour, askScheduleConfirm).
scheduleOrderWizard 4-step scene
bot/modules/schedule/scenes.ts
Implements wizard steps: prompt days → collect days (preset or custom text) → collect/validate hour → confirm/cancel → create ScheduledOrder and reply with schedule id.
scheduleorder and cancelschedule commands
bot/modules/schedule/commands.ts
scheduleorder validates DM args and enters SCHEDULE_ORDER scene; cancelschedule looks up an active schedule by id and deactivates it.
Hourly publishScheduledOrders job
jobs/scheduled_orders.ts, jobs/index.ts
Queries active ScheduledOrder records matching current UTC day/hour, creates and publishes orders, updates last_order_id and decrements republish_count, deactivates exhausted schedules.
Refresh scheduled order on take
bot/modules/orders/takeOrder.ts
Calls refreshScheduledOrder() after a taken order is deleted from the channel; the helper finds the schedule by last_order_id, creates a new order, republishes it, and updates the schedule record.
Bot wiring, stage, and cron
bot/start.ts, bot/middleware/stage.ts, bot/modules/schedule/index.ts, tests/bot/bot.spec.ts
Registers scheduleOrderWizard in stage middleware, wires commands via ScheduleModule.configure, schedules hourly cron for publishScheduledOrders, and updates the job-count test assertion from 8 to 9.
i18n strings (10 locales)
locales/*.yaml
Adds /scheduleorder and /cancelschedule help entries plus full schedule wizard message keys in de, en, es, fa, fr, it, ko, pt, ru, and uk.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • Luquitasjeffrey
  • grunch
  • mostronatorcoder

Poem

🐇 Hop, hop, schedule set!
Each order posts without a fret.
Days and hours, neatly stored,
Auto-publish — never ignored.
The rabbit clocks each UTC beat,
Recurring trades, tidy and neat! 🕐

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the new recurring auto-republishing schedule commands.
Linked Issues check ✅ Passed The PR implements /scheduleorder, /cancelschedule, hourly republishing, and take-time reset, matching #769's recurring-order workflow.
Out of Scope Changes check ✅ Passed All changes support the scheduled-order feature set; no unrelated functionality was introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (1)
tests/bot/bot.spec.ts (1)

466-466: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the scheduled-order cron explicitly.

This only checks the total number of jobs. The test still passes if some unrelated job is added while the publishScheduledOrders registration disappears or its cron expression changes.

🧪 Tighten the assertion
-    expect(scheduleStub.scheduleJob.callCount).to.be.equal(9);
+    expect(scheduleStub.scheduleJob.callCount).to.equal(9);
+    expect(
+      scheduleStub.scheduleJob
+        .getCalls()
+        .some(call => call.args[0] === '0 * * * *'),
+    ).to.equal(true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bot/bot.spec.ts` at line 466, The test currently only verifies the
total number of scheduled jobs, so it can miss regressions in the
`publishScheduledOrders` registration. Update the assertion in `bot.spec.ts` to
explicitly check the `scheduleJob` call for `publishScheduledOrders`, including
its cron expression and identifying callback/argument, so the test fails if that
schedule is removed or changed even when the overall call count stays the same.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bot/modules/orders/takeOrder.ts`:
- Around line 98-100: The take flow in takeOrder.ts is being delayed by awaiting
scheduled-order republishing before sending the taker’s next-step message. In
the take-buy and take-sell paths around refreshScheduledOrder and
messages.beginTakeBuyMessage / messages.beginTakeSellMessage, start
refreshScheduledOrder in parallel without awaiting it, or move it after the
beginTake*Message call so Telegram/channel publish latency does not block the
active trade flow.
- Around line 200-204: The schedule update in takeOrder is happening
unconditionally after publishFn, even when the order was not actually published;
update the schedule only when the new order has been successfully published. Add
a guard in takeOrder around the schedule.last_order_id and
schedule.republish_count assignments, using the published state returned or set
by publishFn/newOrder before calling schedule.save. Keep the fix localized to
the publish-and-save flow in takeOrder so failed or skipped publishes do not
advance the schedule.

In `@bot/modules/schedule/commands.ts`:
- Around line 1-6: The schedule cancellation flow should validate scheduleId
before calling ScheduledOrder.findOne, since a non-ObjectId value can throw and
currently only gets logged without replying to the user. Add a guard using
Types.ObjectId.isValid(scheduleId) in the command path in commands.ts before the
query, and return an appropriate user-facing message when the id is invalid so
/cancelschedule cannot fail silently.

In `@bot/modules/schedule/helpers.ts`:
- Around line 8-58: parseCustomDays is currently hardcoded to English/Spanish
weekday aliases, so localized wizard prompts still reject valid user input in
other locales. Update the weekday parsing in helpers.ts by either making
DAY_ALIASES locale-aware (driven by the active locale or translated weekday
names) or switching the custom day input flow to a locale-neutral format, and
keep the parser logic in parseCustomDays aligned with whatever format the
schedule wizard expects.

In `@bot/modules/schedule/messages.ts`:
- Around line 3-15: formatDays() is hardcoding English weekday abbreviations, so
the confirmation summary is not localized. Update the DAY_LABELS mapping in
messages.ts to read weekday names from i18n/locales instead of static strings,
and make sure the caller still uses formatDays(days) unchanged. Keep the
localized labels aligned with the existing confirmation text flow and add/update
the matching entries under locales/ for each weekday.

In `@bot/modules/schedule/scenes.ts`:
- Around line 83-88: The schedule wizard hour parsing is too permissive because
scenes.ts uses parseInt on ctx.message text, which accepts inputs like 7pm,
12.5, and 0x10. Tighten the validation in the hour-handling flow so only a
strict integer string in the 0–23 range is accepted before setting state.hour,
and keep the invalid_hour reply path in the same scene logic.

In `@jobs/scheduled_orders.ts`:
- Around line 48-75: The scheduled order flow in the job handler should only
advance the schedule when a post is actually published successfully. Update the
logic around createOrder and the publishBuyOrderMessage/publishSellOrderMessage
call so you capture an explicit publish-success result, and only then set
last_order_id, decrement republish_count, and possibly deactivate the schedule.
Also reserve the current UTC slot before calling the publish helper, and make
sure a failed schedule.save() does not leave the order eligible to be published
again on the next run.

In `@locales/de.yaml`:
- Around line 264-265: The help text for the schedule order command is
translating the required buy|sell argument, which breaks the command handler’s
expected input. Update the localized usage strings in the entries for the
schedule-order command so the argument remains exactly buy|sell while keeping
the surrounding German text translated. Make the same correction in the
duplicate occurrence mentioned in the comment, and verify the command names and
placeholders in the help output still match what the wizard/parser expects.

In `@locales/es.yaml`:
- Around line 265-266: The Spanish command help text is using translated
placeholders for the first argument, but bot/modules/schedule/commands.ts only
accepts the literal values buy or sell, so update the schedule command syntax in
locales/es.yaml to keep buy|sell unchanged while leaving the rest of the
localized text intact. Make the same correction anywhere else in the Spanish
schedule help entries that mirror this syntax so users are shown the exact
values accepted by the command parser.

In `@locales/fa.yaml`:
- Line 317: The help text for the `/scheduleorder` command contains a malformed
optional premium token, so update the string in the locale entry to use a
properly closed optional bracketed token for the premium argument. Locate the
command usage text in the `fa.yaml` locale and correct the `[_پریمیوم_>`
fragment so the optional parameter syntax is valid and consistent with the other
placeholders.

In `@locales/fr.yaml`:
- Line 266: The `/scheduleorder` help text is using translated action tokens,
but the command handler only accepts the literal `buy|sell` values. Update the
relevant `/scheduleorder` entries in the locale definitions to document
`buy|sell` instead of `acheter|vendre`, keeping the rest of the argument
placeholders unchanged so the displayed usage matches the parser expected by the
schedule order handler.
- Around line 741-742: The weekday prompts in the French locale are using
examples that the schedule parser does not accept. Update the messages for
schedule_enter_custom_days and invalid_days in locales/fr.yaml to use weekday
aliases supported by bot/modules/schedule/helpers.ts, or add matching French
aliases in that parser so the examples and validation are aligned.

In `@locales/it.yaml`:
- Line 264: The localized `/scheduleorder` help text is using translated
arguments that do not match what `bot/modules/schedule/commands.ts` actually
parses. Update the `/scheduleorder` usage string in the locale entry to keep the
action argument as `buy|sell` (including the other affected locale occurrence)
so the help output matches the command’s accepted values and users can copy it
correctly.

In `@locales/pt.yaml`:
- Line 265: The `/scheduleorder` help text in the Portuguese locale is
advertising unsupported argument names, which makes the command docs
inconsistent with the parser. Update the localized string for the schedule order
entry in the locale file to use the same `buy|sell` arguments that the command
handler recognizes, and make the same correction in the other referenced
occurrence so the help text stays aligned with the actual parser.

In `@models/scheduled_order.ts`:
- Around line 3-5: The scheduled order typing still uses an `extends Document`
interface with `_id: string`, which should be updated consistently with the
other model typings to fix the Mongoose 8 TS2430 issue. Refactor
`IScheduledOrder` to avoid directly extending `Document`, and instead use the
same pattern applied in `models/user.ts`, `models/order.ts`, and
`models/community.ts`—either a plain interface paired with `HydratedDocument`,
or a `Document` generic approach that types `_id` consistently across all
models.

---

Nitpick comments:
In `@tests/bot/bot.spec.ts`:
- Line 466: The test currently only verifies the total number of scheduled jobs,
so it can miss regressions in the `publishScheduledOrders` registration. Update
the assertion in `bot.spec.ts` to explicitly check the `scheduleJob` call for
`publishScheduledOrders`, including its cron expression and identifying
callback/argument, so the test fails if that schedule is removed or changed even
when the overall call count stays the same.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ce3d73c9-7d9c-4d07-84ff-ec93c2b72026

📥 Commits

Reviewing files that changed from the base of the PR and between bf32684 and 2c56591.

📒 Files selected for processing (23)
  • bot/middleware/stage.ts
  • bot/modules/orders/takeOrder.ts
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/helpers.ts
  • bot/modules/schedule/index.ts
  • bot/modules/schedule/messages.ts
  • bot/modules/schedule/scenes.ts
  • bot/start.ts
  • jobs/index.ts
  • jobs/scheduled_orders.ts
  • locales/de.yaml
  • locales/en.yaml
  • locales/es.yaml
  • locales/fa.yaml
  • locales/fr.yaml
  • locales/it.yaml
  • locales/ko.yaml
  • locales/pt.yaml
  • locales/ru.yaml
  • locales/uk.yaml
  • models/index.ts
  • models/scheduled_order.ts
  • tests/bot/bot.spec.ts

Comment on lines +98 to 100
await refreshScheduledOrder(order._id, bot);

await messages.beginTakeBuyMessage(ctx, bot, user, order);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don’t block the take flow on scheduled republishing.

Line 98 and Line 151 await a DB create plus Telegram channel publish before sending the taker’s next-step message. Start the refresh in parallel or move it after beginTake*Message so channel-publish latency does not delay the active trade flow.

Suggested ordering
       await deleteOrderFromChannel(order, bot.telegram);
-      await refreshScheduledOrder(order._id, bot);
-
+      const refreshPromise = refreshScheduledOrder(order._id, bot);
       await messages.beginTakeBuyMessage(ctx, bot, user, order);
+      await refreshPromise;
       await deleteOrderFromChannel(order, bot.telegram);
-      await refreshScheduledOrder(order._id, bot);
+      const refreshPromise = refreshScheduledOrder(order._id, bot);
       await messages.beginTakeSellMessage(ctx, bot, user, order);
+      await refreshPromise;

Also applies to: 151-152

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/orders/takeOrder.ts` around lines 98 - 100, The take flow in
takeOrder.ts is being delayed by awaiting scheduled-order republishing before
sending the taker’s next-step message. In the take-buy and take-sell paths
around refreshScheduledOrder and messages.beginTakeBuyMessage /
messages.beginTakeSellMessage, start refreshScheduledOrder in parallel without
awaiting it, or move it after the beginTake*Message call so Telegram/channel
publish latency does not block the active trade flow.

Comment on lines +200 to +204
await publishFn(bot, creator, newOrder, i18n, false);

schedule.last_order_id = newOrder._id;
schedule.republish_count = getRepublishCount();
await schedule.save();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Only advance the schedule after a successful publish.

The publish helpers can return after closing the new order or swallowing a send failure, but Line 202 still stores that order as last_order_id and Line 203 resets the counter. Guard on the published state before updating the schedule.

Guard before saving the schedule
     await publishFn(bot, creator, newOrder, i18n, false);
+
+    if (newOrder.status === 'CLOSED' || !newOrder.tg_channel_message1) {
+      logger.error(
+        `refreshScheduledOrder: refreshed order ${newOrder._id} was not published`,
+      );
+      return;
+    }
 
     schedule.last_order_id = newOrder._id;
     schedule.republish_count = getRepublishCount();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await publishFn(bot, creator, newOrder, i18n, false);
schedule.last_order_id = newOrder._id;
schedule.republish_count = getRepublishCount();
await schedule.save();
await publishFn(bot, creator, newOrder, i18n, false);
if (newOrder.status === 'CLOSED' || !newOrder.tg_channel_message1) {
logger.error(
`refreshScheduledOrder: refreshed order ${newOrder._id} was not published`,
);
return;
}
schedule.last_order_id = newOrder._id;
schedule.republish_count = getRepublishCount();
await schedule.save();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/orders/takeOrder.ts` around lines 200 - 204, The schedule update
in takeOrder is happening unconditionally after publishFn, even when the order
was not actually published; update the schedule only when the new order has been
successfully published. Add a guard in takeOrder around the
schedule.last_order_id and schedule.republish_count assignments, using the
published state returned or set by publishFn/newOrder before calling
schedule.save. Keep the fix localized to the publish-and-save flow in takeOrder
so failed or skipped publishes do not advance the schedule.

Comment on lines +1 to +6
import { CommunityContext } from '../community/communityContext';
import { ScheduledOrder } from '../../../models';
import { validateSellOrder, validateBuyOrder } from '../../validations';
import { getCurrency } from '../../../util';
import { logger } from '../../../logger';
import { SCHEDULE_ORDER } from './scenes';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## File outline\n'
ast-grep outline bot/modules/schedule/commands.ts --view expanded || true

printf '\n## Relevant file section\n'
sed -n '1,140p' bot/modules/schedule/commands.ts | cat -n

printf '\n## Mongoose dependency/version\n'
rg -n '"mongoose"|from '"'"'mongoose'"'"'|from "mongoose"' package.json . --glob 'package.json' --glob '!**/node_modules/**' || true

Repository: lnp2pBot/bot

Length of output: 3330


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Mongoose ObjectId validation probe\n'
node - <<'JS'
const samples = [undefined, null, '', 'foo', '507f1f77bcf86cd799439011', '507f1f77bcf86cd79943901', 123];
try {
  const mongoose = require('mongoose');
  console.log('mongoose version:', mongoose.version);
  for (const s of samples) {
    let valid = mongoose.Types.ObjectId.isValid(s);
    console.log(JSON.stringify(s), '=>', valid);
  }
} catch (e) {
  console.log('Cannot require mongoose in sandbox:', e.message);
}
JS

Repository: lnp2pBot/bot

Length of output: 356


Validate scheduleId before findOne.

ScheduledOrder.findOne({ _id: scheduleId, ... }) will throw on a non-ObjectId value like foo, and this catch only logs the error, so /cancelschedule foo fails without any user-facing reply. Guard with Types.ObjectId.isValid(scheduleId) before querying.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/schedule/commands.ts` around lines 1 - 6, The schedule
cancellation flow should validate scheduleId before calling
ScheduledOrder.findOne, since a non-ObjectId value can throw and currently only
gets logged without replying to the user. Add a guard using
Types.ObjectId.isValid(scheduleId) in the command path in commands.ts before the
query, and return an appropriate user-facing message when the id is invalid so
/cancelschedule cannot fail silently.

Comment on lines +8 to +58
// Day names (Spanish, English, abbreviated) mapped to getUTCDay() values (0=Sun)
const DAY_ALIASES: Record<string, number> = {
domingo: 0,
sunday: 0,
sun: 0,
dom: 0,
lunes: 1,
monday: 1,
mon: 1,
lun: 1,
martes: 2,
tuesday: 2,
tue: 2,
mar: 2,
miercoles: 3,
wednesday: 3,
wed: 3,
mie: 3,
jueves: 4,
thursday: 4,
thu: 4,
jue: 4,
viernes: 5,
friday: 5,
fri: 5,
vie: 5,
sabado: 6,
saturday: 6,
sat: 6,
sab: 6,
};

// Parses a comma/space separated list of day names into sorted weekday numbers.
// Tolerant to accents, casing and extra whitespace. Returns null if any token
// is not a recognized day.
export const parseCustomDays = (input: string): number[] | null => {
const parts = input
.toLowerCase()
.split(/[,\s]+/)
.filter(Boolean);
if (parts.length === 0) return null;
const days = new Set<number>();
for (const part of parts) {
// strip accents so "miércoles" -> "miercoles"
const normalized = part.normalize('NFD').replace(/[̀-ͯ]/g, '');
const dayNum = DAY_ALIASES[normalized];
if (dayNum === undefined) return null;
days.add(dayNum);
}
return [...days].sort((a, b) => a - b);
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Custom day parsing is locale-limited.

This wizard is being shipped with 10 locales, but parseCustomDays() only accepts English/Spanish weekday tokens. In fr, de, ru, etc., users can follow the localized prompt and still never get past invalid_days when they enter weekday names in their own language. Either localize the alias table per locale or change the custom input format to something locale-neutral. As per coding guidelines, "bot/**/*.ts: House commands, scenes, and middleware modules in bot/ directory; pair new flows with text updates under locales/".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/schedule/helpers.ts` around lines 8 - 58, parseCustomDays is
currently hardcoded to English/Spanish weekday aliases, so localized wizard
prompts still reject valid user input in other locales. Update the weekday
parsing in helpers.ts by either making DAY_ALIASES locale-aware (driven by the
active locale or translated weekday names) or switching the custom day input
flow to a locale-neutral format, and keep the parser logic in parseCustomDays
aligned with whatever format the schedule wizard expects.

Source: Coding guidelines

Comment on lines +3 to +15
const DAY_LABELS: Record<number, string> = {
0: 'Sun',
1: 'Mon',
2: 'Tue',
3: 'Wed',
4: 'Thu',
5: 'Fri',
6: 'Sat',
};

export const formatDays = (days: number[]): string =>
days.map(d => DAY_LABELS[d]).join(', ');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the weekday labels in the confirmation summary.

formatDays() always renders Sun/Mon/… so every non-English locale gets an English day list inside an otherwise translated confirmation message. Pull these labels from i18n instead of hardcoding them here. As per coding guidelines, "bot/**/*.ts: House commands, scenes, and middleware modules in bot/ directory; pair new flows with text updates under locales/".

Also applies to: 65-70

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/schedule/messages.ts` around lines 3 - 15, formatDays() is
hardcoding English weekday abbreviations, so the confirmation summary is not
localized. Update the DAY_LABELS mapping in messages.ts to read weekday names
from i18n/locales instead of static strings, and make sure the caller still uses
formatDays(days) unchanged. Keep the localized labels aligned with the existing
confirmation text flow and add/update the matching entries under locales/ for
each weekday.

Source: Coding guidelines

Comment thread locales/fr.yaml
/blocklist - Liste de vos utilisateurs bloqués
/cancel <_order id_> - Annule une commande qui n'a pas encore été prise
/cancelall - Annule toutes les commandes validées et non prises
/scheduleorder <_acheter|vendre_> <_sats_> <_montant fiat_> <_code fiat_> <_méthode de paiement_> [_prime_] - Crée une commande récurrente qui se republie automatiquement

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep /scheduleorder arguments as buy|sell.

The handler only accepts the literal English tokens buy and sell. Documenting acheter|vendre here will bounce French users straight back to the usage message.

Proposed fix
-  /scheduleorder <_acheter|vendre_> <_sats_> <_montant fiat_> <_code fiat_> <_méthode de paiement_> [_prime_] - Crée une commande récurrente qui se republie automatiquement
+  /scheduleorder <_buy|sell_> <_sats_> <_montant fiat_> <_code fiat_> <_méthode de paiement_> [_prime_] - Crée une commande récurrente qui se republie automatiquement
-scheduleorder_usage: "Utilisation : /scheduleorder <acheter|vendre> <sats> <montant fiat> <code fiat> <méthode de paiement> [prime]"
+scheduleorder_usage: "Utilisation : /scheduleorder <buy|sell> <sats> <montant fiat> <code fiat> <méthode de paiement> [prime]"

Also applies to: 730-730

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@locales/fr.yaml` at line 266, The `/scheduleorder` help text is using
translated action tokens, but the command handler only accepts the literal
`buy|sell` values. Update the relevant `/scheduleorder` entries in the locale
definitions to document `buy|sell` instead of `acheter|vendre`, keeping the rest
of the argument placeholders unchanged so the displayed usage matches the parser
expected by the schedule order handler.

Comment thread locales/fr.yaml
Comment on lines +741 to +742
schedule_enter_custom_days: "✍️ Entrez les jours séparés par des virgules (ex : lundi, mercredi, vendredi)"
invalid_days: "⚠️ Je n'ai pas compris ces jours. Veuillez les entrer séparés par des virgules (ex : lundi, mercredi, vendredi)."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== helpers outline =="
ast-grep outline bot/modules/schedule/helpers.ts --view expanded

echo
echo "== DAY_ALIASES / parseCustomDays =="
nl -ba bot/modules/schedule/helpers.ts | sed -n '1,260p' | sed -n '/DAY_ALIASES/,+140p'

Repository: lnp2pBot/bot

Length of output: 542


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' bot/modules/schedule/helpers.ts

Repository: lnp2pBot/bot

Length of output: 1703


Add French weekday aliases or change the examples

lundi, mercredi, and vendredi are not accepted by bot/modules/schedule/helpers.ts; the parser only maps Spanish and English aliases, so this prompt currently tells users to enter invalid values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@locales/fr.yaml` around lines 741 - 742, The weekday prompts in the French
locale are using examples that the schedule parser does not accept. Update the
messages for schedule_enter_custom_days and invalid_days in locales/fr.yaml to
use weekday aliases supported by bot/modules/schedule/helpers.ts, or add
matching French aliases in that parser so the examples and validation are
aligned.

Comment thread locales/it.yaml
/blocklist - Elenco degli utenti bloccati
/cancel <_order id_> - Cancella un ordine che non è ancora stato accettato
/cancelall - Cancella tutti gli ordini non ancora accettati
/scheduleorder <_comprare|vendere_> <_sats_> <_importo fiat_> <_codice fiat_> <_metodo di pagamento_> [_premio_] - Crea un ordine ricorrente che si ripubblica automaticamente

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep /scheduleorder arguments as buy|sell.

bot/modules/schedule/commands.ts only accepts buy and sell. Showing comprare|vendere here makes the localized help incorrect and the command fail for users who follow it.

Proposed fix
-  /scheduleorder <_comprare|vendere_> <_sats_> <_importo fiat_> <_codice fiat_> <_metodo di pagamento_> [_premio_] - Crea un ordine ricorrente che si ripubblica automaticamente
+  /scheduleorder <_buy|sell_> <_sats_> <_importo fiat_> <_codice fiat_> <_metodo di pagamento_> [_premio_] - Crea un ordine ricorrente che si ripubblica automaticamente
-scheduleorder_usage: "Utilizzo: /scheduleorder <comprare|vendere> <sats> <importo fiat> <codice fiat> <metodo di pagamento> [premio]"
+scheduleorder_usage: "Utilizzo: /scheduleorder <buy|sell> <sats> <importo fiat> <codice fiat> <metodo di pagamento> [premio]"

Also applies to: 728-728

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@locales/it.yaml` at line 264, The localized `/scheduleorder` help text is
using translated arguments that do not match what
`bot/modules/schedule/commands.ts` actually parses. Update the `/scheduleorder`
usage string in the locale entry to keep the action argument as `buy|sell`
(including the other affected locale occurrence) so the help output matches the
command’s accepted values and users can copy it correctly.

Comment thread locales/pt.yaml
/blocklist - Lista de seus usuários bloqueados
/cancel <ID do pedido> - Cancela um pedido que não foi feito
/cancelall - Cancela todos os pedidos postados e não atendidos
/scheduleorder <_comprar|vender_> <_sats_> <_valor fiat_> <_código fiat_> <_método de pagamento_> [_prêmio_] - Cria um pedido recorrente que se republica automaticamente

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep /scheduleorder arguments as buy|sell.

The command handler only recognizes buy and sell. Advertising comprar|vender here makes the Portuguese help unusable for the actual parser.

Proposed fix
-  /scheduleorder <_comprar|vender_> <_sats_> <_valor fiat_> <_código fiat_> <_método de pagamento_> [_prêmio_] - Cria um pedido recorrente que se republica automaticamente
+  /scheduleorder <_buy|sell_> <_sats_> <_valor fiat_> <_código fiat_> <_método de pagamento_> [_prêmio_] - Cria um pedido recorrente que se republica automaticamente
-scheduleorder_usage: "Uso: /scheduleorder <comprar|vender> <sats> <valor fiat> <código fiat> <método de pagamento> [prêmio]"
+scheduleorder_usage: "Uso: /scheduleorder <buy|sell> <sats> <valor fiat> <código fiat> <método de pagamento> [prêmio]"

Also applies to: 730-730

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@locales/pt.yaml` at line 265, The `/scheduleorder` help text in the
Portuguese locale is advertising unsupported argument names, which makes the
command docs inconsistent with the parser. Update the localized string for the
schedule order entry in the locale file to use the same `buy|sell` arguments
that the command handler recognizes, and make the same correction in the other
referenced occurrence so the help text stays aligned with the actual parser.

Comment thread models/scheduled_order.ts
Comment on lines +3 to +5
export interface IScheduledOrder extends Document {
_id: string;
creator_id: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tsconfig files =="
fd 'tsconfig.*json$'

echo
echo "== Document extensions in model files =="
rg -n --type=ts 'extends Document' models

echo
echo "== explicit _id string declarations =="
rg -n --type=ts '_id:\s*string' models

echo
echo "== scheduled_order model =="
sed -n '1,80p' models/scheduled_order.ts

Repository: lnp2pBot/bot

Length of output: 3534


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json (deps + scripts) =="
sed -n '1,220p' package.json

echo
echo "== tsconfig.json =="
sed -n '1,220p' tsconfig.json

echo
echo "== tsconfig.test.json =="
sed -n '1,220p' tsconfig.test.json

echo
echo "== model files with Document/_id patterns =="
for f in models/*.ts; do
  echo "--- $f ---"
  rg -n 'extends Document|_id:\s*string|mongoose\.model<|Schema<' "$f" || true
done

Repository: lnp2pBot/bot

Length of output: 5299


🌐 Web query:

Mongoose 8 Document _id string TS2430 interface extends Document typings

💡 Result:

In Mongoose 8, you should avoid creating interfaces that extend the Document class, as this pattern is discouraged, causes performance issues, and often leads to TypeScript errors like TS2430 [1][2]. Instead, use a separate interface for your raw document data and define a union type for the hydrated document [3][4]. If your _id is a string instead of the default ObjectId, you must explicitly specify this using the Document generic [5]. Recommended Pattern: 1. Define a clean interface representing the data structure (do not extend Document) [3][4]. 2. Use HydratedDocument or an intersection type for Mongoose-specific functionality [3][6][4]. // 1. Define raw data interface interface IUser { _id: string; // Explicitly define as string name: string; } // 2. Define the HydratedDocument type import { HydratedDocument } from 'mongoose'; type UserDocument = HydratedDocument; // Alternatively, use an intersection if you need manual control type UserDocument = IUser & Document; Why TS2430 occurs: This error typically arises because the interface you are extending (Document) has a predefined _id property (defaulting to ObjectId), and your custom interface attempts to override it with a different type (e.g., string), which violates inheritance constraints [7][8]. By moving away from extends Document and utilizing the Mongoose generics (e.g., Document), you resolve these conflicts correctly [7][5]. For further guidance, consult the official Mongoose TypeScript documentation which explicitly advises against the "extends Document" approach [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== scheduled_order usages =="
rg -n --type=ts 'ScheduledOrder|IScheduledOrder|scheduled_order' .

echo
echo "== _id usage on scheduled order instances =="
rg -n --type=ts '\._id\b' models jobs util bot ln lnurl app.ts scripts tests

Repository: lnp2pBot/bot

Length of output: 23854


Update the Mongoose model typings across all extends Document interfaces with _id: string.
models/scheduled_order.ts has the same pattern as models/user.ts, models/order.ts, and models/community.ts, so changing only this file won’t clear the Mongoose 8 TS2430 error. Use a raw interface + HydratedDocument, or type _id through the Document generic consistently.

🧰 Tools
🪛 GitHub Actions: NodeJS Continuous Integration / 0_ci_to_main.txt

[error] 3-3: TypeScript (tsc) error TS2430: Interface 'IScheduledOrder' incorrectly extends interface 'Document<ObjectId, any, any, Record<string, any>, {}>'. Types of property '_id' are incompatible: Type 'string' is not assignable to type 'ObjectId'.

🪛 GitHub Actions: NodeJS Continuous Integration / ci_to_main

[error] 3-3: TypeScript (tsc) error TS2430: Interface 'IScheduledOrder' incorrectly extends interface 'Document<ObjectId, any, any, Record<string, any>, {}>'. Types of property '_id' are incompatible: Type 'string' is not assignable to type 'ObjectId'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@models/scheduled_order.ts` around lines 3 - 5, The scheduled order typing
still uses an `extends Document` interface with `_id: string`, which should be
updated consistently with the other model typings to fix the Mongoose 8 TS2430
issue. Refactor `IScheduledOrder` to avoid directly extending `Document`, and
instead use the same pattern applied in `models/user.ts`, `models/order.ts`, and
`models/community.ts`—either a plain interface paired with `HydratedDocument`,
or a `Document` generic approach that types `_id` consistently across all
models.

Source: Pipeline failures

@Matobi98 Matobi98 marked this pull request as draft June 29, 2026 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add /scheduleorder command for recurring/auto-republishing orders

1 participant