Skip to content

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

Open
Matobi98 wants to merge 19 commits into
lnp2pBot:mainfrom
Matobi98:feat/scheduleorder
Open

feat: add /scheduleorder and /cancelschedule for recurring auto-republishing orders#849
Matobi98 wants to merge 19 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 /scheduleorder with an interactive wizard (preset/custom days) and UTC hour confirmation, including eligibility gating.
    • Added schedule management commands: /cancelschedule, /listschedules, and /cancelallschedules.
    • Scheduled orders automatically publish at the top of every hour (UTC).
  • Bug Fixes
    • Improved recurring order publishing reliability by reserving republish cycles to reduce duplicates; added clearer handling for dormant creators.
    • Refined the buy/sell take-order flow to clean up channel messages and proceed immediately.
  • Documentation
    • Expanded /help text and added schedule-flow translations in multiple languages.
  • Tests
    • Updated recurring job scheduling assertions.

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

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds scheduled-order creation, hourly publishing, cancellation commands, bot wiring, and locale text for recurring schedules.

Changes

Scheduled Recurring Orders

Layer / File(s) Summary
Model and schedule utilities
models/scheduled_order.ts, models/index.ts, bot/modules/schedule/helpers.ts, bot/modules/schedule/messages.ts
Defines the scheduled-order schema and exports helpers for republish counts, day parsing, preset days, day labels, and schedule confirmation prompts.
Schedule command flow and wizard
bot/modules/schedule/commands.ts, bot/modules/schedule/scenes.ts, bot/modules/schedule/index.ts, bot/middleware/stage.ts
Adds /scheduleorder, /cancelschedule, /listschedules, and /cancelallschedules, wires the schedule wizard into the module and stage, and implements the 4-step scheduling scene.
Publishing and refresh after take
jobs/scheduled_orders.ts, jobs/index.ts, bot/modules/orders/takeOrder.ts
Publishes matching schedules hourly, reserves republish cycles atomically, and changes take-order message sequencing.
Bot module and cron wiring
bot/start.ts, tests/bot/bot.spec.ts
Registers the schedule module, schedules the hourly job, and updates the recurring-jobs test assertion.
Locale strings
locales/*.yaml, .env-sample
Adds schedule help text, schedule-flow translations, and schedule-related environment variables in the updated locales and sample config.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: Luquitasjeffrey, grunch, mostronatorcoder

Poem

A bunny hops at UTC dawn,
With schedule bells all nicely drawn.
By day and hour, the orders wake,
Then hop to chat for duty’s sake.
Hop hop! 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers scheduling and cancellation, but the summaries don't show the required Order.republish_count/reset-on-take/delete-on-expiry flow from #769. Align the implementation with #769 by adding the republish counter to Order, resetting it on take, and republishing expired orders through the expiry job.
Out of Scope Changes check ⚠️ Warning It bundles extra features beyond #769, including /listschedules, /cancelallschedules, schedule-eligibility gating, and dormant-maker removal. Split unrelated schedule-management, gating, and dormant-maker logic into separate follow-up PRs unless they are explicitly required.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main new commands and recurring auto-republishing behavior.
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 thread bot/modules/orders/takeOrder.ts Outdated
Comment thread bot/modules/orders/takeOrder.ts Outdated
Comment thread bot/modules/schedule/commands.ts
Comment thread bot/modules/schedule/helpers.ts Outdated
Comment thread bot/modules/schedule/messages.ts
Comment thread locales/fr.yaml Outdated
Comment thread locales/fr.yaml Outdated
Comment thread locales/it.yaml Outdated
Comment thread locales/pt.yaml Outdated
Comment thread models/scheduled_order.ts
@Matobi98 Matobi98 marked this pull request as draft June 29, 2026 23:14

@ermeme ermeme 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.

Code Review Summary

Verdict: Changes Requested

I re-read the PR conversation and the current head. The feature direction is good, but there are two blocking consistency bugs in the schedule refresh flow:

Blocking

  • bot/modules/orders/takeOrder.ts: refreshScheduledOrder() treats publishBuyOrderMessage() / publishSellOrderMessage() as if they return a success flag. They do not — those helpers swallow errors and return void, so if (!published) is always true. That means the schedule never updates its last_order_id or republish_count after a take, so the next taken order cannot be matched back to the schedule. In practice, the recurring-order feature does not refresh correctly.
  • jobs/scheduled_orders.ts: the hourly publisher advances the schedule after calling the publish helper, but there is no actual success signal and no atomic claim/check around the publish+save boundary. Because the publish helpers swallow failures, a Telegram/channel failure can still consume a republish cycle and move last_order_id, leaving the schedule desynced with the real published state. There is no reconciliation path here, so a transient failure can strand the schedule or make later refreshes behave incorrectly.

Why this is blocking

  • This is state that controls future publications. If it is advanced incorrectly, the feature can silently stop republishing or republish against the wrong state.
  • In a schedule-driven flow, I want the code to prove the publish succeeded before it mutates the schedule record.

If you want, I can re-check the next iteration after the publish/schedule handoff is made atomic or otherwise crash-safe.

Comment thread bot/modules/orders/takeOrder.ts Outdated
Comment thread jobs/scheduled_orders.ts

@ermeme ermeme 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.

Code Review Summary

Verdict: Request changes

Blocking

  • The new models/scheduled_order.ts interface still extends Document while declaring _id: string, which is a Mongoose 8 TS2430 compile blocker.
  • The publish/update path in jobs/scheduled_orders.ts (and the same refresh path in bot/modules/orders/takeOrder.ts) still advances the schedule only after the external publish. If schedule.save() fails after the order is already posted, the next tick can republish the same schedule or leave the refresh chain stranded.

Non-blocking

  • I did not find other blocking issues on the current head.

Comment thread models/scheduled_order.ts
Comment thread jobs/scheduled_orders.ts Outdated
Comment thread bot/modules/orders/takeOrder.ts Outdated
@Matobi98 Matobi98 marked this pull request as ready for review July 1, 2026 21:46

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
jobs/scheduled_orders.ts (1)

48-93: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reserve before creating the order, or clean up the loser.

createOrder() runs before the atomic claim. If reserved is null, Line 92 only skips publishing; the already-created order is left behind. Move the reservation before order creation, then set last_order_id after creation, or explicitly close/delete the unclaimed order.

Possible direction
-        const order = await ordersActions.createOrder(i18n, bot, user, {
-          type: schedule.type,
-          amount: schedule.amount,
-          fiatAmount: schedule.fiat_amount,
-          fiatCode: schedule.fiat_code,
-          paymentMethod: schedule.payment_method,
-          status: 'PENDING',
-          priceMargin: schedule.price_margin,
-          community_id: schedule.community_id,
-        });
-
-        if (!order) {
-          logger.warning(
-            `ScheduledOrder ${schedule._id}: failed to create order`,
-          );
-          continue;
-        }
-
         const remaining = schedule.republish_count - 1;
         const reserved = await ScheduledOrder.findOneAndUpdate(
           {
             _id: schedule._id,
@@
-          {
-            last_order_id: order._id,
-            republish_count: remaining,
-            active: remaining > 0,
-          },
+          { republish_count: remaining, active: remaining > 0 },
           { new: true },
         );
@@
           continue;
         }
+
+        const order = await ordersActions.createOrder(i18n, bot, user, {
+          type: schedule.type,
+          amount: schedule.amount,
+          fiatAmount: schedule.fiat_amount,
+          fiatCode: schedule.fiat_code,
+          paymentMethod: schedule.payment_method,
+          status: 'PENDING',
+          priceMargin: schedule.price_margin,
+          community_id: schedule.community_id,
+        });
+
+        if (!order) {
+          logger.warning(
+            `ScheduledOrder ${schedule._id}: failed to create order after reservation`,
+          );
+          continue;
+        }
+
+        await ScheduledOrder.updateOne(
+          { _id: reserved._id },
+          { last_order_id: order._id },
+        );
🤖 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 `@jobs/scheduled_orders.ts` around lines 48 - 93, The ScheduledOrder processing
flow creates the order before claiming the cycle, so when
ScheduledOrder.findOneAndUpdate returns null the orphaned order is left behind.
Update the logic around ordersActions.createOrder and
ScheduledOrder.findOneAndUpdate so the cycle is reserved before creating the
order, or add cleanup for the loser by closing/deleting the created order when
the reservation fails. Use the existing ScheduledOrder and
ordersActions.createOrder flow to keep the reservation and order creation
consistent.
🤖 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.

Outside diff comments:
In `@jobs/scheduled_orders.ts`:
- Around line 48-93: The ScheduledOrder processing flow creates the order before
claiming the cycle, so when ScheduledOrder.findOneAndUpdate returns null the
orphaned order is left behind. Update the logic around ordersActions.createOrder
and ScheduledOrder.findOneAndUpdate so the cycle is reserved before creating the
order, or add cleanup for the loser by closing/deleting the created order when
the reservation fails. Use the existing ScheduledOrder and
ordersActions.createOrder flow to keep the reservation and order creation
consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c3221794-0f60-4ed5-aa5e-3adb99f2a5db

📥 Commits

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

📒 Files selected for processing (17)
  • bot/modules/orders/takeOrder.ts
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/helpers.ts
  • bot/modules/schedule/messages.ts
  • bot/modules/schedule/scenes.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/scheduled_order.ts
💤 Files with no reviewable changes (1)
  • models/scheduled_order.ts
✅ Files skipped from review due to trivial changes (8)
  • locales/en.yaml
  • locales/de.yaml
  • locales/ko.yaml
  • locales/it.yaml
  • locales/ru.yaml
  • locales/pt.yaml
  • locales/fr.yaml
  • locales/es.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
  • locales/uk.yaml
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/messages.ts
  • bot/modules/orders/takeOrder.ts
  • locales/fa.yaml
  • bot/modules/schedule/scenes.ts

@Luquitasjeffrey Luquitasjeffrey left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It is a nice feature and can improve user experience if it's well instrumented.

The objective of this PR is that it will increase user experience by making order creation easier and increasing the number of orders published, and thus incrementing chance of takers to find a good match.

Direction is good but it can lead to some problems:

  • Spam: users create a lot of schedules, the order book is filled with orders but no takers for those orders.
  • UX degradation: if we allow anyone to create unlimited number of scheduled orders, it can lead to a situtation in which makers are publishing orders that then they are not completing, the takers would waste time searching for a good match.

To prevent that, I think we should apply some requirements for users to create scheduled orders:

  • A hard limit on how much scheduled orders can an user create SCHEDULE_MAX_PER_USER env var
  • Minimal user requirements (user age using the bot, number of completed orders, volume traded, reputation)
  • COMPLETION RATE REQUIREMENT (important): The rate of orders in status SUCCESS / total_orders should be greater than a configured threshhold. For instance if the maker only completes 10% of all his orders, there is a good chance that the orders being published automatically by the scheduler will result in time wasting for the taker, degrading user expeience. In contrast if we require for instance 90% completion rate, only the best and qualified users will have the chance of publishing orders automatically, and user experience will be improved.
  • Limit on dormant users: the completion rate is good to limit spam, but if an user with scheduled orders doesn't answers orders for a week, the UX would still be degrading, so I think we should put a hard limit on how much consecutive orders can an user with scheduled orders have, in that case, the bot should send the user "I removed all your scheduled orders because you didn't complete your last 5 taken orders".

Comment thread bot/modules/orders/takeOrder.ts Outdated
Comment on lines +160 to +225
// When a scheduled order is taken, reset its cycle counter and publish a
// fresh order from the mold so the trader's offer stays live on the channel.
const refreshScheduledOrder = async (
orderId: string,
bot: HasTelegram,
): Promise<void> => {
try {
const schedule = await ScheduledOrder.findOne({
last_order_id: orderId,
active: true,
});
if (!schedule) return;

const creator = await User.findById(schedule.creator_id);
if (!creator) return;

const i18n = await getUserI18nContext(creator);

const ordersActions = require('../../ordersActions');
const {
publishBuyOrderMessage,
publishSellOrderMessage,
} = require('../../messages');

const newOrder = await ordersActions.createOrder(i18n, bot, creator, {
type: schedule.type,
amount: schedule.amount,
fiatAmount: schedule.fiat_amount,
fiatCode: schedule.fiat_code,
paymentMethod: schedule.payment_method,
status: 'PENDING',
priceMargin: schedule.price_margin,
community_id: schedule.community_id,
});

if (!newOrder) return;

// Claim the refresh atomically BEFORE the external publish. Guarding on the
// old last_order_id moves the mold->order link to newOrder in a single step,
// so a concurrent take or a retry after a crash can't republish twice: once
// the link points at newOrder, this branch no longer matches the old id.
const claimed = await ScheduledOrder.findOneAndUpdate(
{ _id: schedule._id, last_order_id: orderId, active: true },
{ last_order_id: newOrder._id, republish_count: getRepublishCount() },
{ new: true },
);
if (!claimed) return; // already refreshed by a concurrent take

const publishFn =
schedule.type === 'buy'
? publishBuyOrderMessage
: publishSellOrderMessage;
await publishFn(bot, creator, newOrder, i18n, false);

// publishFn swallows errors and returns void; surface a failed publish for
// observability, but the refresh is already claimed either way.
if (newOrder.status === 'CLOSED' || !newOrder.tg_channel_message1) {
logger.warning(
`refreshScheduledOrder: publish failed for schedule ${schedule._id} after claim`,
);
}
} catch (error) {
logger.error(`refreshScheduledOrder error: ${String(error)}`);
}
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This function seems very overengineered for me. By republishing the order once it has been taken this would flood the order book. The feature should only publish orders when they were scheduled.
Also, the order is automatically republished after it has been completed, that's because if the order was a range order, then after finalization of the order it is republished with the remaning fiat amount

Comment on lines +28 to +44
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) {
const dayNum = DAY_ALIASES[part];
// Guard against inherited Object.prototype keys (e.g. "constructor",
// "toString") which would resolve to a function instead of undefined.
if (typeof dayNum !== 'number') return null;
days.add(dayNum);
}
return [...days].sort((a, b) => a - b);
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This parsing is a little bit hacky for me, BUT i don't see it as a blocker for this PR.
It forces the user of the command to express the days he wants to schedule the order only in english. Definetly not the best UI.
BUT, considering that the command would be targeted to experienced users, it's ok for me in a first implementation.
Given that the command will be used sporadically, I think we should improve this UI only if the command becomes widely used and users don't fully understand the command.

Comment on lines +9 to +10
bot.command('scheduleorder', userMiddleware, scheduleorder);
bot.command('cancelschedule', userMiddleware, cancelschedule);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we should add the commands:
/cancelallschedules: cancels all scheduled orders for this user
/listschedules: lists a brief description of all the user's scheduled orders and gives the user the id of the schedule.

@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: 3

🤖 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`:
- Line 2: The take order flow in takebuy/takesell no longer updates the linked
ScheduledOrder after a successful take, so restore the immediate refresh logic
in takeOrder.ts. Reintroduce the atomic scheduled-order republish/update in the
same path that handles the order take, using the takebuy/takesell flow and the
ScheduledOrder-related logic so the refreshed order is persisted right away
instead of waiting for the hourly job.

In `@bot/modules/schedule/commands.ts`:
- Around line 97-111: The schedule list item rendering in commands.ts is
interpolating free-text payment_method into a Markdown reply without escaping,
which can break ctx.reply in the schedule_list flow. Update the
schedule_list_item mapping in the schedule command to escape Markdown-special
characters in schedule.payment_method before passing it into ctx.i18n.t, and
keep the change localized around the items construction so the schedule listing
remains safe with parse_mode: 'Markdown'.
- Around line 84-140: Both listschedules and cancelallschedules need a
private-chat guard because userMiddleware alone still allows execution in
groups. Add the same ctx.chat?.type !== 'private' check used by scheduleorder at
the start of these handlers, and return a DM-only response before querying
ScheduledOrder or replying with schedule details.
🪄 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: da6d2673-1a98-4ea2-a8e6-9572541aa7b4

📥 Commits

Reviewing files that changed from the base of the PR and between b5e9792 and 9b5fe9d.

📒 Files selected for processing (13)
  • bot/modules/orders/takeOrder.ts
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/index.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
✅ Files skipped from review due to trivial changes (5)
  • locales/en.yaml
  • locales/fr.yaml
  • locales/ru.yaml
  • locales/ko.yaml
  • locales/es.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • locales/uk.yaml
  • locales/it.yaml
  • locales/de.yaml

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🤖 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`:
- Line 2: The take order flow in takebuy/takesell no longer updates the linked
ScheduledOrder after a successful take, so restore the immediate refresh logic
in takeOrder.ts. Reintroduce the atomic scheduled-order republish/update in the
same path that handles the order take, using the takebuy/takesell flow and the
ScheduledOrder-related logic so the refreshed order is persisted right away
instead of waiting for the hourly job.

In `@bot/modules/schedule/commands.ts`:
- Around line 97-111: The schedule list item rendering in commands.ts is
interpolating free-text payment_method into a Markdown reply without escaping,
which can break ctx.reply in the schedule_list flow. Update the
schedule_list_item mapping in the schedule command to escape Markdown-special
characters in schedule.payment_method before passing it into ctx.i18n.t, and
keep the change localized around the items construction so the schedule listing
remains safe with parse_mode: 'Markdown'.
- Around line 84-140: Both listschedules and cancelallschedules need a
private-chat guard because userMiddleware alone still allows execution in
groups. Add the same ctx.chat?.type !== 'private' check used by scheduleorder at
the start of these handlers, and return a DM-only response before querying
ScheduledOrder or replying with schedule details.
🪄 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: da6d2673-1a98-4ea2-a8e6-9572541aa7b4

📥 Commits

Reviewing files that changed from the base of the PR and between b5e9792 and 9b5fe9d.

📒 Files selected for processing (13)
  • bot/modules/orders/takeOrder.ts
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/index.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
✅ Files skipped from review due to trivial changes (5)
  • locales/en.yaml
  • locales/fr.yaml
  • locales/ru.yaml
  • locales/ko.yaml
  • locales/es.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • locales/uk.yaml
  • locales/it.yaml
  • locales/de.yaml
🛑 Comments failed to post (3)
bot/modules/orders/takeOrder.ts (1)

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm refreshScheduledOrder no longer exists/used anywhere in the take flow
rg -n "refreshScheduledOrder|ScheduledOrder" bot/modules/orders/takeOrder.ts jobs/scheduled_orders.ts

Repository: lnp2pBot/bot

Length of output: 1460


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the takeOrder file and inspect the relevant sections.
ast-grep outline bot/modules/orders/takeOrder.ts --view expanded

echo '--- takeOrder.ts (lines 1-220) ---'
sed -n '1,220p' bot/modules/orders/takeOrder.ts | cat -n

echo '--- scheduled_orders.ts (lines 1-180) ---'
sed -n '1,180p' jobs/scheduled_orders.ts | cat -n

Repository: lnp2pBot/bot

Length of output: 12026


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the take-order module for any schedule-refresh logic or model writes.
rg -n "refresh|scheduled|ScheduledOrder|findOneAndUpdate|createOrder|takebuy|takesell" bot/modules/orders/takeOrder.ts

Repository: lnp2pBot/bot

Length of output: 304


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the take-order flow still uses the schedule refresh behavior indirectly.
rg -n "refreshScheduledOrder|cycle already claimed|reserved atomically|published order|schedule counter" -S bot jobs

Repository: lnp2pBot/bot

Length of output: 393


Restore scheduled-order refresh in bot/modules/orders/takeOrder.ts
takebuy/takesell no longer refresh the linked ScheduledOrder, so a taken scheduled order now waits for the hourly job instead of being republished immediately. Reintroduce the atomic refresh here, or update the feature contract if that behavior moved elsewhere.

🤖 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` at line 2, The take order flow in
takebuy/takesell no longer updates the linked ScheduledOrder after a successful
take, so restore the immediate refresh logic in takeOrder.ts. Reintroduce the
atomic scheduled-order republish/update in the same path that handles the order
take, using the takebuy/takesell flow and the ScheduledOrder-related logic so
the refreshed order is persisted right away instead of waiting for the hourly
job.
bot/modules/schedule/commands.ts (2)

84-140: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether other sensitive account commands (e.g. /listorders, /settings) restrict to private chat
rg -n "chat.type" bot/modules --type=ts -C2

Repository: lnp2pBot/bot

Length of output: 3339


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the schedule command handlers and their registration points.
git ls-files bot/modules/schedule
printf '\n--- commands.ts ---\n'
sed -n '1,220p' bot/modules/schedule/commands.ts
printf '\n--- index.ts ---\n'
sed -n '1,220p' bot/modules/schedule/index.ts

Repository: lnp2pBot/bot

Length of output: 4972


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any global private-chat gating around command handling.
rg -n "chat\.type !== 'private'|chat\.type === 'private'|bot\.use|command\(" bot -g '*.ts' -C 2

Repository: lnp2pBot/bot

Length of output: 17624


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect userMiddleware for any chat-type restriction or message redirection.
git ls-files bot/middleware
printf '\n--- user.ts ---\n'
sed -n '1,220p' bot/middleware/user.ts

Repository: lnp2pBot/bot

Length of output: 1156


Keep listschedules and cancelallschedules DM-only. userMiddleware doesn’t block group chats, so these commands can be run publicly; listschedules will expose schedule IDs, payment methods, fiat amounts, and timing to the whole chat. Add the same chat.type !== 'private' guard used by scheduleorder if these are meant to stay private.

🤖 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 84 - 140, Both listschedules
and cancelallschedules need a private-chat guard because userMiddleware alone
still allows execution in groups. Add the same ctx.chat?.type !== 'private'
check used by scheduleorder at the start of these handlers, and return a DM-only
response before querying ScheduledOrder or replying with schedule details.

97-111: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Unescaped user-controlled payment_method can break Markdown replies.

schedule.payment_method is free-text input (per the payment-method prompts in locales) and is interpolated directly into schedule_list_item, which is sent with parse_mode: 'Markdown'. Telegram's classic Markdown parser rejects unbalanced _, *, `, [, ]; any such character in a saved payment method will make ctx.reply throw, and the outer catch only logs it — the user silently gets no reply at all.

🛡️ Proposed fix: escape Markdown special characters before interpolation
+const escapeMarkdown = (text: string) => text.replace(/([_*`[\]])/g, '\\$1');
+
 const items = schedules
   .map(schedule => {
     const hour = String(schedule.hour).padStart(2, '0');
     const typeKey = schedule.type === 'buy' ? 'buying' : 'selling';
     return ctx.i18n.t('schedule_list_item', {
       scheduleId: String(schedule._id),
       type: ctx.i18n.t(typeKey),
       fiatAmount: schedule.fiat_amount.join('-'),
       fiatCode: schedule.fiat_code,
-      paymentMethod: schedule.payment_method,
+      paymentMethod: escapeMarkdown(schedule.payment_method),
       days: formatDays(schedule.days),
       hour: `${hour}:00 UTC`,
     });
   })
   .join('\n');
📝 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.

    const escapeMarkdown = (text: string) => text.replace(/([_*`[\]])/g, '\\$1');

    const items = schedules
      .map(schedule => {
        const hour = String(schedule.hour).padStart(2, '0');
        const typeKey = schedule.type === 'buy' ? 'buying' : 'selling';
        return ctx.i18n.t('schedule_list_item', {
          scheduleId: String(schedule._id),
          type: ctx.i18n.t(typeKey),
          fiatAmount: schedule.fiat_amount.join('-'),
          fiatCode: schedule.fiat_code,
          paymentMethod: escapeMarkdown(schedule.payment_method),
          days: formatDays(schedule.days),
          hour: `${hour}:00 UTC`,
        });
      })
      .join('\n');
🤖 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 97 - 111, The schedule list
item rendering in commands.ts is interpolating free-text payment_method into a
Markdown reply without escaping, which can break ctx.reply in the schedule_list
flow. Update the schedule_list_item mapping in the schedule command to escape
Markdown-special characters in schedule.payment_method before passing it into
ctx.i18n.t, and keep the change localized around the items construction so the
schedule listing remains safe with parse_mode: 'Markdown'.

@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: 1

🧹 Nitpick comments (1)
.env-sample (1)

122-137: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: quoting inconsistency for numeric env vars.

New numeric vars (REPUBLISH_ORDER_DAYS, SCHEDULE_MAX_PER_USER, SCHEDULE_MIN_ACCOUNT_AGE_DAYS, etc.) are quoted, while existing numeric vars in this file (e.g. MAX_DISPUTES=4, PAYMENT_ATTEMPTS=2, COMMUNITY_TTL=31) are unquoted. Functionally harmless since Number() coerces either form, but inconsistent with file convention.

♻️ Optional cleanup
-REPUBLISH_ORDER_DAYS='10'
+REPUBLISH_ORDER_DAYS=10
...
-SCHEDULE_MAX_PER_USER='3'
+SCHEDULE_MAX_PER_USER=3
...
-SCHEDULE_MIN_ACCOUNT_AGE_DAYS='7'
+SCHEDULE_MIN_ACCOUNT_AGE_DAYS=7
...
-SCHEDULE_MIN_COMPLETED_ORDERS='5'
+SCHEDULE_MIN_COMPLETED_ORDERS=5
...
-SCHEDULE_MIN_VOLUME='0'
+SCHEDULE_MIN_VOLUME=0
...
-SCHEDULE_MIN_RATING='4'
+SCHEDULE_MIN_RATING=4
...
-SCHEDULE_MIN_COMPLETION_RATE='0.9'
+SCHEDULE_MIN_COMPLETION_RATE=0.9
🤖 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 @.env-sample around lines 122 - 137, The new scheduled-order numeric env vars
in the sample file are quoted inconsistently with the existing numeric
conventions. Update the added entries around REPUBLISH_ORDER_DAYS and the
SCHEDULE_* settings to match the unquoted style used by MAX_DISPUTES,
PAYMENT_ATTEMPTS, and COMMUNITY_TTL, keeping the .env-sample formatting
consistent.
🤖 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/schedule/helpers.ts`:
- Around line 70-73: The envNumber helper currently converts blank strings to 0
because Number('') is finite, which bypasses the fallback and can disable
schedule gates. Update envNumber in helpers.ts to treat undefined, empty, or
whitespace-only raw values as missing and return the fallback before calling
Number, while keeping the existing finite-number validation for real inputs. Use
the envNumber symbol so the fix applies to all callers like
SCHEDULE_MIN_COMPLETION_RATE and SCHEDULE_MIN_RATING.

---

Nitpick comments:
In @.env-sample:
- Around line 122-137: The new scheduled-order numeric env vars in the sample
file are quoted inconsistently with the existing numeric conventions. Update the
added entries around REPUBLISH_ORDER_DAYS and the SCHEDULE_* settings to match
the unquoted style used by MAX_DISPUTES, PAYMENT_ATTEMPTS, and COMMUNITY_TTL,
keeping the .env-sample formatting consistent.
🪄 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: 8d51863e-2acb-45cc-bf41-a8d0a3b017dc

📥 Commits

Reviewing files that changed from the base of the PR and between 9b5fe9d and 5c21fc0.

📒 Files selected for processing (14)
  • .env-sample
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/helpers.ts
  • bot/modules/schedule/scenes.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
✅ Files skipped from review due to trivial changes (5)
  • locales/uk.yaml
  • locales/ru.yaml
  • locales/ko.yaml
  • locales/de.yaml
  • locales/fa.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • locales/fr.yaml
  • locales/it.yaml
  • bot/modules/schedule/commands.ts
  • locales/en.yaml
  • bot/modules/schedule/scenes.ts

Comment on lines +70 to +73
const envNumber = (raw: string | undefined, fallback: number): number => {
const value = Number(raw);
return Number.isFinite(value) ? value : fallback;
};

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.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

envNumber treats an empty env var as 0, silently disabling gates.

Number('') is 0, which is finite, so a present-but-empty variable bypasses the fallback. For example SCHEDULE_MIN_COMPLETION_RATE= (or SCHEDULE_MIN_RATING=) resolves to 0 instead of the intended default, quietly turning off the anti-spam check. Guard against blank/whitespace input.

🛡️ Proposed fix
 const envNumber = (raw: string | undefined, fallback: number): number => {
-  const value = Number(raw);
-  return Number.isFinite(value) ? value : fallback;
+  if (raw === undefined || raw.trim() === '') return fallback;
+  const value = Number(raw);
+  return Number.isFinite(value) ? value : fallback;
 };
📝 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
const envNumber = (raw: string | undefined, fallback: number): number => {
const value = Number(raw);
return Number.isFinite(value) ? value : fallback;
};
const envNumber = (raw: string | undefined, fallback: number): number => {
if (raw === undefined || raw.trim() === '') return fallback;
const value = Number(raw);
return Number.isFinite(value) ? value : fallback;
};
🤖 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 70 - 73, The envNumber helper
currently converts blank strings to 0 because Number('') is finite, which
bypasses the fallback and can disable schedule gates. Update envNumber in
helpers.ts to treat undefined, empty, or whitespace-only raw values as missing
and return the fallback before calling Number, while keeping the existing
finite-number validation for real inputs. Use the envNumber symbol so the fix
applies to all callers like SCHEDULE_MIN_COMPLETION_RATE and
SCHEDULE_MIN_RATING.

@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: 1

🧹 Nitpick comments (1)
bot/modules/schedule/helpers.ts (1)

175-189: 🚀 Performance & Scalability | 🔵 Trivial

Consider indexing (creator_id, taken_at) on Order for this query.

isDormantMaker runs a find({creator_id, taken_at: {$ne: null}}).sort({taken_at: -1}).limit(limit) on every hourly tick for every non-dormant-cached active schedule. Without a supporting compound index this becomes a collection scan as Order grows.

🤖 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 175 - 189, The isDormantMaker
query in helpers.ts is doing repeated creator_id/taken_at filtering and taken_at
sorting, so add support for this access pattern by indexing the Order model on
(creator_id, taken_at). Update the Order schema/model definition to include a
compound index that matches the find(...).sort(...).limit(...) used by
isDormantMaker so the hourly schedule check can use the index instead of
scanning the collection.
🤖 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/schedule/helpers.ts`:
- Around line 171-189: Exclude in-flight orders from isDormantMaker: the current
Order query in isDormantMaker treats every taken order that is not SUCCESS as
evidence of dormancy, which can incorrectly include ACTIVE, FIAT_SENT, and
DISPUTE. Update the recent-order filtering logic in isDormantMaker to consider
only terminal outcomes (or explicitly skip open states) before applying the
dormant check, while keeping the existing limit and history-length behavior
intact.

---

Nitpick comments:
In `@bot/modules/schedule/helpers.ts`:
- Around line 175-189: The isDormantMaker query in helpers.ts is doing repeated
creator_id/taken_at filtering and taken_at sorting, so add support for this
access pattern by indexing the Order model on (creator_id, taken_at). Update the
Order schema/model definition to include a compound index that matches the
find(...).sort(...).limit(...) used by isDormantMaker so the hourly schedule
check can use the index instead of scanning the collection.
🪄 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: 8c031642-6aed-4348-b86d-9e55bee052ce

📥 Commits

Reviewing files that changed from the base of the PR and between 5c21fc0 and a6142d5.

📒 Files selected for processing (14)
  • .env-sample
  • bot/modules/schedule/helpers.ts
  • bot/modules/schedule/scenes.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
✅ Files skipped from review due to trivial changes (4)
  • locales/fr.yaml
  • locales/de.yaml
  • locales/fa.yaml
  • locales/ko.yaml
🚧 Files skipped from review as they are similar to previous changes (7)
  • .env-sample
  • locales/es.yaml
  • locales/en.yaml
  • locales/pt.yaml
  • locales/it.yaml
  • locales/ru.yaml
  • locales/uk.yaml

Comment on lines +171 to +189
// A maker is "dormant" when their last N taken orders all ended without
// success. Such a user keeps getting orders taken but never follows through, so
// their auto-published orders only waste takers' time. Returns false while
// there is not enough taken-order history to judge.
export const isDormantMaker = async (userId: string): Promise<boolean> => {
const limit = getDormantLimit();
if (limit <= 0) return false;

const recent = await Order.find({
creator_id: userId,
taken_at: { $ne: null },
})
.sort({ taken_at: -1 })
.limit(limit)
.select('status');

if (recent.length < limit) return false;
return recent.every(order => order.status !== 'SUCCESS');
};

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the Order model and inspect the status field / enum values.
fd -a 'order.ts' models
ast-grep outline models/order.ts --items all --match 'status'
rg -n -A5 -B2 "status" models/order.ts | head -80

Repository: lnp2pBot/bot

Length of output: 1061


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the full status enum and any lifecycle helpers for Order.
sed -n '1,220p' models/order.ts | cat -n
printf '\n--- search for terminal/state helpers ---\n'
rg -n "terminal|is.*terminal|SUCCESS|FAILED|CANCELED|EXPIRED|DISPUTE|FIAT_SENT|ACTIVE" models bot | head -200

Repository: lnp2pBot/bot

Length of output: 10599


Exclude in-flight orders from isDormantMaker ACTIVE, FIAT_SENT, and DISPUTE are still open states here, so counting any non-SUCCESS taken order can mark an engaged maker dormant too early. Only evaluate terminal outcomes, or skip orders that haven’t finished yet.

🤖 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 171 - 189, Exclude in-flight
orders from isDormantMaker: the current Order query in isDormantMaker treats
every taken order that is not SUCCESS as evidence of dormancy, which can
incorrectly include ACTIVE, FIAT_SENT, and DISPUTE. Update the recent-order
filtering logic in isDormantMaker to consider only terminal outcomes (or
explicitly skip open states) before applying the dormant check, while keeping
the existing limit and history-length behavior intact.

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

2 participants