From 4150ea5c7feb08c281c97906ca8b141c3795ca6a Mon Sep 17 00:00:00 2001 From: Mehrshad Date: Wed, 4 Oct 2023 16:52:56 +0330 Subject: [PATCH 01/13] util/index: convert js to ts The languageModel & fiatModel interface created to type-check the data and ensure that it conforms to the expected structure and format. This can help you avoid errors and bugs when working with the data in your code. This commit enables resolveJsonModule to import module with '.json' extension. Co-authored-by: webwarrior --- tsconfig.json | 1 + util/fiatModel.ts | 4 + util/{index.js => index.ts} | 201 +++++++++++++++++++++--------------- util/languagesModel.ts | 9 ++ 4 files changed, 133 insertions(+), 82 deletions(-) rename util/{index.js => index.ts} (70%) create mode 100644 util/languagesModel.ts diff --git a/tsconfig.json b/tsconfig.json index f2c464b6..1e13115d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,5 +2,6 @@ "compilerOptions": { "strict": true, "esModuleInterop": true, + "resolveJsonModule": true } } diff --git a/util/fiatModel.ts b/util/fiatModel.ts index a722adc0..cc8d22e8 100644 --- a/util/fiatModel.ts +++ b/util/fiatModel.ts @@ -10,3 +10,7 @@ export interface IFiat { price?: boolean; locale?: string; } + +export interface IFiatCurrencies { + [key: string]: IFiat; +} diff --git a/util/index.js b/util/index.ts similarity index 70% rename from util/index.js rename to util/index.ts index 6128d311..1697643e 100644 --- a/util/index.js +++ b/util/index.ts @@ -1,20 +1,29 @@ -const axios = require('axios'); +import { I18nContext } from "@grammyjs/i18n"; +import { ICommunity, IOrderChannel } from "../models/community"; +import { IOrder } from "../models/order"; +import { UserDocument } from "../models/user"; +import { IFiatCurrencies, IFiat } from "./fiatModel"; +import { ILanguage, ILanguages } from "./languagesModel"; +import { Telegram } from "telegraf"; +import axios from "axios"; +import fiatJson from './fiat.json'; +import languagesJson from './languages.json'; +import { Order, Community } from "../models"; +import { logger } from "../logger"; const { I18n } = require('@grammyjs/i18n'); -const currencies = require('./fiat.json'); // ISO 639-1 language codes -const languages = require('./languages.json'); -const { Order, Community } = require('../models'); -const { logger } = require('../logger'); -// ISO 4217, all ISO currency codes are 3 letters but users can trade shitcoins +const languages: ILanguages = languagesJson; +const currencies: IFiatCurrencies = fiatJson; -const isIso4217 = code => { +// ISO 4217, all ISO currency codes are 3 letters but users can trade shitcoins +const isIso4217 = (code: string): boolean => { if (code.length < 3 || code.length > 5) { return false; } const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); - code = code.toLowerCase().split(''); - return code.every(letter => { + code = code.toLowerCase() + return code.split('').every(letter => { if (alphabet.indexOf(letter) == -1) { return false; } @@ -22,19 +31,16 @@ const isIso4217 = code => { }); }; -exports.isIso4217 = isIso4217; - -const getCurrency = code => { - if (!isIso4217(code)) return false; +const getCurrency = (code: string): (IFiat | null) => { + if (!isIso4217(code)) return null; const currency = currencies[code]; - if (!currency) return false; + if (!currency) return null; return currency; }; -exports.getCurrency = getCurrency; +const plural = (n: number): string => { -const plural = n => { if (n === 1) { return ''; } @@ -45,8 +51,7 @@ exports.plural = plural; // This function formats a number to locale strings. // If Iso code or locale code doesn´t exist, the function will return a number without format. - -exports.numberFormat = (code, number) => { +const numberFormat = (code: string, number: number) => { if (!isIso4217(code)) return false; if (!currencies[code]) return number; @@ -62,8 +67,7 @@ exports.numberFormat = (code, number) => { // This function checks if the current buyer and seller were doing circular operations // In order to increase their trades_completed and volume_traded. // If we found those trades in the last 24 hours we decrease both variables to both users - -exports.handleReputationItems = async (buyer, seller, amount) => { +const handleReputationItems = async (buyer: UserDocument, seller: UserDocument, amount: number) => { try { const yesterday = new Date(Date.now() - 86400000).toISOString(); const orders = await Order.find({ @@ -74,7 +78,7 @@ exports.handleReputationItems = async (buyer, seller, amount) => { }); if (orders.length > 0) { let totalAmount = 0; - orders.forEach(order => { + orders.forEach((order: IOrder) => { totalAmount += order.amount; }); const lastAmount = orders[orders.length - 1].amount; @@ -128,9 +132,10 @@ exports.handleReputationItems = async (buyer, seller, amount) => { } }; -exports.getBtcFiatPrice = async (fiatCode, fiatAmount) => { +const getBtcFiatPrice = async (fiatCode: string, fiatAmount: number) => { try { const currency = getCurrency(fiatCode); + if (currency === null) throw Error("Currency not found"); if (!currency.price) return; // Before hit the endpoint we make sure the code have only 3 chars const code = currency.code.substring(0, 3); @@ -140,13 +145,13 @@ exports.getBtcFiatPrice = async (fiatCode, fiatAmount) => { } const sats = (fiatAmount / response.data.btc) * 100000000; - return parseInt(sats); + return Number(sats); } catch (error) { logger.error(error); } }; -exports.getBtcExchangePrice = (fiatAmount, satsAmount) => { +const getBtcExchangePrice = (fiatAmount: number, satsAmount: number) => { try { const satsPerBtc = 1e8; const feeRate = (satsPerBtc * fiatAmount) / satsAmount; @@ -157,8 +162,8 @@ exports.getBtcExchangePrice = (fiatAmount, satsAmount) => { } }; -const objectToArray = object => { - const array = []; +const objectToArray = (object: any): any[] => { + const array: any[] = []; for (const i in object) array.push(object[i]); @@ -167,20 +172,20 @@ const objectToArray = object => { exports.objectToArray = objectToArray; -exports.getCurrenciesWithPrice = () => { +const getCurrenciesWithPrice = () => { const currenciesArr = objectToArray(currencies); const withPrice = currenciesArr.filter(currency => currency.price); return withPrice; }; -exports.toKebabCase = string => +const toKebabCase = (string: string) => string .replace(/([a-z])([A-Z])/g, '$1-$2') .replace(/[\s_]+/g, '-') .toLowerCase(); -const getEmojiRate = rate => { +const getEmojiRate = (rate: number) => { const star = '⭐'; const roundedRate = Math.round(rate); const output = []; @@ -189,12 +194,9 @@ const getEmojiRate = rate => { return output.join(''); }; -exports.getEmojiRate = getEmojiRate; - // Round number to exp decimal digits // Source: https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Math/round#redondeo_decimal - -const decimalRound = (value, exp) => { +const decimalRound = (value: number, exp: number): number => { if (typeof exp === 'undefined' || +exp === 0) { return Math.round(value); } @@ -205,32 +207,29 @@ const decimalRound = (value, exp) => { return NaN; } // Shift - value = value.toString().split('e'); - value = Math.round(+(value[0] + 'e' + (value[1] ? +value[1] - exp : -exp))); + let valueArr = value.toString().split('e'); + value = Math.round(+(valueArr[0] + 'e' + (valueArr[1] ? +valueArr[1] - exp : -exp))); // Shift back - value = value.toString().split('e'); - return +(value[0] + 'e' + (value[1] ? +value[1] + exp : exp)); + valueArr = value.toString().split('e'); + return +(valueArr[0] + 'e' + (valueArr[1] ? +valueArr[1] + exp : exp)); }; -exports.decimalRound = decimalRound; - -exports.extractId = text => { +const extractId = (text: string): (string | null) => { const matches = text.match(/:([a-f0-9]{24}):$/); - - return matches[1]; + if (matches !== null){ + return matches?.[1]; + } + return null; }; // Clean strings that are going to be rendered with markdown - -const sanitizeMD = text => { +const sanitizeMD = (text: any) => { if (!text) return ''; - return text.toString().replace(/(?=[|<>(){}[\]\-_!#.`=+])/g, '\\'); + return String(text).replace(/(?=[|<>(){}[\]\-_!#.`=+])/g, '\\'); }; -exports.sanitizeMD = sanitizeMD; - -exports.secondsToTime = secs => { +const secondsToTime = (secs: number) => { const hours = Math.floor(secs / (60 * 60)); const divisor = secs % (60 * 60); @@ -242,9 +241,9 @@ exports.secondsToTime = secs => { }; }; -exports.isGroupAdmin = async (groupId, user, telegram) => { +const isGroupAdmin = async (groupId: string, user: UserDocument, telegram: Telegram) => { try { - const member = await telegram.getChatMember(groupId, user.tg_id); + const member = await telegram.getChatMember(groupId, Number(user.tg_id)); if ( member && (member.status === 'creator' || member.status === 'administrator') @@ -268,12 +267,12 @@ exports.isGroupAdmin = async (groupId, user, telegram) => { logger.error(error); return { success: false, - message: error.toString(), + message: String(error), }; } }; -exports.deleteOrderFromChannel = async (order, telegram) => { +const deleteOrderFromChannel = async (order: IOrder, telegram: Telegram) => { try { let channel = process.env.CHANNEL; if (order.community_id) { @@ -291,13 +290,13 @@ exports.deleteOrderFromChannel = async (order, telegram) => { } } } - await telegram.deleteMessage(channel, order.tg_channel_message1); + await telegram.deleteMessage(channel!, Number(order.tg_channel_message1!)); } catch (error) { logger.error(error); } }; -exports.getOrderChannel = async order => { +const getOrderChannel = async (order: IOrder) => { let channel = process.env.CHANNEL; if (order.community_id) { const community = await Community.findOne({ _id: order.community_id }); @@ -307,7 +306,7 @@ exports.getOrderChannel = async order => { if (community.order_channels.length === 1) { channel = community.order_channels[0].name; } else { - community.order_channels.forEach(async c => { + community.order_channels.forEach(async (c: IOrderChannel) => { if (c.type === order.type) { channel = c.name; } @@ -318,10 +317,11 @@ exports.getOrderChannel = async order => { return channel; }; -exports.getDisputeChannel = async order => { +const getDisputeChannel = async (order: IOrder) => { let channel = process.env.DISPUTE_CHANNEL; if (order.community_id) { const community = await Community.findOne({ _id: order.community_id }); + if (community === null) throw Error("Community was not found in DB"); channel = community.dispute_channel; } @@ -333,8 +333,13 @@ exports.getDisputeChannel = async order => { * @param {*} user * @returns i18n context */ -exports.getUserI18nContext = async user => { - const language = user.language || 'en'; +const getUserI18nContext = async (user: UserDocument) => { + let language = null; + if (!('language' in user)) { + language = 'en'; + } else { + language = user.language; + } const i18n = new I18n({ locale: language, defaultLanguageOnMissing: true, @@ -344,7 +349,7 @@ exports.getUserI18nContext = async user => { return i18n.createContext(user.lang); }; -exports.getDetailedOrder = (i18n, order, buyer, seller) => { +const getDetailedOrder = (i18n: I18nContext, order: IOrder, buyer: UserDocument, seller: UserDocument) => { try { const buyerUsername = buyer ? sanitizeMD(buyer.username) : ''; const buyerReputation = buyer @@ -363,7 +368,7 @@ exports.getDetailedOrder = (i18n, order, buyer, seller) => { takenAt = sanitizeMD(takenAt); const previousDisputeStatus = sanitizeMD(order.previous_dispute_status); const status = sanitizeMD(order.status); - const fee = order.fee ? parseInt(order.fee) : ''; + const fee = order.fee ? Number(order.fee) : ''; const creator = order.creator_id === buyerId ? buyerUsername : sellerUsername; const buyerAge = buyer? getUserAge(buyer) : ''; @@ -397,7 +402,7 @@ exports.getDetailedOrder = (i18n, order, buyer, seller) => { }; // We need to know if this user is a dispute solver for this community -exports.isDisputeSolver = (community, user) => { +const isDisputeSolver = (community: ICommunity, user: UserDocument) => { if (!community || !user) { return false; } @@ -407,19 +412,20 @@ exports.isDisputeSolver = (community, user) => { // Return the fee the bot will charge to the seller // this fee is a combination from the global bot fee and the community fee -exports.getFee = async (amount, communityId) => { - const maxFee = Math.round(amount * parseFloat(process.env.MAX_FEE)); +const getFee = async (amount: number, communityId: string) => { + const maxFee = Math.round(amount * Number(process.env.MAX_FEE)); if (!communityId) return maxFee; - const botFee = maxFee * parseFloat(process.env.FEE_PERCENT); + const botFee = maxFee * Number(process.env.FEE_PERCENT); let communityFee = Math.round(maxFee - botFee); const community = await Community.findOne({ _id: communityId }); + if (community === null) throw Error("Community was not found in DB"); communityFee = communityFee * (community.fee / 100); return botFee + communityFee; }; -exports.itemsFromMessage = str => { +const itemsFromMessage = (str: string) => { return str .split(' ') .map(e => e.trim()) @@ -427,29 +433,29 @@ exports.itemsFromMessage = str => { }; // Check if a number is int -const isInt = n => parseInt(n) === n; +const isInt = (n: number) => Number(n) === n; exports.isInt = isInt; // Check if a number is float -exports.isFloat = n => typeof n === 'number' && !isInt(n); +const isFloat = (n: number) => typeof n === 'number' && !Number.isInteger(n); // Returns an emoji flag for a language -exports.getLanguageFlag = code => { +const getLanguageFlag = (code: string): ILanguage => { return languages[code]; }; -exports.delay = time => { +const delay = (time: number) => { return new Promise(resolve => setTimeout(resolve, time)); }; // Returns the hold invoice expiration time in seconds, // and the hold invoice safety window in seconds -exports.holdInvoiceExpirationInSecs = () => { +const holdInvoiceExpirationInSecs = () => { const expirationTimeInSecs = - parseInt(process.env.HOLD_INVOICE_CLTV_DELTA) * 10 * 60; + Number(process.env.HOLD_INVOICE_CLTV_DELTA) * 10 * 60; const safetyWindowInSecs = - parseInt(process.env.HOLD_INVOICE_CLTV_DELTA_SAFETY_WINDOW) * 10 * 60; + Number(process.env.HOLD_INVOICE_CLTV_DELTA_SAFETY_WINDOW) * 10 * 60; return { expirationTimeInSecs, safetyWindowInSecs, @@ -457,7 +463,7 @@ exports.holdInvoiceExpirationInSecs = () => { }; // Returns the user age in days -const getUserAge = user => { +const getUserAge = (user: UserDocument) => { const userCreationDate = new Date(user.created_at); const today = new Date(); const ageInDays = Math.floor( @@ -466,21 +472,19 @@ const getUserAge = user => { return ageInDays; }; -exports.getUserAge = getUserAge; - /** * Returns order expiration time text * @param {*} order order object * @param {*} i18n context * @returns String with the remaining time to expiration in format '1 hours 30 minutes' */ -exports.getTimeToExpirationOrder = (order, i18n) => { +const getTimeToExpirationOrder = (order: IOrder, i18n: I18nContext) => { const initialDateObj = new Date(order.created_at); - const timeToExpire = parseInt(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW); + const timeToExpire = Number(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW); initialDateObj.setSeconds(initialDateObj.getSeconds() + timeToExpire); const currentDateObj = new Date(); - const timeDifferenceMs = initialDateObj - currentDateObj; + const timeDifferenceMs = initialDateObj.valueOf() - currentDateObj.valueOf(); const totalSecondsRemaining = Math.floor(timeDifferenceMs / 1000); const textHour = i18n.t('hours'); const textMin = i18n.t('minutes'); @@ -494,18 +498,18 @@ exports.getTimeToExpirationOrder = (order, i18n) => { return `${hours} ${textHour} ${minutes} ${textMin}`; }; -exports.getStars = (rate, totalReviews) => { +export const getStars = (rate: number, totalReviews: number) => { const stars = getEmojiRate(rate); const roundedRating = decimalRound(rate, -1); return `${roundedRating} ${stars} (${totalReviews})`; }; -exports.removeAtSymbol = text => { +export const removeAtSymbol = (text: string) => { return text[0] === '@' ? text.slice(1) : text; -}; +} -exports.removeLightningPrefix = invoice => { +export const removeLightningPrefix = (invoice: string) => { const prefix = 'lightning:'; // Check if the invoice starts with the prefix @@ -516,3 +520,36 @@ exports.removeLightningPrefix = invoice => { // Return the invoice as is if no prefix is found return invoice; }; + +export { + isIso4217, + plural, + getCurrency, + handleReputationItems, + getBtcFiatPrice, + getBtcExchangePrice, + getCurrenciesWithPrice, + getEmojiRate, + decimalRound, + extractId, + sanitizeMD, + secondsToTime, + isGroupAdmin, + deleteOrderFromChannel, + getOrderChannel, + getUserI18nContext, + numberFormat, + getDisputeChannel, + getDetailedOrder, + isDisputeSolver, + getFee, + itemsFromMessage, + isInt, + isFloat, + getLanguageFlag, + delay, + holdInvoiceExpirationInSecs, + getUserAge, + getTimeToExpirationOrder, + toKebabCase +}; diff --git a/util/languagesModel.ts b/util/languagesModel.ts new file mode 100644 index 00000000..fa159815 --- /dev/null +++ b/util/languagesModel.ts @@ -0,0 +1,9 @@ +export interface ILanguage { + name: string; + emoji: string; + code: string; +} + +export interface ILanguages { + [key: string]: ILanguage; +} From 5d3aa28781428580cab3ed8394ed280982f2abfe Mon Sep 17 00:00:00 2001 From: Mehrshad Date: Mon, 11 Sep 2023 14:34:14 +0330 Subject: [PATCH 02/13] jobs: convert js to ts The --downlevelIteration flag is a TypeScript compiler option that enables support for iterating over new concepts like Map, Set, or Generator in older JavaScript runtimes. By default, TypeScript targets ES3, which does not support these features. If you use a for...of loop or a spread operator on an iterable object, you may get an error. Use Date instead of Date.toISOString cause paid_at has type Date and during the conversion from js to ts, we got compilation errors. Co-authored-by: webwarrior --- ...ngs.js => calculate_community_earnings.ts} | 13 ++++----- jobs/{cancel_orders.js => cancel_orders.ts} | 19 +++++++------ jobs/{communities.js => communities.ts} | 15 ++++++----- ...d_orders.js => delete_published_orders.ts} | 15 ++++++----- jobs/index.js | 19 ------------- jobs/index.ts | 19 +++++++++++++ jobs/{node_info.js => node_info.ts} | 13 +++++---- jobs/pending_payments.ts | 27 ++++++++++++------- tsconfig.json | 3 ++- 9 files changed, 82 insertions(+), 61 deletions(-) rename jobs/{calculate_community_earnings.js => calculate_community_earnings.ts} (77%) rename jobs/{cancel_orders.js => cancel_orders.ts} (86%) rename jobs/{communities.js => communities.ts} (70%) rename jobs/{delete_published_orders.js => delete_published_orders.ts} (73%) delete mode 100644 jobs/index.js create mode 100644 jobs/index.ts rename jobs/{node_info.js => node_info.ts} (62%) diff --git a/jobs/calculate_community_earnings.js b/jobs/calculate_community_earnings.ts similarity index 77% rename from jobs/calculate_community_earnings.js rename to jobs/calculate_community_earnings.ts index 87eb3e28..3ccc7b39 100644 --- a/jobs/calculate_community_earnings.js +++ b/jobs/calculate_community_earnings.ts @@ -1,5 +1,5 @@ -const { Order, Community } = require('../models'); -const { logger } = require('../logger'); +import { Order, Community } from '../models'; +import { logger } from "../logger"; const calculateEarnings = async () => { try { @@ -12,9 +12,9 @@ const calculateEarnings = async () => { for (const order of orders) { const amount = order.amount; const fee = order.fee; - const botFee = order.bot_fee || parseFloat(process.env.MAX_FEE); + const botFee = order.bot_fee || Number(process.env.MAX_FEE); const communityFeePercent = - order.community_fee || parseFloat(process.env.FEE_PERCENT); + order.community_fee || Number(process.env.FEE_PERCENT); const maxFee = amount * botFee; const communityFee = fee - maxFee * communityFeePercent; const earnings = earningsMap.get(order.community_id) || [0, 0]; @@ -27,6 +27,7 @@ const calculateEarnings = async () => { } for (const [communityId, earnings] of earningsMap) { const community = await Community.findById(communityId); + if (community === null) throw Error("Community was not found in DB"); const amount = Math.round(earnings[0]); community.earnings = community.earnings + amount; community.orders_to_redeem = community.orders_to_redeem + earnings[1]; @@ -36,9 +37,9 @@ const calculateEarnings = async () => { ); } } catch (error) { - const message = error.toString(); + const message = String(error); logger.error(`calculateEarnings catch error: ${message}`); } }; -module.exports = calculateEarnings; +export default calculateEarnings; diff --git a/jobs/cancel_orders.js b/jobs/cancel_orders.ts similarity index 86% rename from jobs/cancel_orders.js rename to jobs/cancel_orders.ts index 4cce5064..686b3dec 100644 --- a/jobs/cancel_orders.js +++ b/jobs/cancel_orders.ts @@ -1,16 +1,18 @@ -const { User, Order } = require('../models'); +import { Telegraf } from "telegraf"; +import { MainContext } from "../bot/start"; +import { User, Order } from "../models"; const { cancelShowHoldInvoice, cancelAddInvoice } = require('../bot/commands'); -const messages = require('../bot/messages'); -const { getUserI18nContext, holdInvoiceExpirationInSecs } = require('../util'); -const { logger } = require('../logger'); +import * as messages from "../bot/messages"; +import { getUserI18nContext, holdInvoiceExpirationInSecs } from '../util'; +import { logger } from "../logger"; const OrderEvents = require('../bot/modules/events/orders'); -const cancelOrders = async bot => { +const cancelOrders = async (bot: Telegraf) => { try { const holdInvoiceTime = new Date(); holdInvoiceTime.setSeconds( holdInvoiceTime.getSeconds() - - parseInt(process.env.HOLD_INVOICE_EXPIRATION_WINDOW) + Number(process.env.HOLD_INVOICE_EXPIRATION_WINDOW) ); // We get the orders where the seller didn't pay the hold invoice before expired // or where the buyer didn't add the invoice @@ -54,6 +56,7 @@ const cancelOrders = async bot => { for (const order of activeOrders) { const buyerUser = await User.findOne({ _id: order.buyer_id }); const sellerUser = await User.findOne({ _id: order.seller_id }); + if (buyerUser === null || sellerUser === null) return; const i18nCtxBuyer = await getUserI18nContext(buyerUser); const i18nCtxSeller = await getUserI18nContext(sellerUser); // Instead of cancel this order we should send this to the admins @@ -79,7 +82,7 @@ const cancelOrders = async bot => { // Now we cancel orders expired // ============================== orderTime = new Date(); - let orderExpirationTime = parseInt( + let orderExpirationTime = Number( process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW ); orderExpirationTime = orderExpirationTime + orderExpirationTime * 0.2; @@ -106,4 +109,4 @@ const cancelOrders = async bot => { } }; -module.exports = cancelOrders; +export default cancelOrders; diff --git a/jobs/communities.js b/jobs/communities.ts similarity index 70% rename from jobs/communities.js rename to jobs/communities.ts index 4d28409e..a3599506 100644 --- a/jobs/communities.js +++ b/jobs/communities.ts @@ -1,12 +1,15 @@ -const { Order, Community } = require('../models'); -const { logger } = require('../logger'); +import { Telegraf } from "telegraf"; +import { MainContext } from "../bot/start"; -const deleteCommunity = async bot => { +import { Order, Community } from '../models'; +import { logger } from "../logger"; + +const deleteCommunity = async (bot: Telegraf) => { try { const communities = await Community.find(); for (const community of communities) { // Delete communities with COMMUNITY_TTL days without a successful order - const days = 86400 * parseInt(process.env.COMMUNITY_TTL); + const days = 86400 * Number(process.env.COMMUNITY_TTL); const time = new Date(); time.setSeconds(time.getSeconds() - days); // If is a new community we don't do anything @@ -26,9 +29,9 @@ const deleteCommunity = async bot => { } } } catch (error) { - const message = error.toString(); + const message = String(error); logger.error(`deleteCommunity catch error: ${message}`); } }; -module.exports = deleteCommunity; +export default deleteCommunity; diff --git a/jobs/delete_published_orders.js b/jobs/delete_published_orders.ts similarity index 73% rename from jobs/delete_published_orders.js rename to jobs/delete_published_orders.ts index bec2af2f..4182840d 100644 --- a/jobs/delete_published_orders.js +++ b/jobs/delete_published_orders.ts @@ -1,13 +1,16 @@ -const { Order } = require('../models'); +import { Telegraf } from "telegraf"; +import { MainContext } from "../bot/start"; + +import { Order } from '../models'; const { deleteOrderFromChannel } = require('../util'); -const { logger } = require('../logger'); +import { logger } from '../logger'; -const deleteOrders = async bot => { +const deleteOrders = async (bot: Telegraf) => { try { const windowTime = new Date(); windowTime.setSeconds( windowTime.getSeconds() - - parseInt(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW) + Number(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW) ); // We get the pending orders where time is expired const pendingOrders = await Order.find({ @@ -25,9 +28,9 @@ const deleteOrders = async bot => { await deleteOrderFromChannel(orderCloned, bot.telegram); } } catch (error) { - const message = error.toString(); + const message = String(error); logger.error(`deleteOrders catch error: ${message}`); } }; -module.exports = deleteOrders; +export default deleteOrders; diff --git a/jobs/index.js b/jobs/index.js deleted file mode 100644 index b784f178..00000000 --- a/jobs/index.js +++ /dev/null @@ -1,19 +0,0 @@ -const { - attemptPendingPayments, - attemptCommunitiesPendingPayments, -} = require('./pending_payments'); -const cancelOrders = require('./cancel_orders'); -const deleteOrders = require('./delete_published_orders'); -const calculateEarnings = require('./calculate_community_earnings'); -const deleteCommunity = require('./communities'); -const nodeInfo = require('./node_info'); - -module.exports = { - attemptPendingPayments, - cancelOrders, - deleteOrders, - calculateEarnings, - attemptCommunitiesPendingPayments, - deleteCommunity, - nodeInfo, -}; diff --git a/jobs/index.ts b/jobs/index.ts new file mode 100644 index 00000000..281f16f6 --- /dev/null +++ b/jobs/index.ts @@ -0,0 +1,19 @@ +import { + attemptPendingPayments, + attemptCommunitiesPendingPayments, +} from "./pending_payments"; +import cancelOrders from "./cancel_orders"; +import deleteOrders from "./delete_published_orders"; +import calculateEarnings from './calculate_community_earnings' +import deleteCommunity from './communities' +import nodeInfo from './node_info' + +export { + attemptPendingPayments, + cancelOrders, + deleteOrders, + calculateEarnings, + attemptCommunitiesPendingPayments, + deleteCommunity, + nodeInfo, +}; diff --git a/jobs/node_info.js b/jobs/node_info.ts similarity index 62% rename from jobs/node_info.js rename to jobs/node_info.ts index 94c994d0..22e82eb3 100644 --- a/jobs/node_info.js +++ b/jobs/node_info.ts @@ -1,11 +1,14 @@ -const { Config } = require('../models'); +import { Telegraf } from "telegraf"; +import { MainContext } from "../bot/start"; + +import { Config } from '../models'; const { getInfo } = require('../ln'); const { logger } = require('../logger'); -const info = async bot => { +const info = async (bot: Telegraf) => { try { let config = await Config.findOne({}); - if (!config) { + if (config === null) { config = new Config(); } const info = await getInfo(); @@ -15,9 +18,9 @@ const info = async bot => { config.node_uri = info.uris[0]; await config.save(); } catch (error) { - const message = error.toString(); + const message = String(error); logger.error(`node info catch error: ${message}`); } }; -module.exports = info; +export default info; diff --git a/jobs/pending_payments.ts b/jobs/pending_payments.ts index 90bdfff7..b3b9a091 100644 --- a/jobs/pending_payments.ts +++ b/jobs/pending_payments.ts @@ -1,14 +1,14 @@ -const { payRequest, isPendingPayment } = require('../ln'); -const { PendingPayment, Order, User, Community } = require('../models'); +import { PendingPayment, Order, User, Community } from '../models'; import * as messages from '../bot/messages'; -const { getUserI18nContext } = require('../util'); -const { logger } = require('../logger'); +import { logger } from "../logger"; import { Telegraf } from 'telegraf'; import { I18nContext } from '@grammyjs/i18n'; import { MainContext } from '../bot/start'; +const { payRequest, isPendingPayment } = require('../ln'); +import { getUserI18nContext } from '../util'; const { orderUpdated } = require('../bot/modules/events/orders'); -exports.attemptPendingPayments = async (bot: Telegraf): Promise => { +export const attemptPendingPayments = async (bot: Telegraf): Promise => { const pendingPayments = await PendingPayment.find({ paid: false, attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, @@ -18,6 +18,7 @@ exports.attemptPendingPayments = async (bot: Telegraf): Promise): Promise): Promise): Promise): Promise => { +export const attemptCommunitiesPendingPayments = async (bot: Telegraf): Promise => { const pendingPayments = await PendingPayment.find({ paid: false, attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, @@ -136,6 +141,7 @@ exports.attemptCommunitiesPendingPayments = async (bot: Telegraf): request: pending.payment_request, }); const user = await User.findById(pending.user_id); + if (user === null) throw Error("User was not found in DB"); const i18nCtx: I18nContext = await getUserI18nContext(user); // If the buyer's invoice is expired we let it know and don't try to pay again if (!!payment && payment.is_expired) { @@ -147,9 +153,10 @@ exports.attemptCommunitiesPendingPayments = async (bot: Telegraf): } const community = await Community.findById(pending.community_id); + if (community === null) throw Error("Community was not found in DB"); if (!!payment && !!payment.confirmed_at) { pending.paid = true; - pending.paid_at = new Date().toISOString(); + pending.paid_at = new Date(); // Reset the community's values community.earnings = 0; diff --git a/tsconfig.json b/tsconfig.json index 1e13115d..7b8d0663 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "strict": true, "esModuleInterop": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "downlevelIteration": true } } From 2501abc31f551fa01deacd580f8b1e4c7e0ea5e9 Mon Sep 17 00:00:00 2001 From: Mehrshad Date: Mon, 11 Sep 2023 16:27:10 +0330 Subject: [PATCH 03/13] bot/start: fixing types & import/exports Co-authored-by: webwarrior --- bot/start.ts | 59 ++++++++++++++++++++++++++--------------------- package-lock.json | 10 ++++++++ package.json | 1 + 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/bot/start.ts b/bot/start.ts index fe7a1013..e3876ed8 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -4,18 +4,17 @@ import { Message } from 'typegram' import { UserDocument } from '../models/user' import { FilterQuery } from 'mongoose'; const OrderEvents = require('./modules/events/orders'); - -const { limit } = require('@grammyjs/ratelimiter'); +import { limit } from "@grammyjs/ratelimiter" const schedule = require('node-schedule'); -const { +import { Order, User, PendingPayment, Community, Dispute, Config, -} = require('../models'); -const { getCurrenciesWithPrice, deleteOrderFromChannel, removeAtSymbol } = require('../util'); +} from '../models'; +import { getCurrenciesWithPrice, deleteOrderFromChannel, removeAtSymbol } from '../util'; const { commandArgsMiddleware, stageMiddleware, @@ -55,7 +54,7 @@ const { validateLightningAddress, } = require('./validations'); import * as messages from './messages'; -const { +import { attemptPendingPayments, cancelOrders, deleteOrders, @@ -63,8 +62,10 @@ const { attemptCommunitiesPendingPayments, deleteCommunity, nodeInfo, -} = require('../jobs'); -const { logger } = require('../logger'); +} from '../jobs'; +import { logger } from "../logger"; +import { ICommunity, IUsernameId } from '../models/community'; + export interface MainContext extends Context { match: Array | null; i18n: I18nContext; @@ -72,7 +73,7 @@ export interface MainContext extends Context { admin: UserDocument; } -interface OrderQuery { +export interface OrderQuery { status?: string; buyer_id?: string; seller_id?: string; @@ -80,7 +81,7 @@ interface OrderQuery { const askForConfirmation = async (user: UserDocument, command: string) => { try { - let orders = []; + let orders: any[] = []; if (command === '/cancel') { const where: FilterQuery = { $and: [ @@ -133,6 +134,7 @@ const askForConfirmation = async (user: UserDocument, command: string) => { return orders; } catch (error) { logger.error(error); + return null; } }; @@ -145,7 +147,7 @@ has the same condition. The problem mentioned above is similar to this issue: https://github.com/telegraf/telegraf/issues/1319#issuecomment-766360594 */ -const ctxUpdateAssertMsg = "ctx.update.message.text is not available."; +export const ctxUpdateAssertMsg = "ctx.update.message.text is not available."; const initialize = (botToken: string, options: Partial>): Telegraf => { const i18n = new I18n({ @@ -262,11 +264,11 @@ const initialize = (botToken: string, options: Partial el); + const [command, orderId] = params.filter((el: string) => el); if (!orderId) { const orders = await askForConfirmation(ctx.user, command); - if (!orders.length) return await ctx.reply(`${command} `); + if (orders === null || orders.length === 0) return await ctx.reply(`${command} `); return await messages.showConfirmationButtons(ctx, orders, command); } else if (!(await validateObjectId(ctx, orderId))) { @@ -367,10 +369,11 @@ const initialize = (botToken: string, options: Partial el); + const [command, orderId] = params.filter((el: string) => el); if (!orderId) { const orders = await askForConfirmation(ctx.user, command); - if (!orders.length) return await ctx.reply(`${command} `); + if (orders === null || orders.length === 0) return await ctx.reply(`${command} `); return await messages.showConfirmationButtons(ctx, orders, command); } else if (!(await validateObjectId(ctx, orderId))) { @@ -445,7 +448,7 @@ const initialize = (botToken: string, options: Partial el); + const [command, orderId] = params.filter((el: string) => el); if (!orderId) { const orders = await askForConfirmation(ctx.user, command); - if (!orders.length) return await ctx.reply(`${command} `); + if (orders === null || orders.length === 0) return await ctx.reply(`${command} `); return await messages.showConfirmationButtons(ctx, orders, command); } else if (!(await validateObjectId(ctx, orderId))) { @@ -648,6 +652,7 @@ const initialize = (botToken: string, options: Partial el.id !== user.id + if (community === null) throw Error("Community was not found in DB"); + community.banned_users = community.banned_users.toObject().filter( + (el: IUsernameId) => el.id !== user.id ); await community.save(); } else { @@ -739,7 +745,7 @@ const initialize = (botToken: string, options: Partial { try { const config = await Config.findOne({}); + if (config === null) throw Error("Config was not found in DB"); await messages.showInfoMessage(ctx, ctx.user, config); } catch (error) { logger.error(error); diff --git a/package-lock.json b/package-lock.json index fd8c05d8..6458396d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ }, "devDependencies": { "@types/node": "^20.5.0", + "@types/node-schedule": "^2.1.0", "@types/qrcode": "^1.5.2", "chai": "^4.3.4", "chokidar": "^3.5.3", @@ -1770,6 +1771,15 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/node-schedule": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.0.tgz", + "integrity": "sha512-NiTwl8YN3v/1YCKrDFSmCTkVxFDylueEqsOFdgF+vPsm+AlyJKGAo5yzX1FiOxPsZiN6/r8gJitYx2EaSuBmmg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qrcode": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz", diff --git a/package.json b/package.json index 47a582ed..87471cd2 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "devDependencies": { "@types/node": "^20.5.0", "@types/qrcode": "^1.5.2", + "@types/node-schedule": "^2.1.0", "chai": "^4.3.4", "chokidar": "^3.5.3", "eslint": "^8.15.0", From df9681cbe1cea34e0e0e57070646af741143e5ce Mon Sep 17 00:00:00 2001 From: Mehrshad Date: Tue, 12 Sep 2023 12:22:04 +0330 Subject: [PATCH 04/13] bot/validation: convert js to ts Using null instead of a boolean/undefined type is better. --- bot/{validations.js => validations.ts} | 125 +++++++++++++++---------- 1 file changed, 73 insertions(+), 52 deletions(-) rename bot/{validations.js => validations.ts} (77%) diff --git a/bot/validations.js b/bot/validations.ts similarity index 77% rename from bot/validations.js rename to bot/validations.ts index 356d5447..b6521334 100644 --- a/bot/validations.js +++ b/bot/validations.ts @@ -1,22 +1,32 @@ +import { MainContext, OrderQuery, ctxUpdateAssertMsg } from "./start"; +import { ICommunity } from "../models/community"; +import { FilterQuery } from "mongoose"; +import { UserDocument } from "../models/user"; +import { IOrder } from "../models/order"; +import { Telegraf } from "telegraf"; + const { parsePaymentRequest } = require('invoices'); const { ObjectId } = require('mongoose').Types; -const messages = require('./messages'); -const { Order, User, Community } = require('../models'); -const { - isIso4217, - isDisputeSolver, - removeLightningPrefix, -} = require('../util'); +import * as messages from './messages'; +import { Order, User, Community } from '../models'; +import { isIso4217, isDisputeSolver, removeLightningPrefix } from '../util'; const { existLightningAddress } = require('../lnurl/lnurl-pay'); -const { logger } = require('../logger'); +import { logger } from '../logger'; // We look in database if the telegram user exists, // if not, it creates a new user -const validateUser = async (ctx, start) => { +const validateUser = async (ctx: MainContext, start: boolean) => { try { - const tgUser = ctx.update.callback_query - ? ctx.update.callback_query.from - : ctx.update.message.from; + let tgUser = null; + if (("callback_query" in ctx.update) && ctx.update.callback_query) { + tgUser = ctx.update.callback_query.from; + } + else if (("message" in ctx.update) && ctx.update.message) { + tgUser = ctx.update.message.from; + } + else { + throw new Error(ctxUpdateAssertMsg); + } // We need to make sure the user has a username if (!tgUser.username) { await ctx.telegram.sendMessage(tgUser.id, ctx.i18n.t('non_handle_error')); @@ -51,15 +61,18 @@ const validateUser = async (ctx, start) => { } }; -const validateSuperAdmin = async (ctx, id) => { +const validateSuperAdmin = async (ctx: MainContext, id?: string) => { try { + if (!('message' in ctx.update) || !('text' in ctx.update.message)) { + throw new Error(ctxUpdateAssertMsg); + } const tgUserId = id || ctx.update.message.from.id; const user = await User.findOne({ tg_id: tgUserId }); // If the user never started the bot we can't send messages // to that user, so we do nothing if (!user) return; - if (!user.admin) return await messages.notAuthorized(ctx, tgUserId); + if (!user.admin) return await messages.notAuthorized(ctx, tgUserId.toString()); return user; } catch (error) { @@ -68,8 +81,11 @@ const validateSuperAdmin = async (ctx, id) => { } }; -const validateAdmin = async (ctx, id) => { +const validateAdmin = async (ctx: MainContext, id?: string) => { try { + if (!('message' in ctx.update) || !('text' in ctx.update.message)) { + throw new Error(ctxUpdateAssertMsg); + } const tgUserId = id || ctx.update.message.from.id; const user = await User.findOne({ tg_id: tgUserId }); // If the user never started the bot we can't send messages @@ -80,10 +96,11 @@ const validateAdmin = async (ctx, id) => { if (user.default_community_id) community = await Community.findOne({ _id: user.default_community_id }); + if (community === null) throw Error("Community was not found in DB"); const isSolver = isDisputeSolver(community, user); if (!user.admin && !isSolver) - return await messages.notAuthorized(ctx, tgUserId); + return await messages.notAuthorized(ctx, tgUserId.toString()); return user; } catch (error) { @@ -92,7 +109,7 @@ const validateAdmin = async (ctx, id) => { } }; -const processParameters = args => { +const processParameters = (args: string[]) => { const correctedArgs = []; let isGrouping = false; let groupedString = ''; @@ -124,7 +141,7 @@ const processParameters = args => { return correctedArgs; }; -const validateSellOrder = async ctx => { +const validateSellOrder = async (ctx: MainContext) => { try { let args = ctx.state.command.args; if (args.length < 4) { @@ -169,11 +186,11 @@ const validateSellOrder = async ctx => { return false; } - if (amount !== 0 && amount < process.env.MIN_PAYMENT_AMT) { + if (amount !== 0 && amount < Number(process.env.MIN_PAYMENT_AMT)) { await messages.mustBeGreatherEqThan( ctx, 'monto_en_sats', - process.env.MIN_PAYMENT_AMT + Number(process.env.MIN_PAYMENT_AMT) ); return false; } @@ -188,7 +205,7 @@ const validateSellOrder = async ctx => { return false; } - if (fiatAmount.some(x => x < 1)) { + if (fiatAmount.some((x: number) => x < 1)) { await messages.mustBeGreatherEqThan(ctx, 'monto_en_fiat', 1); return false; } @@ -213,7 +230,7 @@ const validateSellOrder = async ctx => { } }; -const validateBuyOrder = async ctx => { +const validateBuyOrder = async (ctx: MainContext) => { try { let args = ctx.state.command.args; if (args.length < 4) { @@ -257,11 +274,11 @@ const validateBuyOrder = async ctx => { return false; } - if (amount !== 0 && amount < process.env.MIN_PAYMENT_AMT) { + if (amount !== 0 && amount < Number(process.env.MIN_PAYMENT_AMT)) { await messages.mustBeGreatherEqThan( ctx, 'monto_en_sats', - process.env.MIN_PAYMENT_AMT + Number(process.env.MIN_PAYMENT_AMT) ); return false; } @@ -276,7 +293,7 @@ const validateBuyOrder = async ctx => { return false; } - if (fiatAmount.some(x => x < 1)) { + if (fiatAmount.some((x: number) => x < 1)) { await messages.mustBeGreatherEqThan(ctx, 'monto_en_fiat', 1); return false; } @@ -300,21 +317,22 @@ const validateBuyOrder = async ctx => { return false; } }; -const validateLightningAddress = async lightningAddress => { +const validateLightningAddress = async (lightningAddress: string) => { const pattern = /^[\w-.]+@(?:[\w-]+(? { +const validateInvoice = async (ctx: MainContext, lnInvoice: string) => { try { const checkedPrefixlnInvoice = removeLightningPrefix(lnInvoice); const invoice = parsePaymentRequest({ request: checkedPrefixlnInvoice }); const latestDate = new Date( - Date.now() + parseInt(process.env.INVOICE_EXPIRATION_WINDOW) - ).toISOString(); - if (!!invoice.tokens && invoice.tokens < process.env.MIN_PAYMENT_AMT) { + Date.now() + Number(process.env.INVOICE_EXPIRATION_WINDOW) + ); + if (!("MAIN_PAYMENT_AMT" in process.env)) throw Error("MIN_PAYMENT_AMT not found, please check .env file"); + if (!!invoice.tokens && invoice.tokens < Number(process.env.MIN_PAYMENT_AMT)) { await messages.minimunAmountInvoiceMessage(ctx); return false; } @@ -347,21 +365,21 @@ const validateInvoice = async (ctx, lnInvoice) => { } }; -const isValidInvoice = async (ctx, lnInvoice) => { +const isValidInvoice = async (ctx: MainContext, lnInvoice: string) => { try { const checkedPrefixlnInvoice = removeLightningPrefix(lnInvoice); const invoice = parsePaymentRequest({ request: checkedPrefixlnInvoice }); const latestDate = new Date( - Date.now() + parseInt(process.env.INVOICE_EXPIRATION_WINDOW) + Date.now() + Number(process.env.INVOICE_EXPIRATION_WINDOW) ).toISOString(); - if (!!invoice.tokens && invoice.tokens < process.env.MIN_PAYMENT_AMT) { + if (!!invoice.tokens && invoice.tokens < Number(process.env.MIN_PAYMENT_AMT)) { await messages.invoiceMustBeLargerMessage(ctx); return { success: false, }; } - if (new Date(invoice.expires_at) < latestDate) { + if (new Date(invoice.expires_at).toISOString() < latestDate) { await messages.invoiceExpiryTooShortMessage(ctx); return { success: false, @@ -401,7 +419,7 @@ const isValidInvoice = async (ctx, lnInvoice) => { } }; -const isOrderCreator = (user, order) => { +const isOrderCreator = (user: UserDocument, order: IOrder) => { try { return user._id == order.creator_id; } catch (error) { @@ -410,7 +428,7 @@ const isOrderCreator = (user, order) => { } }; -const validateTakeSellOrder = async (ctx, bot, user, order) => { +const validateTakeSellOrder = async (ctx: MainContext, bot: Telegraf, user: UserDocument, order: IOrder) => { try { if (!order) { await messages.invalidOrderMessage(ctx, bot, user); @@ -439,10 +457,10 @@ const validateTakeSellOrder = async (ctx, bot, user, order) => { } }; -const validateTakeBuyOrder = async (ctx, bot, user, order) => { +const validateTakeBuyOrder = async (ctx: MainContext, bot: Telegraf, user: UserDocument, order: IOrder) => { try { if (!order) { - await messages.invalidOrderMessage(bot, user); + await messages.invalidOrderMessage(ctx, bot, user); return false; } if (isOrderCreator(user, order) && process.env.NODE_ENV === 'production') { @@ -464,9 +482,9 @@ const validateTakeBuyOrder = async (ctx, bot, user, order) => { } }; -const validateReleaseOrder = async (ctx, user, orderId) => { +const validateReleaseOrder = async (ctx: MainContext, user: UserDocument, orderId: string) => { try { - let where = { + let where: FilterQuery = { seller_id: user._id, status: 'WAITING_BUYER_INVOICE', _id: orderId, @@ -507,7 +525,7 @@ const validateReleaseOrder = async (ctx, user, orderId) => { } }; -const validateDisputeOrder = async (ctx, user, orderId) => { +const validateDisputeOrder = async (ctx: MainContext, user: UserDocument, orderId: string) => { try { const where = { $and: [ @@ -531,9 +549,9 @@ const validateDisputeOrder = async (ctx, user, orderId) => { } }; -const validateFiatSentOrder = async (ctx, user, orderId) => { +const validateFiatSentOrder = async (ctx: MainContext, user: UserDocument, orderId: string) => { try { - const where = { + const where: FilterQuery = { $and: [ { buyer_id: user._id }, { $or: [{ status: 'ACTIVE' }, { status: 'PAID_HOLD_INVOICE' }] }, @@ -567,7 +585,7 @@ const validateFiatSentOrder = async (ctx, user, orderId) => { }; // If a seller have an order with status FIAT_SENT, return false -const validateSeller = async (ctx, user) => { +const validateSeller = async (ctx: MainContext, user: UserDocument) => { try { const where = { seller_id: user._id, @@ -588,10 +606,13 @@ const validateSeller = async (ctx, user) => { } }; -const validateParams = async (ctx, paramNumber, errOutputString) => { +const validateParams = async (ctx: MainContext, paramNumber: number, errOutputString: string): Promise> => { try { + if (!('message' in ctx.update) || !('text' in ctx.update.message)) { + throw new Error(ctxUpdateAssertMsg); + } const paramsArray = ctx.update.message.text.split(' '); - const params = paramsArray.filter(el => el !== ''); + const params = paramsArray.filter((el: string) => el !== ''); if (params.length !== paramNumber) { await messages.customMessage( ctx, @@ -604,11 +625,11 @@ const validateParams = async (ctx, paramNumber, errOutputString) => { return params.slice(1); } catch (error) { logger.error(error); - return false; + return null; } }; -const validateObjectId = async (ctx, id) => { +const validateObjectId = async (ctx: MainContext, id: string) => { try { if (!ObjectId.isValid(id)) { await messages.notValidIdMessage(ctx); @@ -622,10 +643,10 @@ const validateObjectId = async (ctx, id) => { } }; -const validateUserWaitingOrder = async (ctx, bot, user) => { +const validateUserWaitingOrder = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { try { // If is a seller - let where = { + let where: FilterQuery = { seller_id: user._id, status: 'WAITING_PAYMENT', }; @@ -652,12 +673,12 @@ const validateUserWaitingOrder = async (ctx, bot, user) => { }; // We check if the user is banned from the community in the order -const isBannedFromCommunity = async (user, communityId) => { +const isBannedFromCommunity = async (user: UserDocument, communityId: string) => { try { if (!communityId) return false; const community = await Community.findOne({ _id: communityId }); if (!community) return false; - return community.banned_users.some(buser => buser.id == user._id); + return community.banned_users.toObject().some((buser: ICommunity) => buser.id == user._id); } catch (error) { logger.error(error); return false; From ff52901486c004ae3d29d0db067a12a87d2642db Mon Sep 17 00:00:00 2001 From: Mehrshad Date: Mon, 11 Sep 2023 13:46:49 +0330 Subject: [PATCH 05/13] lnurl/lnurl-pay: convert js to ts I had to change the '|', otherwise typescript would complain this error msg: ``` The '|' operator is not allowed for boolean types. Consider using '||' instead. ``` Co-authored-by: webwarrior --- lnurl/{lnurl-pay.js => lnurl-pay.ts} | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) rename lnurl/{lnurl-pay.js => lnurl-pay.ts} (76%) diff --git a/lnurl/lnurl-pay.js b/lnurl/lnurl-pay.ts similarity index 76% rename from lnurl/lnurl-pay.js rename to lnurl/lnurl-pay.ts index bd0ada6a..96aa85d0 100644 --- a/lnurl/lnurl-pay.js +++ b/lnurl/lnurl-pay.ts @@ -1,11 +1,11 @@ -const axios = require('axios').default; -const { logger } = require('../logger'); +import axios from 'axios'; +import { logger } from "../logger"; // { // pr: String, // bech32-serialized lightning invoice // routes: [], // an empty array // } -const resolvLightningAddress = async (address, amountMsat) => { +const resolvLightningAddress = async (address: string, amountMsat: number) => { const [user, domain] = address.split('@'); const lnAddressQuery = `https://${domain}/.well-known/lnurlp/${user}`; @@ -17,7 +17,7 @@ const resolvLightningAddress = async (address, amountMsat) => { } if ( - (lnAddressRes.minSendable > amountMsat) | + (lnAddressRes.minSendable > amountMsat) || (lnAddressRes.maxSendable < amountMsat) ) { logger.info('lnAddress invalid amount'); @@ -31,7 +31,7 @@ const resolvLightningAddress = async (address, amountMsat) => { return res; }; -const existLightningAddress = async address => { +const existLightningAddress = async (address: string) => { const [user, domain] = address.split('@'); const lnAddressQuery = `https://${domain}/.well-known/lnurlp/${user}`; @@ -48,7 +48,4 @@ const existLightningAddress = async address => { } }; -module.exports = { - resolvLightningAddress, - existLightningAddress, -}; +export { resolvLightningAddress, existLightningAddress } From 27c978e5e9cc8a63cc42b92a92fce8a5879d651b Mon Sep 17 00:00:00 2001 From: Mehrshad Date: Wed, 13 Sep 2023 13:58:27 +0330 Subject: [PATCH 06/13] refactor: correcting a bad practice --- bot/start.ts | 16 ++++++++-------- bot/validations.ts | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bot/start.ts b/bot/start.ts index e3876ed8..25984393 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -217,7 +217,7 @@ const initialize = (botToken: string, options: Partial'); if (!val) return; let config = await Config.findOne(); - if (!config) { + if (config === null) { config = new Config(); } config.maintenance = false; @@ -327,7 +327,7 @@ const initialize = (botToken: string, options: Partial { const user = await User.findOne({ tg_id: tgUserId }); // If the user never started the bot we can't send messages // to that user, so we do nothing - if (!user) return; + if (user === null) return; if (!user.admin) return await messages.notAuthorized(ctx, tgUserId.toString()); @@ -90,7 +90,7 @@ const validateAdmin = async (ctx: MainContext, id?: string) => { const user = await User.findOne({ tg_id: tgUserId }); // If the user never started the bot we can't send messages // to that user, so we do nothing - if (!user) return; + if (user === null) return; let community = null; if (user.default_community_id) @@ -513,7 +513,7 @@ const validateReleaseOrder = async (ctx: MainContext, user: UserDocument, orderI } order = await Order.findOne(where); - if (!order) { + if (order === null) { await messages.notActiveOrderMessage(ctx); return false; } @@ -537,7 +537,7 @@ const validateDisputeOrder = async (ctx: MainContext, user: UserDocument, orderI const order = await Order.findOne(where); - if (!order) { + if (order === null) { await messages.notActiveOrderMessage(ctx); return false; } @@ -562,7 +562,7 @@ const validateFiatSentOrder = async (ctx: MainContext, user: UserDocument, order where._id = orderId; } const order = await Order.findOne(where); - if (!order) { + if (order === null) { await messages.notActiveOrderMessage(ctx); return false; } From 4aeffd6c9a841f3f36187d0aefb277e6aebab3e9 Mon Sep 17 00:00:00 2001 From: Mehrshad Date: Thu, 14 Sep 2023 15:04:14 +0330 Subject: [PATCH 07/13] refactor: there's no need for isInt() --- util/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/util/index.ts b/util/index.ts index 1697643e..d1308a6d 100644 --- a/util/index.ts +++ b/util/index.ts @@ -432,11 +432,6 @@ const itemsFromMessage = (str: string) => { .filter(e => !!e); }; -// Check if a number is int -const isInt = (n: number) => Number(n) === n; - -exports.isInt = isInt; - // Check if a number is float const isFloat = (n: number) => typeof n === 'number' && !Number.isInteger(n); @@ -544,7 +539,6 @@ export { isDisputeSolver, getFee, itemsFromMessage, - isInt, isFloat, getLanguageFlag, delay, From 8d7865ad700e0d9528739c4a6e697d1f69fc0088 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Mon, 2 Sep 2024 12:33:26 +0200 Subject: [PATCH 08/13] bot: fix bugs in validations.ts Fix bugs in validations.ts introduced in d36a6b7 when porting to TypeScript. --- bot/validations.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/bot/validations.ts b/bot/validations.ts index 0cd718db..2d9cba4d 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -13,6 +13,8 @@ import { isIso4217, isDisputeSolver, removeLightningPrefix } from '../util'; const { existLightningAddress } = require('../lnurl/lnurl-pay'); import { logger } from '../logger'; +const ctxUpdateMessageFromAssertMsg = "ctx.update.message.from is not available"; + // We look in database if the telegram user exists, // if not, it creates a new user const validateUser = async (ctx: MainContext, start: boolean) => { @@ -63,16 +65,19 @@ const validateUser = async (ctx: MainContext, start: boolean) => { const validateSuperAdmin = async (ctx: MainContext, id?: string) => { try { - if (!('message' in ctx.update) || !('text' in ctx.update.message)) { - throw new Error(ctxUpdateAssertMsg); + let tgUserId = id; + if (!tgUserId) { + if (!('message' in ctx.update) || !('from' in ctx.update.message)) { + throw new Error(ctxUpdateMessageFromAssertMsg); + } + tgUserId = ctx.update.message.from.id.toString(); } - const tgUserId = id || ctx.update.message.from.id; const user = await User.findOne({ tg_id: tgUserId }); // If the user never started the bot we can't send messages // to that user, so we do nothing if (user === null) return; - if (!user.admin) return await messages.notAuthorized(ctx, tgUserId.toString()); + if (!user.admin) return await messages.notAuthorized(ctx, tgUserId); return user; } catch (error) { @@ -83,10 +88,13 @@ const validateSuperAdmin = async (ctx: MainContext, id?: string) => { const validateAdmin = async (ctx: MainContext, id?: string) => { try { - if (!('message' in ctx.update) || !('text' in ctx.update.message)) { - throw new Error(ctxUpdateAssertMsg); + let tgUserId = id; + if (!tgUserId) { + if (!('message' in ctx.update) || !('from' in ctx.update.message)) { + throw new Error(ctxUpdateMessageFromAssertMsg); + } + tgUserId = ctx.update.message.from.id.toString(); } - const tgUserId = id || ctx.update.message.from.id; const user = await User.findOne({ tg_id: tgUserId }); // If the user never started the bot we can't send messages // to that user, so we do nothing @@ -100,7 +108,7 @@ const validateAdmin = async (ctx: MainContext, id?: string) => { const isSolver = isDisputeSolver(community, user); if (!user.admin && !isSolver) - return await messages.notAuthorized(ctx, tgUserId.toString()); + return await messages.notAuthorized(ctx, tgUserId); return user; } catch (error) { From a256aaaf702caacaee5f1ac3f1584ae7b4d3ae46 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 5 Sep 2024 10:43:10 +0200 Subject: [PATCH 09/13] bot,util: fix bug in validateAdmin Fixed bug in validateAdmin that would throw an error when community is null even though null value is valid in this case. --- bot/validations.ts | 3 +-- util/index.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bot/validations.ts b/bot/validations.ts index 2d9cba4d..805c28ad 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -103,8 +103,7 @@ const validateAdmin = async (ctx: MainContext, id?: string) => { let community = null; if (user.default_community_id) community = await Community.findOne({ _id: user.default_community_id }); - - if (community === null) throw Error("Community was not found in DB"); + const isSolver = isDisputeSolver(community, user); if (!user.admin && !isSolver) diff --git a/util/index.ts b/util/index.ts index d1308a6d..0b1bc7e7 100644 --- a/util/index.ts +++ b/util/index.ts @@ -402,7 +402,7 @@ const getDetailedOrder = (i18n: I18nContext, order: IOrder, buyer: UserDocument, }; // We need to know if this user is a dispute solver for this community -const isDisputeSolver = (community: ICommunity, user: UserDocument) => { +const isDisputeSolver = (community: ICommunity | null, user: UserDocument) => { if (!community || !user) { return false; } From 8a22b8fc57928a4f0a05ff99a75d6c05d66bcc09 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 5 Sep 2024 10:52:41 +0200 Subject: [PATCH 10/13] bot: fix date comparison in isValidInvoice Don't convert dates to strings in isValidInvoice. --- bot/validations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/validations.ts b/bot/validations.ts index 805c28ad..28cb2803 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -378,7 +378,7 @@ const isValidInvoice = async (ctx: MainContext, lnInvoice: string) => { const invoice = parsePaymentRequest({ request: checkedPrefixlnInvoice }); const latestDate = new Date( Date.now() + Number(process.env.INVOICE_EXPIRATION_WINDOW) - ).toISOString(); + ); if (!!invoice.tokens && invoice.tokens < Number(process.env.MIN_PAYMENT_AMT)) { await messages.invoiceMustBeLargerMessage(ctx); return { @@ -386,7 +386,7 @@ const isValidInvoice = async (ctx: MainContext, lnInvoice: string) => { }; } - if (new Date(invoice.expires_at).toISOString() < latestDate) { + if (new Date(invoice.expires_at) < latestDate) { await messages.invoiceExpiryTooShortMessage(ctx); return { success: false, From 20b6ccfc29319577432aa71280ca3836fbed370a Mon Sep 17 00:00:00 2001 From: webwarrior Date: Mon, 9 Sep 2024 15:19:46 +0200 Subject: [PATCH 11/13] WIP: added some debug logging Added debug logging for case when `Invoice expiry is too short` error is encountered. --- bot/validations.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/validations.ts b/bot/validations.ts index 28cb2803..2af82307 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -387,6 +387,9 @@ const isValidInvoice = async (ctx: MainContext, lnInvoice: string) => { } if (new Date(invoice.expires_at) < latestDate) { + console.debug(`Date(invoice.expires_at) = ${new Date(invoice.expires_at)}`); + console.debug(`latestDate = ${latestDate}`); + console.debug(`INVOICE_EXPIRATION_WINDOW = ${Number(process.env.INVOICE_EXPIRATION_WINDOW)}`); await messages.invoiceExpiryTooShortMessage(ctx); return { success: false, From ba09fddefd2fba38f514144fe8ed96e7f8587ff9 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 12 Sep 2024 14:38:37 +0200 Subject: [PATCH 12/13] WIP: more debug logging Added debug logging of message that caused error when taking dispute in community. --- bot/modules/dispute/messages.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/bot/modules/dispute/messages.js b/bot/modules/dispute/messages.js index 5b1aa0bb..a8870123 100644 --- a/bot/modules/dispute/messages.js +++ b/bot/modules/dispute/messages.js @@ -92,22 +92,24 @@ exports.disputeData = async ( const escapedInitiatorUsername = sanitizeMD(initiatorUser.username); const escapedCounterPartyUsername = sanitizeMD(counterPartyUser.username); + const message = ctx.i18n.t('dispute_started_channel', { + initiatorUser: { ...initiatorUser, username: escapedInitiatorUsername }, + initiatorTgId: initiatorUser.tg_id, + counterPartyUser: { ...counterPartyUser, username: escapedCounterPartyUsername }, + counterPartyUserTgId: counterPartyUser.tg_id, + buyer, + seller, + buyerDisputes, + sellerDisputes, + detailedOrder, + type, + sellerToken: order.seller_dispute_token, + buyerToken: order.buyer_dispute_token, + }); + console.log(`Contens of message:\n${message}`); await ctx.telegram.sendMessage( solver.tg_id, - ctx.i18n.t('dispute_started_channel', { - initiatorUser: { ...initiatorUser, username: escapedInitiatorUsername }, - initiatorTgId: initiatorUser.tg_id, - counterPartyUser: { ...counterPartyUser, username: escapedCounterPartyUsername }, - counterPartyUserTgId: counterPartyUser.tg_id, - buyer, - seller, - buyerDisputes, - sellerDisputes, - detailedOrder, - type, - sellerToken: order.seller_dispute_token, - buyerToken: order.buyer_dispute_token, - }), + message, { parse_mode: 'MarkdownV2' } ); // message to both parties letting them know the dispute From 706b9fdbf5beeb4518520367bd31989f0f3baa71 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 26 Sep 2024 14:26:16 +0200 Subject: [PATCH 13/13] bot,util,locales,tests: don't use objects in locales Don't use object properties inside locales, as it doesn't work and leads to parse errors. --- bot/modules/dispute/messages.js | 20 +++++++++++--------- locales/de.yaml | 8 ++++---- locales/en.yaml | 8 ++++---- locales/es.yaml | 8 ++++---- locales/fa.yaml | 8 ++++---- locales/fr.yaml | 8 ++++---- locales/it.yaml | 8 ++++---- locales/ko.yaml | 8 ++++---- locales/pt.yaml | 8 ++++---- locales/ru.yaml | 8 ++++---- locales/uk.yaml | 8 ++++---- tests/bot/modules/dispute/messages.js | 8 ++++---- util/index.ts | 2 +- 13 files changed, 56 insertions(+), 54 deletions(-) diff --git a/bot/modules/dispute/messages.js b/bot/modules/dispute/messages.js index a8870123..d8bc775f 100644 --- a/bot/modules/dispute/messages.js +++ b/bot/modules/dispute/messages.js @@ -1,4 +1,8 @@ -const { getDisputeChannel, getDetailedOrder, sanitizeMD } = require('../../../util'); +const { + getDisputeChannel, + getDetailedOrder, + sanitizeMD, +} = require('../../../util'); const { logger } = require('../../../logger'); exports.beginDispute = async (ctx, initiator, order, buyer, seller) => { @@ -91,11 +95,11 @@ exports.disputeData = async ( // Fix Issue 543: Escape underscores in usernames const escapedInitiatorUsername = sanitizeMD(initiatorUser.username); const escapedCounterPartyUsername = sanitizeMD(counterPartyUser.username); - + const message = ctx.i18n.t('dispute_started_channel', { - initiatorUser: { ...initiatorUser, username: escapedInitiatorUsername }, + initiatorUser: escapedInitiatorUsername, initiatorTgId: initiatorUser.tg_id, - counterPartyUser: { ...counterPartyUser, username: escapedCounterPartyUsername }, + counterPartyUser: escapedCounterPartyUsername, counterPartyUserTgId: counterPartyUser.tg_id, buyer, seller, @@ -107,11 +111,9 @@ exports.disputeData = async ( buyerToken: order.buyer_dispute_token, }); console.log(`Contens of message:\n${message}`); - await ctx.telegram.sendMessage( - solver.tg_id, - message, - { parse_mode: 'MarkdownV2' } - ); + await ctx.telegram.sendMessage(solver.tg_id, message, { + parse_mode: 'MarkdownV2', + }); // message to both parties letting them know the dispute // has been taken by a solver await ctx.telegram.sendMessage( diff --git a/locales/de.yaml b/locales/de.yaml index 4b1e32dd..d3182561 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -202,16 +202,16 @@ order_detail: | seller: Käufer buyer: Verkäufer dispute_started_channel: | - Benutzer ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - hat einen Streitfall mit @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} für den folgenden Auftrag eröffnet + Benutzer ${type} @${initiatorUser} TG ID: ${initiatorTgId} + hat einen Streitfall mit @${counterPartyUser} TG ID: ${counterPartyUserTgId} für den folgenden Auftrag eröffnet ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - @${initiatorUser.username} war an ${initiatorUser.disputes} Streitfällen beteiligt - @${counterPartyUser.username} war an ${counterPartyUser.disputes} Streitfällen beteiligt + @${initiatorUser} war an ${buyerDisputes} Streitfällen beteiligt + @${counterPartyUser} war an ${sellerDisputes} Streitfällen beteiligt you_started: '🥴 Sie haben einen Streitfall zu Ihrer Bestellnummer begonnen: ${orderId}.' counterpart_started: '🥴 Ihre Gegenpartei hat einen Streit über Ihre Bestellungsnummer begonnen: ${orderId}.' dispute_started: '${who} Wenn er/sie Ihrem Streitfall zugewiesen wird, teilt der Bot Ihnen seinen/ihren Benutzernamen mit, und nur er/sie kann Sie betreuen. Sie können ihm/ihr direkt schreiben, aber wenn er/sie Sie zuerst kontaktiert, müssen Sie ihn/sie bitten, Ihnen den Token Ihres Streitfalls mitzuteilen, Ihr Token ist: ${token}.' diff --git a/locales/en.yaml b/locales/en.yaml index 08baacf0..a9d73982 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -204,16 +204,16 @@ order_detail: | seller: seller buyer: buyer dispute_started_channel: | - User ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - has started a dispute with @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} for the order + User ${type} @${initiatorUser} TG ID: ${initiatorTgId} + has started a dispute with @${counterPartyUser} TG ID: ${counterPartyUserTgId} for the order ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - @${initiatorUser.username} has been involved in ${initiatorUser.disputes} disputes - @${counterPartyUser.username} has been involved in ${counterPartyUser.disputes} disputes + @${initiatorUser} has been involved in ${buyerDisputes} disputes + @${counterPartyUser} has been involved in ${sellerDisputes} disputes you_started: '🥴 You have started a dispute on your order Id: ${orderId}.' counterpart_started: '🥴 Your counterparty started a dispute on your order Id: ${orderId}.' dispute_started: '${who} A solver will attend you soon, when he/she is assigned to your dispute the bot will tell you his/her username, only he/she will be able to attend you. You can write to him/her directly, but if he/she contacts you first, you must ask him/her to tell you what is the token of your dispute, your token is: ${token}.' diff --git a/locales/es.yaml b/locales/es.yaml index 9baf03fd..26cbc22a 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -202,16 +202,16 @@ order_detail: | seller: vendedor buyer: comprador dispute_started_channel: | - El ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - ha iniciado una disputa con @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} en la orden: + El ${type} @${initiatorUser} TG ID: ${initiatorTgId} + ha iniciado una disputa con @${counterPartyUser} TG ID: ${counterPartyUserTgId} en la orden: ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - @${initiatorUser.username} ya tiene ${buyerDisputes} disputas - @${counterPartyUser.username} ya tiene ${sellerDisputes} disputas + @${initiatorUser} ya tiene ${buyerDisputes} disputas + @${counterPartyUser} ya tiene ${sellerDisputes} disputas you_started: '🥴 Has iniciado una disputa en tu orden con Id: ${orderId}.' counterpart_started: '🥴 Tu contraparte ha iniciado una disputa en tu orden con Id: ${orderId}.' dispute_started: '${who} Un solver te atenderá pronto, cuando él/la solver sea asignado a tu disputa el bot te dirá su username, solo él/ella podrá atenderte. Puedes escribirle directamente, pero si él/ella te contacta primero, debes pedirle que te diga cuál es el token de tu disputa, tu token es: ${token}.' diff --git a/locales/fa.yaml b/locales/fa.yaml index b95a5ebb..64fa703a 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -204,16 +204,16 @@ order_detail: | seller: seller buyer: buyer dispute_started_channel: | - کاربر ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - بابت سفارش زیر یک مشاجره را با کاربر @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} آغاز کرده + کاربر ${type} @${initiatorUser} TG ID: ${initiatorTgId} + بابت سفارش زیر یک مشاجره را با کاربر @${counterPartyUser} TG ID: ${counterPartyUserTgId} آغاز کرده ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - کابر@${initiatorUser.username} بابت ${initiatorUser.disputes} به اختلاف خورده - کاربر@${counterPartyUser.username} بابت ${counterPartyUser.disputes} به اختلاف خورده + کابر@${initiatorUser} بابت ${buyerDisputes} به اختلاف خورده + کاربر@${counterPartyUser} بابت ${sellerDisputes} به اختلاف خورده you_started: '🥴 شما در مورد شناسه سفارش خود اختلاف نظر شروع کرده اید: ${orderId}.' counterpart_started: '🥴 طرف مقابل شما یک اختلاف بر سر شناسه سفارش شما شروع کرد: ${orderId}.' dispute_started: '${who} یک حل کننده به زودی در شما حضور خواهد یافت، هنگامی که او به منازعه شما منصوب شد، ربات نام کاربری خود را به شما می گوید، فقط او می تواند در شما حضور داشته باشد. شما می توانید مستقیماً برای او نامه بنویسید، اما اگر ابتدا با شما تماس گرفت، باید از او بخواهید که به شما بگوید نشانه اختلاف شما چیست، رمز شما این است: ${token}.' diff --git a/locales/fr.yaml b/locales/fr.yaml index 72421581..7fe68d21 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -204,16 +204,16 @@ order_detail: | seller: vendeur buyer: acheteur dispute_started_channel: | - L'utilisateur ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - à déclenché un litige avec @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} concernant l'offre + L'utilisateur ${type} @${initiatorUser} TG ID: ${initiatorTgId} + à déclenché un litige avec @${counterPartyUser} TG ID: ${counterPartyUserTgId} concernant l'offre ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - @${initiatorUser.username} a été impliqué.e dans ${initiatorUser.disputes} litiges - @${counterPartyUser.username} a été impliqué.e dans ${counterPartyUser.disputes} litiges + @${initiatorUser} a été impliqué.e dans ${buyerDisputes} litiges + @${counterPartyUser} a été impliqué.e dans ${sellerDisputes} litiges you_started : '🥴 Vous avez commencé un litige sur votre commande Id : ${orderId}.' counterpart_started : '🥴 Votre contrepartie a démarré un litige sur votre commande Id : ${orderId}.' dispute_started : "${who} Un solver va bientôt venir vous voir, lorsqu'il/elle sera assigné(e) à votre litige, le bot vous indiquera son nom d'utilisateur, il/elle seul(e) sera en mesure de venir vous voir. Vous pouvez lui écrire directement, mais s'il vous contacte en premier, vous devez lui demander de vous dire quel est le jeton de votre litige, votre jeton est : ${token}." diff --git a/locales/it.yaml b/locales/it.yaml index 4d3862cd..b46301fe 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -202,16 +202,16 @@ order_detail: | seller: venditore buyer: acquirente dispute_started_channel: | - User ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - ha iniziato una disputa con @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} per l'ordine + User ${type} @${initiatorUser} TG ID: ${initiatorTgId} + ha iniziato una disputa con @${counterPartyUser} TG ID: ${counterPartyUserTgId} per l'ordine ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - @${initiatorUser.username} è stato coinvolto in ${initiatorUser.disputes} dispute - @${counterPartyUser.username} è stato coinvolto in in ${counterPartyUser.disputes} disputee + @${initiatorUser} è stato coinvolto in ${buyerDisputes} dispute + @${counterPartyUser} è stato coinvolto in in ${sellerDisputes} disputee you_started: '🥴 Hai aperto una controversia per il tuo ordine Id: ${orderId}.' counterpart_started: '🥴 La tua controparte ha aperto una controversia sul tuo ordine Id: ${orderId}.' dispute_started: '${who} Un risolutore ti assisterà presto, quando sarà assegnato alla tua controversia il bot ti dirà il suo nome utente, solo lui potrà assisterti. Potete scrivergli direttamente, ma se vi contatta prima, dovete chiedergli di dirvi qual è il token della vostra controversia, il vostro token è: ${token}.' diff --git a/locales/ko.yaml b/locales/ko.yaml index db10e9e8..cd019dab 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -203,16 +203,16 @@ order_detail: | seller: 판매자 buyer: 구매자 dispute_started_channel: | - 사용자 ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - 님께서 @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} 와의 분쟁 조정을 시작하였습니다. 주문 상세 내역은 다음과 같습니다. + 사용자 ${type} @${initiatorUser} TG ID: ${initiatorTgId} + 님께서 @${counterPartyUser} TG ID: ${counterPartyUserTgId} 와의 분쟁 조정을 시작하였습니다. 주문 상세 내역은 다음과 같습니다. ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - ${initiatorUser.disputes} 분쟁 조정에 @${initiatorUser.username}님께서 참여하셨습니다. - ${counterPartyUser.disputes} 분쟁 조정에 @${counterPartyUser.username}님께서 참여하셨습니다. + ${buyerDisputes} 분쟁 조정에 @${initiatorUser}님께서 참여하셨습니다. + ${sellerDisputes} 분쟁 조정에 @${counterPartyUser}님께서 참여하셨습니다. you_started: '🥴 주문 ID에 대한 분쟁이 시작되었습니다: ${orderId}' counterpart_started: '🥴 거래 상대방이 주문 ID에 대해 분쟁을 시작했습니다: ${orderId}' dispute_started: '${who} 해결사가 분쟁에 배정되면 봇이 자신의 사용자 아이디를 알려주며, 해당 해결사만 분쟁에 참석할 수 있습니다. 해결사에게 직접 편지를 보낼 수도 있지만, 해결사가 먼저 연락하는 경우 분쟁의 토큰이 무엇인지 알려달라고 요청해야 합니다(토큰은 ${토큰}입니다).' diff --git a/locales/pt.yaml b/locales/pt.yaml index cbf6b53a..21c518e0 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -201,16 +201,16 @@ order_detail: | seller: vendedora buyer: compradora dispute_started_channel: | - Usuário ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - iniciou uma disputa com @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} para o pedido + Usuário ${type} @${initiatorUser} TG ID: ${initiatorTgId} + iniciou uma disputa com @${counterPartyUser} TG ID: ${counterPartyUserTgId} para o pedido ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - @${initiatorUser.username} esteve envolvido em ${initiatorUser.disputes} disputas - @${counterPartyUser.username} esteve envolvido em ${counterPartyUser.disputes} disputas + @${initiatorUser} esteve envolvido em ${buyerDisputes} disputas + @${counterPartyUser} esteve envolvido em ${sellerDisputes} disputas you_started: '🥴 Você iniciou uma disputa em seu pedido Id: ${orderId}.' counterpart_started: '🥴 Sua contraparte iniciou uma disputa em seu pedido Id: ${orderId}.' dispute_started: '${who} Um solucionador o atenderá em breve. Quando ele for designado para sua disputa, o bot informará seu nome de usuário e somente ele poderá atendê-lo. Você pode escrever diretamente para ele/ela, mas se ele/ela entrar em contato com você primeiro, você deve pedir a ele/ela que lhe diga qual é o token de sua disputa, seu token é: ${token}.' diff --git a/locales/ru.yaml b/locales/ru.yaml index 34122308..32c5c2d2 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -201,16 +201,16 @@ order_detail: | seller: продавец buyer: покупатель dispute_started_channel: | - ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - инициировал разбирательство с @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} относительно заявки: + ${type} @${initiatorUser} TG ID: ${initiatorTgId} + инициировал разбирательство с @${counterPartyUser} TG ID: ${counterPartyUserTgId} относительно заявки: ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - @${initiatorUser.username} уже имел разбирательств: ${initiatorUser.disputes} - @${counterPartyUser.username} уже имел разбирательств: ${counterPartyUser.disputes} + @${initiatorUser} уже имел разбирательств: ${buyerDisputes} + @${counterPartyUser} уже имел разбирательств: ${sellerDisputes} you_started: '🥴 Вы начали спор по вашему заказу Id: ${orderId}.' counterpart_started: '🥴 Ваш контрагент начал спор по вашему заказу Id: ${orderId}.' dispute_started: '${who} Скоро к вам придет решатель, когда он будет назначен на ваш спор, бот сообщит вам его/ее имя пользователя, только он/она сможет прийти к вам. Вы можете написать ему напрямую, но если он свяжется с вами первым, вы должны попросить его сказать вам, какой токен у вашего спора, ваш токен: ${token}.' diff --git a/locales/uk.yaml b/locales/uk.yaml index 5793167b..60ef8e2c 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -201,16 +201,16 @@ order_detail: | seller: продавець buyer: покупець dispute_started_channel: | - ${type} @${initiatorUser.username} TG ID: ${initiatorTgId} - ініціював диспут з @${counterPartyUser.username} TG ID: ${counterPartyUserTgId} щодо заявки: + ${type} @${initiatorUser} TG ID: ${initiatorTgId} + ініціював диспут з @${counterPartyUser} TG ID: ${counterPartyUserTgId} щодо заявки: ${detailedOrder} Seller Token: ${sellerToken} Buyer Token: ${buyerToken} - @${initiatorUser.username} вже мав спір: ${initiatorUser.disputes} - @${counterPartyUser.username} вже мав спір: ${counterPartyUser.disputes} + @${initiatorUser} вже мав спір: ${buyerDdisputes} + @${counterPartyUser} вже мав спір: ${sellerDisputes} you_started: '🥴 Ви почали оскарження свого замовлення з ідентифікатором: ${orderId}.' counterpart_started: '🥴 Ваш контрагент розпочав суперечку щодо вашого замовлення з ідентифікатором: ${orderId}.' dispute_started: "${who} Розв'язувач незабаром прийде до вас, коли його/її буде призначено до вашої суперечки, бот повідомить вам своє ім'я користувача, лише він/вона зможе прийти до вас. Ви можете написати йому/їй безпосередньо, але якщо він/вона зв’яжеться з вами першим, ви повинні попросити його/її сказати вам, що є символом вашої суперечки, ваш маркер: ${token}." diff --git a/tests/bot/modules/dispute/messages.js b/tests/bot/modules/dispute/messages.js index dd876916..1c0d4ba2 100644 --- a/tests/bot/modules/dispute/messages.js +++ b/tests/bot/modules/dispute/messages.js @@ -16,16 +16,16 @@ const mockI18n = { t: sinon.stub((key, params) => { switch (key) { case 'dispute_started_channel': - return `User ${params.type} @${params.initiatorUser.username} TG ID: ${params.initiatorTgId} - has started a dispute with @${params.counterPartyUser.username} TG ID: ${params.counterPartyUserTgId} for the order + return `User ${params.type} @${params.initiatorUser} TG ID: ${params.initiatorTgId} + has started a dispute with @${params.counterPartyUser} TG ID: ${params.counterPartyUserTgId} for the order ${params.detailedOrder} Seller Token: ${params.sellerToken} Buyer Token: ${params.buyerToken} - @${params.initiatorUser.username} has been involved in ${params.buyerDisputes} disputes - @${params.counterPartyUser.username} has been involved in ${params.sellerDisputes} disputes`; + @${params.initiatorUser} has been involved in ${params.buyerDisputes} disputes + @${params.counterPartyUser} has been involved in ${params.sellerDisputes} disputes`; case 'seller': return 'seller'; case 'buyer': diff --git a/util/index.ts b/util/index.ts index 0b1bc7e7..902d9e4c 100644 --- a/util/index.ts +++ b/util/index.ts @@ -368,7 +368,7 @@ const getDetailedOrder = (i18n: I18nContext, order: IOrder, buyer: UserDocument, takenAt = sanitizeMD(takenAt); const previousDisputeStatus = sanitizeMD(order.previous_dispute_status); const status = sanitizeMD(order.status); - const fee = order.fee ? Number(order.fee) : ''; + const fee = order.fee ? sanitizeMD(Number(order.fee)) : ''; const creator = order.creator_id === buyerId ? buyerUsername : sellerUsername; const buyerAge = buyer? getUserAge(buyer) : '';