Skip to content

Commit 5426763

Browse files
committed
feat: add counterparty requirements configuration in /settings
1 parent 2b6b496 commit 5426763

15 files changed

Lines changed: 397 additions & 40 deletions

File tree

.env-sample

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ PAYMENT_ATTEMPTS=2
7373
# Here will go the disputes from non community orders
7474
DISPUTE_CHANNEL='@p2plnbotDispute'
7575

76+
# Maximum values for counterparty requirements that users can set
77+
MAX_COUNTERPARTY_AGE_REQUIREMENT=30
78+
MAX_COUNTERPARTY_ORDERS_REQUIREMENT=10
79+
7680
# time-to-live for communities in days, communities without successful orders on this time are deleted
7781
COMMUNITY_TTL=31
7882

bot/messages.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,20 @@ const userTakerIsBlockedByUserOrder = async (
975975
}
976976
};
977977

978+
const notMeetingRequirementsMessage = async (
979+
ctx: MainContext,
980+
user: UserDocument,
981+
) => {
982+
try {
983+
await ctx.telegram.sendMessage(
984+
user.tg_id,
985+
ctx.i18n.t('not_meeting_requirements'),
986+
);
987+
} catch (error) {
988+
logger.error(error);
989+
}
990+
};
991+
978992
const fiatSentMessages = async (
979993
ctx: MainContext,
980994
buyer: UserDocument,
@@ -2224,4 +2238,5 @@ export {
22242238
userTakerIsBlockedByUserOrder,
22252239
userOrderIsBlockedByUserTaker,
22262240
showQRCodeMessage,
2241+
notMeetingRequirementsMessage,
22272242
};

bot/modules/orders/takeOrder.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { logger } from '../../../logger';
22
import { Block, Order, User } from '../../../models';
33
import { UserDocument } from '../../../models/user';
4-
import { deleteOrderFromChannel, generateRandomImage } from '../../../util';
4+
import {
5+
deleteOrderFromChannel,
6+
generateRandomImage,
7+
getUserAge,
8+
} from '../../../util';
59
import * as messages from '../../messages';
610
import { HasTelegram, MainContext } from '../../start';
711
import {
@@ -73,6 +77,8 @@ export const takebuy = async (
7377
if (await isBannedFromCommunity(user, order.community_id))
7478
return await messages.bannedUserErrorMessage(ctx, user);
7579

80+
if (!(await meetsCounterpartyRequirements(ctx, user, userOffer))) return;
81+
7682
if (!(await validateTakeBuyOrder(ctx, bot, user, order))) return;
7783

7884
const { randomImage } = generateRandomImage(user._id.toString());
@@ -127,6 +133,9 @@ export const takesell = async (
127133
// We verify if the user is not banned on this community
128134
if (await isBannedFromCommunity(user, order.community_id))
129135
return await messages.bannedUserErrorMessage(ctx, user);
136+
137+
if (!(await meetsCounterpartyRequirements(ctx, user, seller))) return;
138+
130139
if (!(await validateTakeSellOrder(ctx, bot, user, order))) return;
131140
order.status = 'WAITING_BUYER_INVOICE';
132141
order.buyer_id = user._id;
@@ -143,6 +152,34 @@ export const takesell = async (
143152
}
144153
};
145154

155+
const meetsCounterpartyRequirements = async (
156+
ctx: MainContext,
157+
user: UserDocument,
158+
orderCreator: UserDocument,
159+
) => {
160+
if (!orderCreator.counterparty_requirements) return true;
161+
162+
const { min_days_using_bot, min_completed_orders } =
163+
orderCreator.counterparty_requirements;
164+
165+
if (min_days_using_bot > 0) {
166+
const ageInDays = getUserAge(user);
167+
if (ageInDays < min_days_using_bot) {
168+
await messages.notMeetingRequirementsMessage(ctx, user);
169+
return false;
170+
}
171+
}
172+
173+
if (min_completed_orders > 0) {
174+
if (user.trades_completed < min_completed_orders) {
175+
await messages.notMeetingRequirementsMessage(ctx, user);
176+
return false;
177+
}
178+
}
179+
180+
return true;
181+
};
182+
146183
const checkBlockingStatus = async (
147184
ctx: MainContext,
148185
user: UserDocument,

bot/modules/user/scenes/settings.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CommunityWizardState,
88
} from '../../community/communityContext';
99
import { Message } from 'telegraf/typings/core/types/typegram';
10+
import { logger } from '../../../../logger';
1011

1112
function make() {
1213
const resetMessage = async (ctx: CommunityContext, next: () => void) => {
@@ -24,6 +25,10 @@ function make() {
2425
npub: '',
2526
community: '',
2627
lightning_address: '',
28+
min_days_using_bot:
29+
user.counterparty_requirements?.min_days_using_bot ?? 0,
30+
min_completed_orders:
31+
user.counterparty_requirements?.min_completed_orders ?? 0,
2732
};
2833
if (user.default_community_id) {
2934
const community = await Community.findById(user.default_community_id);
@@ -129,6 +134,123 @@ function make() {
129134
}
130135
});
131136

137+
scene.command(
138+
'counterpartyage',
139+
resetMessage,
140+
async (ctx: CommunityContext) => {
141+
try {
142+
await ctx.deleteMessage();
143+
const state = ctx.scene.state as CommunityWizardState;
144+
if (ctx.message === undefined || !('text' in ctx.message))
145+
throw new Error('ctx.message is undefined');
146+
const [, days] = ctx.message.text.trim().split(' ');
147+
const min_days = parseInt(days);
148+
if (isNaN(min_days) || min_days < 0) throw new Error('NotValidNumber');
149+
const maxAge = parseInt(
150+
process.env.MAX_COUNTERPARTY_AGE_REQUIREMENT || '30',
151+
);
152+
if (min_days > maxAge) {
153+
state.error = {
154+
i18n: 'invalid_range',
155+
command: '/counterpartyage',
156+
max: maxAge,
157+
};
158+
return await updateMessage(ctx);
159+
}
160+
const user = state.user;
161+
if (!user.counterparty_requirements) {
162+
user.counterparty_requirements = {
163+
min_days_using_bot: 0,
164+
min_completed_orders: 0,
165+
};
166+
}
167+
user.counterparty_requirements.min_days_using_bot = min_days;
168+
await user.save();
169+
state.feedback = { i18n: 'counterpartyage_updated', days: min_days };
170+
await updateMessage(ctx);
171+
} catch (err) {
172+
logger.error(err);
173+
(ctx.scene.state as CommunityWizardState).error = {
174+
i18n: 'invalid_number',
175+
};
176+
await updateMessage(ctx);
177+
}
178+
},
179+
);
180+
181+
scene.command(
182+
'counterpartyorders',
183+
resetMessage,
184+
async (ctx: CommunityContext) => {
185+
try {
186+
await ctx.deleteMessage();
187+
const state = ctx.scene.state as CommunityWizardState;
188+
if (ctx.message === undefined || !('text' in ctx.message))
189+
throw new Error('ctx.message is undefined');
190+
const [, orders] = ctx.message.text.trim().split(' ');
191+
const min_orders = parseInt(orders);
192+
if (isNaN(min_orders) || min_orders < 0)
193+
throw new Error('NotValidNumber');
194+
const maxOrders = parseInt(
195+
process.env.MAX_COUNTERPARTY_ORDERS_REQUIREMENT || '10',
196+
);
197+
if (min_orders > maxOrders) {
198+
state.error = {
199+
i18n: 'invalid_range',
200+
command: '/counterpartyorders',
201+
max: maxOrders,
202+
};
203+
return await updateMessage(ctx);
204+
}
205+
const user = state.user;
206+
if (!user.counterparty_requirements) {
207+
user.counterparty_requirements = {
208+
min_days_using_bot: 0,
209+
min_completed_orders: 0,
210+
};
211+
}
212+
user.counterparty_requirements.min_completed_orders = min_orders;
213+
await user.save();
214+
state.feedback = {
215+
i18n: 'counterpartyorders_updated',
216+
orders: min_orders,
217+
};
218+
await updateMessage(ctx);
219+
} catch (err) {
220+
logger.error(err);
221+
(ctx.scene.state as CommunityWizardState).error = {
222+
i18n: 'invalid_number',
223+
};
224+
await updateMessage(ctx);
225+
}
226+
},
227+
);
228+
229+
scene.command(
230+
'resetrequirements',
231+
resetMessage,
232+
async (ctx: CommunityContext) => {
233+
try {
234+
await ctx.deleteMessage();
235+
const state = ctx.scene.state as CommunityWizardState;
236+
const user = state.user;
237+
user.counterparty_requirements = {
238+
min_days_using_bot: 0,
239+
min_completed_orders: 0,
240+
};
241+
await user.save();
242+
state.feedback = { i18n: 'requirements_reset' };
243+
await updateMessage(ctx);
244+
} catch (err) {
245+
logger.error(err);
246+
(ctx.scene.state as CommunityWizardState).error = {
247+
i18n: 'generic_error',
248+
};
249+
await updateMessage(ctx);
250+
}
251+
},
252+
);
253+
132254
return scene;
133255
}
134256

locales/de.yaml

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ help: |
251251
/setaddress <_lightning Adresse / off_> - Ermöglicht es dem Käufer, eine statische Zahlungsadresse (Lightning-Adresse) einzurichten, _off_ zum Deaktivieren
252252
/setlang - Ermöglicht dem Benutzer, die Sprache zu ändern
253253
/settings - Zeigt die aktuellen Einstellungen des Benutzers an
254+
Innerhalb von /settings kannst du Gegenpartei-Anforderungen konfigurieren:
255+
/counterpartyage &lt;Tage&gt; - Legt das Mindestalter (in Tagen) für die Gegenpartei fest
256+
/counterpartyorders &lt;Aufträge&gt; - Legt die Mindestanzahl abgeschlossener Aufträge für die Gegenpartei fest
257+
/resetrequirements - Setzt die Anforderungen auf Standardwerte zurück
254258
/listorders - Benutze diesen Befehl, um alle deine ausstehenden Transaktionen aufzulisten
255259
/listcurrencies - Listet alle FIAT Währungen auf, die wir verwenden können
256260
/fiatsent <_order id_> - Der Käufer teilt mit, dass er dem Verkäufer das FIAT-Geld geschickt hat
@@ -629,20 +633,33 @@ order_not_found: Bestellung nicht gefunden.
629633

630634
# START modules/user
631635
user_settings: |
632-
<strong>User settings for @${user.username}</strong>
636+
<strong>Benutzereinstellungen für @${user.username}</strong>
633637
634-
Language:
638+
Sprache:
635639
${language.emoji} ${language.name}
636-
Community:
640+
Gemeinschaft:
637641
${community || '🛇'}
638642
npub:
639643
<code>${npub || '🛇'}</code>
640-
lightning address:
644+
Lightning-Adresse:
641645
<code>${lightning_address || '🛇'}</code>
642646
643-
<strong># HELP</strong>
644-
/setnpub &lt;npub&gt; - Configure user's Nostr public key.
645-
/exit - to exit the wizard.
647+
<strong>Gegenpartei-Anforderungen</strong>
648+
Mindestalter: ${min_days_using_bot} Tage
649+
Mindestanzahl abgeschlossener Aufträge: ${min_completed_orders}
650+
651+
<strong># HILFE</strong>
652+
/setnpub &lt;npub&gt; - Nostr-Schlüssel des Benutzers konfigurieren.
653+
/counterpartyage &lt;Tage&gt; - Legt das Mindestalter (in Tagen) für die Gegenpartei fest.
654+
/counterpartyorders &lt;Aufträge&gt; - Legt die Mindestanzahl abgeschlossener Aufträge fest.
655+
/resetrequirements - Setzt die Gegenpartei-Anforderungen auf Standardwerte zurück.
656+
/exit - um den Assistenten zu verlassen.
657+
not_meeting_requirements: Du erfüllst nicht die Anforderungen der Gegenpartei, um diesen Auftrag anzunehmen.
658+
counterpartyage_updated: Altersanforderung der Gegenpartei auf ${days} Tage aktualisiert.
659+
counterpartyorders_updated: Auftragsanforderung der Gegenpartei auf ${orders} aktualisiert.
660+
requirements_reset: Gegenpartei-Anforderungen wurden auf Standardwerte zurückgesetzt.
661+
invalid_number: Ungültige Zahl.
662+
invalid_range: Ungültiger Wert für ${command}, bitte wähle eine Zahl im Bereich [0 - ${max}].
646663
# END modules/user
647664
# check hold invoice
648665
invoice_settled: Rechnung wurde bereits niedergelassen

locales/en.yaml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,11 @@ help: |
254254
/setaddress <_lightning address / off_> - Allows the buyer to establish a static payment address (lightning address), _off_ to deactivate
255255
/setlang - Allows the user to change the language
256256
/settings - Displays the user's current settings
257-
/listorders - Use this command to list all your pending transactions
257+
While in /settings you can configure counterparty requirements:
258+
/counterpartyage &lt;days&gt; - Sets the minimum account age (in days) required for the counterparty to take your orders
259+
/counterpartyorders &lt;orders&gt; - Sets the minimum number of completed orders required for the counterparty
260+
/resetrequirements - Resets counterparty requirements to default
261+
/listorders - Use this command to list all your pending transactions
258262
/listcurrencies - Lists all the currencies we can use to without indicatin the amount in sats.
259263
/fiatsent <_order id_> - Buyer informs that he has already sent FIAT money to seller
260264
/release <_order id_> - Seller releases satoshis
@@ -646,9 +650,22 @@ user_settings: |
646650
lightning address:
647651
<code>${lightning_address || '🛇'}</code>
648652
653+
<strong>Counterparty requirements</strong>
654+
Min. account age: ${min_days_using_bot} days
655+
Min. completed orders: ${min_completed_orders}
656+
649657
<strong># HELP</strong>
650658
/setnpub &lt;npub&gt; - Configure user's Nostr public key.
659+
/counterpartyage &lt;days&gt; - Sets the minimum account age (in days) for the counterparty.
660+
/counterpartyorders &lt;orders&gt; - Sets the minimum number of completed orders for the counterparty.
661+
/resetrequirements - Resets counterparty requirements to default.
651662
/exit - to exit the wizard.
663+
not_meeting_requirements: You do not meet the counterparty requirements to take this order.
664+
counterpartyage_updated: Counterparty age requirement updated to ${days} days.
665+
counterpartyorders_updated: Counterparty completed orders requirement updated to ${orders}.
666+
requirements_reset: Counterparty requirements have been reset to default.
667+
invalid_number: Invalid number.
668+
invalid_range: Invalid value for ${command}, please choose a number in the range [0 - ${max}].
652669
# END modules/user
653670
# check hold invoice
654671
invoice_settled: Invoice already settled

locales/es.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ help: |
251251
/setaddress <_lightning address / off_> - Permite al comprador indicar una dirección de pago estática (lightning address), _off_ para desactivarla
252252
/setlang - Le permite al usuario cambiar el idioma
253253
/settings - Muestra la configuración actual del usuario
254+
Dentro de /settings podés configurar los requisitos de contraparte:
255+
/counterpartyage &lt;días&gt; - Establece la antigüedad mínima (en días) requerida para la contraparte
256+
/counterpartyorders &lt;órdenes&gt; - Establece el mínimo de órdenes completadas requeridas para la contraparte
257+
/resetrequirements - Restablece los requisitos al valor por defecto
254258
/listorders - El usuario puede listar sus órdenes no finalizadas
255259
/listcurrencies - Lista las monedas que podemos utilizar sin indicar el monto en satoshis
256260
/fiatsent <_id orden_> - El comprador indica que ya ha enviado el dinero Fiat al vendedor
@@ -642,9 +646,22 @@ user_settings: |
642646
lightning address:
643647
<code>${lightning_address || '🛇'}</code>
644648
649+
<strong>Requisitos de contraparte</strong>
650+
Antigüedad mínima: ${min_days_using_bot} días
651+
Órdenes mínimas completadas: ${min_completed_orders}
652+
645653
<strong># AYUDA</strong>
646654
/setnpub &lt;npub&gt; - Configura la llave pública de Nostr.
655+
/counterpartyage &lt;días&gt; - Establece la antigüedad mínima (en días) para la contraparte.
656+
/counterpartyorders &lt;órdenes&gt; - Establece el mínimo de órdenes completadas para la contraparte.
657+
/resetrequirements - Restablece los requisitos de contraparte al valor por defecto.
647658
/exit - para salir del asistente.
659+
not_meeting_requirements: No cumplís los requisitos de la contraparte para tomar esta orden.
660+
counterpartyage_updated: Requisito de antigüedad de contraparte actualizado a ${days} días.
661+
counterpartyorders_updated: Requisito de órdenes completadas de contraparte actualizado a ${orders}.
662+
requirements_reset: Los requisitos de contraparte han sido restablecidos al valor por defecto.
663+
invalid_number: Número inválido.
664+
invalid_range: Valor inválido para ${command}, por favor elegí un número en el rango [0 - ${max}].
648665
# END modules/user
649666
# check hold invoice
650667
invoice_settled: Factura ya cobrada

0 commit comments

Comments
 (0)