-
Notifications
You must be signed in to change notification settings - Fork 138
feat: add /scheduleorder and /cancelschedule for recurring auto-republishing orders #849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
26b89f5
d867784
f73a432
2c56591
0704d18
73d0f42
30d6ac6
5fb65f3
971a00a
6791463
6e18662
b5e9792
8c17b63
9b5fe9d
5d46b9d
0ccbbeb
5c21fc0
d957c15
a6142d5
7e42629
5307ff7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| import { Types } from 'mongoose'; | ||
| import { CommunityContext } from '../community/communityContext'; | ||
| import { ScheduledOrder } from '../../../models'; | ||
| import { validateSellOrder, validateBuyOrder } from '../../validations'; | ||
| import { getCurrency, sanitizeMD } from '../../../util'; | ||
| import { logger } from '../../../logger'; | ||
| import { SCHEDULE_ORDER } from './scenes'; | ||
| import { formatDays } from './messages'; | ||
| import { checkScheduleRequirements } from './helpers'; | ||
|
|
||
| export const scheduleorder = async (ctx: CommunityContext) => { | ||
| try { | ||
| // The scheduling flow is an interactive DM wizard | ||
| if (ctx.message?.chat.type !== 'private') return; | ||
|
|
||
| const text = (ctx.message as any)?.text || ''; | ||
| const [, type, ...rest] = text.trim().split(/\s+/); | ||
|
|
||
| if (!type || !['buy', 'sell'].includes(type.toLowerCase())) { | ||
| return ctx.reply(ctx.i18n.t('scheduleorder_usage')); | ||
| } | ||
|
|
||
| const orderType = type.toLowerCase(); | ||
|
|
||
| // Reuse the existing buy/sell validators by feeding them the order args | ||
| if (!ctx.state) (ctx as any).state = {}; | ||
| if (!ctx.state.command) (ctx as any).state.command = {}; | ||
| ctx.state.command.args = rest; | ||
|
|
||
| const params = | ||
| orderType === 'sell' | ||
| ? await validateSellOrder(ctx) | ||
| : await validateBuyOrder(ctx); | ||
|
|
||
| if (!params) return; | ||
|
|
||
| if (!getCurrency(params.fiatCode)) { | ||
| await ctx.reply(ctx.i18n.t('must_be_valid_currency')); | ||
| return; | ||
| } | ||
|
|
||
| // Gate creation behind anti-spam requirements (limit, seniority, completion | ||
| // rate, etc.) so only trusted makers can auto-publish orders. | ||
| const requirement = await checkScheduleRequirements(ctx.user); | ||
| if (!requirement.ok) { | ||
| await ctx.reply(ctx.i18n.t(requirement.messageKey!, requirement.params)); | ||
| return; | ||
| } | ||
|
|
||
| await ctx.scene.enter(SCHEDULE_ORDER, { | ||
| user: ctx.user, | ||
| scheduleType: orderType, | ||
| amount: params.amount, | ||
| fiatAmount: params.fiatAmount, | ||
| fiatCode: params.fiatCode, | ||
| paymentMethod: params.paymentMethod, | ||
| priceMargin: params.priceMargin, | ||
| }); | ||
| } catch (error) { | ||
| logger.error(error); | ||
| } | ||
| }; | ||
|
|
||
| export const cancelschedule = async (ctx: CommunityContext) => { | ||
| try { | ||
| // Schedule management must stay in the private DM to avoid leaking or | ||
| // destroying schedule data from a group chat. | ||
| if (ctx.message?.chat.type !== 'private') return; | ||
|
|
||
| const user = ctx.user; | ||
| const args = ctx.state?.command?.args || []; | ||
| const scheduleId = args[0]; | ||
|
|
||
| if (!scheduleId || !Types.ObjectId.isValid(scheduleId)) { | ||
| return ctx.reply(ctx.i18n.t('cancelschedule_usage')); | ||
| } | ||
|
|
||
| const schedule = await ScheduledOrder.findOne({ | ||
| _id: scheduleId, | ||
| creator_id: user._id, | ||
| active: true, | ||
| }); | ||
|
|
||
| if (!schedule) { | ||
| return ctx.reply(ctx.i18n.t('schedule_not_found')); | ||
| } | ||
|
|
||
| schedule.active = false; | ||
| await schedule.save(); | ||
|
|
||
| await ctx.reply(ctx.i18n.t('schedule_cancelled', { scheduleId })); | ||
| } catch (error) { | ||
| logger.error(error); | ||
| } | ||
| }; | ||
|
|
||
| export const listschedules = async (ctx: CommunityContext) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These schedule-management commands should be private-chat only. |
||
| try { | ||
| // Schedule management must stay in the private DM to avoid leaking or | ||
| // destroying schedule data from a group chat. | ||
| if (ctx.message?.chat.type !== 'private') return; | ||
|
|
||
| const user = ctx.user; | ||
|
|
||
| const schedules = await ScheduledOrder.find({ | ||
| creator_id: user._id, | ||
| active: true, | ||
| }).sort({ created_at: 1 }); | ||
|
|
||
| if (schedules.length === 0) { | ||
| return ctx.reply(ctx.i18n.t('listschedules_empty')); | ||
| } | ||
|
|
||
| const items = schedules | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .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: sanitizeMD(schedule.payment_method), | ||
| days: formatDays(schedule.days), | ||
| hour: `${hour}:00 UTC`, | ||
| }); | ||
| }) | ||
| .join('\n'); | ||
|
|
||
| await ctx.reply(`${ctx.i18n.t('listschedules_header')}\n\n${items}`, { | ||
| parse_mode: 'Markdown', | ||
| }); | ||
| } catch (error) { | ||
| logger.error(error); | ||
| } | ||
| }; | ||
|
|
||
| export const cancelallschedules = async (ctx: CommunityContext) => { | ||
| try { | ||
| // Schedule management must stay in the private DM to avoid leaking or | ||
| // destroying schedule data from a group chat. | ||
| if (ctx.message?.chat.type !== 'private') return; | ||
|
|
||
| const user = ctx.user; | ||
|
|
||
| const result = await ScheduledOrder.updateMany( | ||
| { creator_id: user._id, active: true }, | ||
| { active: false }, | ||
| ); | ||
|
|
||
| if (result.modifiedCount === 0) { | ||
| return ctx.reply(ctx.i18n.t('cancelallschedules_none')); | ||
| } | ||
|
|
||
| await ctx.reply( | ||
| ctx.i18n.t('all_schedules_cancelled', { count: result.modifiedCount }), | ||
| ); | ||
| } catch (error) { | ||
| logger.error(error); | ||
| } | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.