From d25c37f3eddada2bca64fdc1a52b041251694ac3 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Tue, 6 Aug 2024 14:23:47 +0200 Subject: [PATCH 01/22] bot/modules/community: convert to TS Convert bot/modules/community module to TypeScript. --- .../community/{actions.js => actions.ts} | 41 ++- .../community/{commands.js => commands.ts} | 58 ++-- bot/modules/community/communityContext.ts | 35 +++ bot/modules/community/{index.js => index.ts} | 18 +- .../community/{messages.js => messages.ts} | 44 +-- ...unityAdmin.js => scenes.communityAdmin.ts} | 23 +- .../community/{scenes.js => scenes.ts} | 252 +++++++++--------- 7 files changed, 267 insertions(+), 204 deletions(-) rename bot/modules/community/{actions.js => actions.ts} (70%) rename bot/modules/community/{commands.js => commands.ts} (78%) create mode 100644 bot/modules/community/communityContext.ts rename bot/modules/community/{index.js => index.ts} (87%) rename bot/modules/community/{messages.js => messages.ts} (82%) rename bot/modules/community/{scenes.communityAdmin.js => scenes.communityAdmin.ts} (64%) rename bot/modules/community/{scenes.js => scenes.ts} (80%) diff --git a/bot/modules/community/actions.js b/bot/modules/community/actions.ts similarity index 70% rename from bot/modules/community/actions.js rename to bot/modules/community/actions.ts index dfd30f70..734fa1f8 100644 --- a/bot/modules/community/actions.js +++ b/bot/modules/community/actions.ts @@ -1,6 +1,15 @@ -const { Community, Order, User } = require('../../../models'); +import { ExtraReplyMessage } from 'telegraf/typings/telegram-types'; +import { Community, Order, User } from '../../../models'; +import { MainContext } from '../../start'; +import { CommunityContext } from './communityContext'; -const getOrdersNDays = async (days, communityId) => { +interface OrderFilter { + status: string; + created_at: any; + community_id?: string; +} + +const getOrdersNDays = async (days: number, communityId: (string | undefined)) => { const yesterday = new Date(); yesterday.setHours(yesterday.getHours() - days * 24); const filter = { @@ -8,13 +17,13 @@ const getOrdersNDays = async (days, communityId) => { created_at: { $gte: yesterday, }, - }; + } as OrderFilter; if (communityId) filter.community_id = communityId; return Order.count(filter); }; -const getVolumeNDays = async (days, communityId) => { +const getVolumeNDays = async (days: number, communityId: (string | undefined)) => { const yesterday = new Date(); yesterday.setHours(yesterday.getHours() - days * 24); const filter = { @@ -22,7 +31,7 @@ const getVolumeNDays = async (days, communityId) => { created_at: { $gte: yesterday, }, - }; + } as OrderFilter; if (communityId) filter.community_id = communityId; const [row] = await Order.aggregate([ { @@ -42,14 +51,18 @@ const getVolumeNDays = async (days, communityId) => { return row.amount; }; -exports.onCommunityInfo = async ctx => { - const commId = ctx.match[1]; +export const onCommunityInfo = async (ctx: MainContext) => { + const commId = ctx.match?.[1]; const community = await Community.findById(commId); + if(community === null) + throw new Error("community not found"); const userCount = await User.count({ default_community_id: commId }); const orderCount = await getOrdersNDays(1, commId); const volume = await getVolumeNDays(1, commId); const creator = await User.findById(community.creator_id); + if(creator === null) + throw new Error("creator not found"); let orderChannelsText = ''; if (community.order_channels.length === 1) { @@ -58,7 +71,7 @@ exports.onCommunityInfo = async ctx => { orderChannelsText = `${community.order_channels[0].name} (${community.order_channels[0].type}) ${community.order_channels[1].name} (${community.order_channels[1].type})`; } - const options = { year: 'numeric', month: 'short', day: 'numeric' }; + const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' }; const formatDate = community.created_at.toLocaleDateString('en-US', options); const rows = []; @@ -84,12 +97,12 @@ exports.onCommunityInfo = async ctx => { const text = `${community.name}: ${community.group} \nCreator: @${creator.username} \nOrder Channels: ${orderChannelsText} \nFee: ${community.fee} \nCreated At: ${formatDate}`; await ctx.reply(text, { reply_markup: { inline_keyboard: rows }, - }); + } as ExtraReplyMessage); }; -exports.onSetCommunity = async ctx => { - const tgId = ctx.update.callback_query.from.id; - const defaultCommunityId = ctx.match[1]; +export const onSetCommunity = async (ctx: CommunityContext) => { + const tgId = (ctx.update as any).callback_query.from.id; + const defaultCommunityId = ctx.match?.[1]; await User.findOneAndUpdate( { tg_id: tgId }, { default_community_id: defaultCommunityId } @@ -97,9 +110,9 @@ exports.onSetCommunity = async ctx => { await ctx.reply(ctx.i18n.t('operation_successful')); }; -exports.withdrawEarnings = async ctx => { +export const withdrawEarnings = async (ctx: CommunityContext) => { ctx.deleteMessage(); - const community = await Community.findById(ctx.match[1]); + const community = await Community.findById(ctx.match?.[1]); ctx.scene.enter('ADD_EARNINGS_INVOICE_WIZARD_SCENE_ID', { community, }); diff --git a/bot/modules/community/commands.js b/bot/modules/community/commands.ts similarity index 78% rename from bot/modules/community/commands.js rename to bot/modules/community/commands.ts index 47e6f997..f378c1db 100644 --- a/bot/modules/community/commands.js +++ b/bot/modules/community/commands.ts @@ -1,11 +1,14 @@ /* eslint-disable no-underscore-dangle */ // @ts-check -const { logger } = require('../../../logger'); -const { showUserCommunitiesMessage } = require('./messages'); -const { Community, Order } = require('../../../models'); -const { validateParams, validateObjectId } = require('../../validations'); - -async function getOrderCountByCommunity() { +import { logger } from '../../../logger'; +import { showUserCommunitiesMessage } from './messages'; +import { Community, Order } from '../../../models'; +import { validateParams, validateObjectId } from '../../validations'; +import { MainContext } from '../../start'; +import { CommunityContext } from './communityContext'; +import { Telegraf } from 'telegraf'; + +async function getOrderCountByCommunity(): Promise { const data = await Order.aggregate([ { $group: { _id: '$community_id', total: { $count: {} } } }, ]); @@ -15,7 +18,7 @@ async function getOrderCountByCommunity() { }, {}); } -async function findCommunities(currency) { +async function findCommunities(currency: string) { const communities = await Community.find({ currencies: currency, public: true, @@ -28,21 +31,18 @@ async function findCommunities(currency) { }); } -exports.setComm = async ctx => { +export const setComm = async (ctx: MainContext) => { try { const { user } = ctx; - const [groupName] = await validateParams( + const [groupName] = (await validateParams( ctx, 2, '\\<_@communityGroupName \\| telegram\\-group\\-id / off_\\>' - ); - if (!groupName) { - return; - } + ))!; if (groupName === 'off') { - user.default_community_id = null; + user.default_community_id = undefined; await user.save(); return await ctx.reply(ctx.i18n.t('no_default_community')); } @@ -67,15 +67,14 @@ exports.setComm = async ctx => { } }; -exports.communityAdmin = async ctx => { +export const communityAdmin = async (ctx: CommunityContext) => { try { - const [group] = await validateParams(ctx, 2, '\\<_community_\\>'); - if (!group) return; + const [group] = (await validateParams(ctx, 2, '\\<_community_\\>'))!; const creator_id = ctx.user.id; const [community] = await Community.find({ group, creator_id }); if (!community) throw new Error('CommunityNotFound'); await ctx.scene.enter('COMMUNITY_ADMIN', { community }); - } catch (err) { + } catch (err: any) { switch (err.message) { case 'CommunityNotFound': { return ctx.reply(ctx.i18n.t('community_not_found')); @@ -87,7 +86,7 @@ exports.communityAdmin = async ctx => { } }; -exports.myComms = async ctx => { +export const myComms = async (ctx: MainContext) => { try { const { user } = ctx; @@ -102,10 +101,9 @@ exports.myComms = async ctx => { } }; -exports.findCommunity = async ctx => { +export const findCommunity = async (ctx: CommunityContext) => { try { - const [fiatCode] = await validateParams(ctx, 2, '\\<_fiat code_\\>'); - if (!fiatCode) return; + const [fiatCode] = (await validateParams(ctx, 2, '\\<_fiat code_\\>'))!; const communities = await findCommunities(fiatCode.toUpperCase()); if (!communities.length) { @@ -132,11 +130,11 @@ exports.findCommunity = async ctx => { } }; -exports.updateCommunity = async (ctx, id, field, bot) => { +export const updateCommunity = async (ctx: CommunityContext, id: string, field: string, bot?: Telegraf) => { try { ctx.deleteMessage(); if (!id) return; - const tgUser = ctx.update.callback_query.from; + const tgUser = (ctx.update as any).callback_query.from; if (!tgUser) return; const { user } = ctx; @@ -203,10 +201,12 @@ exports.updateCommunity = async (ctx, id, field, bot) => { } }; -exports.deleteCommunity = async ctx => { +export const deleteCommunity = async (ctx: CommunityContext) => { try { ctx.deleteMessage(); - const id = ctx.match[1]; + const id = ctx.match?.[1]; + if(id === undefined) + throw new Error("id is undefined"); if (!(await validateObjectId(ctx, id))) return; const community = await Community.findOne({ @@ -225,10 +225,12 @@ exports.deleteCommunity = async ctx => { } }; -exports.changeVisibility = async ctx => { +export const changeVisibility = async (ctx: CommunityContext) => { try { ctx.deleteMessage(); - const id = ctx.match[1]; + const id = ctx.match?.[1]; + if(id === undefined) + throw new Error("id is undefined"); if (!(await validateObjectId(ctx, id))) return; const community = await Community.findOne({ diff --git a/bot/modules/community/communityContext.ts b/bot/modules/community/communityContext.ts new file mode 100644 index 00000000..b47656a0 --- /dev/null +++ b/bot/modules/community/communityContext.ts @@ -0,0 +1,35 @@ +import { MainContext } from '../../start'; +import { SceneContextScene, WizardContextWizard, WizardSessionData } from 'telegraf/typings/scenes'; +import { Update, Message } from 'telegraf/typings/core/types/typegram'; +import { Scenes, Telegraf } from 'telegraf'; +import { ICommunity, IOrderChannel, IUsernameId } from '../../../models/community'; + +export interface CommunityContext extends MainContext { + scene: SceneContextScene; + wizard: CommunityWizard; + message: (Update.New & Update.NonChannel & Message.TextMessage) | undefined; +} + +export interface CommunityWizardState { + name: string; + currencies: any; + group: any; + channels: IOrderChannel[]; + fee: number; + solvers: IUsernameId[]; + disputeChannel: any; + user: any; + statusMessage: any; + currentStatusText: string; + community: ICommunity; + bot: Telegraf; + message: Message.TextMessage | undefined; + error?: any; + updateUI: (() => Promise); + handler?: ((ctx: CommunityContext) => Promise); +} + +export interface CommunityWizard extends WizardContextWizard { + state: CommunityWizardState; + bot: Telegraf; +} diff --git a/bot/modules/community/index.js b/bot/modules/community/index.ts similarity index 87% rename from bot/modules/community/index.js rename to bot/modules/community/index.ts index eb59def8..048faf82 100644 --- a/bot/modules/community/index.js +++ b/bot/modules/community/index.ts @@ -1,15 +1,13 @@ // @ts-check +import { Telegraf } from 'telegraf'; const { userMiddleware } = require('../../middleware/user'); -const actions = require('./actions'); -const commands = require('./commands'); -const { - earningsMessage, - updateCommunityMessage, - sureMessage, -} = require('./messages'); -exports.Scenes = require('./scenes'); +import * as actions from './actions'; +import * as commands from './commands'; +import { earningsMessage, updateCommunityMessage, sureMessage } from './messages'; +import { CommunityContext } from './communityContext'; +import * as Scenes from './scenes'; -exports.configure = bot => { +export const configure = (bot: Telegraf) => { bot.command('mycomm', userMiddleware, commands.communityAdmin); bot.command('mycomms', userMiddleware, commands.myComms); bot.command('community', userMiddleware, async ctx => { @@ -89,3 +87,5 @@ exports.configure = bot => { commands.changeVisibility ); }; + +export { Scenes }; diff --git a/bot/modules/community/messages.js b/bot/modules/community/messages.ts similarity index 82% rename from bot/modules/community/messages.js rename to bot/modules/community/messages.ts index 85d3bfc4..386d3129 100644 --- a/bot/modules/community/messages.js +++ b/bot/modules/community/messages.ts @@ -1,18 +1,22 @@ -const { logger } = require('../../../logger'); -const { Community, PendingPayment } = require('../../../models'); +import { logger } from '../../../logger'; +import { Community, PendingPayment } from '../../../models'; +import { CommunityWizardState } from './communityContext'; +import { ICommunity } from '../../../models/community'; +import { I18nContext } from '@grammyjs/i18n'; +import { MainContext } from '../../start'; -exports.createCommunityWizardStatus = (i18n, state) => { +export const createCommunityWizardStatus = (i18n: I18nContext, state: CommunityWizardState) => { try { - let { name, currencies, group, channels, fee, solvers } = state; + let { name, currencies, group } = state; name = state.name || '__'; currencies = state.currencies && state.currencies.join(', '); currencies = currencies || '__'; group = state.group || '__'; - channels = - state.channels && state.channels.map(channel => channel.name).join(', '); + let channels = + state.channels && state.channels.map((channel: { name: string }) => channel.name).join(', '); channels = channels || '__'; - fee = state.fee || '__'; - solvers = + const fee = String(state.fee) || '__'; + let solvers = state.solvers && state.solvers.map(solver => solver.username).join(', '); solvers = solvers || '__'; const text = [ @@ -34,11 +38,13 @@ exports.createCommunityWizardStatus = (i18n, state) => { } }; -exports.updateCommunityMessage = async ctx => { +export const updateCommunityMessage = async (ctx: MainContext) => { try { await ctx.deleteMessage(); - const id = ctx.match[1]; + const id = ctx.match?.[1]; const community = await Community.findById(id); + if(community == null) + throw new Error("community was not found"); let text = ctx.i18n.t('community') + `: ${community.name}\n`; text += ctx.i18n.t('what_to_do'); const visibilityText = community.public @@ -105,7 +111,7 @@ exports.updateCommunityMessage = async ctx => { } }; -exports.listCommunitiesMessage = async (ctx, communities) => { +export const listCommunitiesMessage = async (ctx: MainContext, communities: ICommunity[]) => { try { let message = ''; communities.forEach(community => { @@ -130,9 +136,9 @@ exports.listCommunitiesMessage = async (ctx, communities) => { } }; -exports.earningsMessage = async ctx => { +export const earningsMessage = async (ctx: MainContext) => { try { - const communityId = ctx.match[1]; + const communityId = ctx.match?.[1]; // We check if there is a payment scheduled for this community const isScheduled = await PendingPayment.findOne({ community_id: communityId, @@ -143,6 +149,8 @@ exports.earningsMessage = async ctx => { return await ctx.reply(ctx.i18n.t('invoice_already_being_paid')); const community = await Community.findById(communityId); + if(community == null) + throw new Error("community was not found"); const button = community.earnings > 0 ? { @@ -157,7 +165,7 @@ exports.earningsMessage = async ctx => { ], }, } - : null; + : undefined; await ctx.reply( ctx.i18n.t('current_earnings', { ordersToRedeem: community.orders_to_redeem, @@ -170,7 +178,7 @@ exports.earningsMessage = async ctx => { } }; -exports.showUserCommunitiesMessage = async (ctx, communities) => { +export const showUserCommunitiesMessage = async (ctx: MainContext, communities: ICommunity[]) => { try { const buttons = []; while (communities.length > 0) { @@ -194,7 +202,7 @@ exports.showUserCommunitiesMessage = async (ctx, communities) => { } }; -exports.wizardCommunityWrongPermission = async (ctx, channel, response) => { +export const wizardCommunityWrongPermission = async (ctx: MainContext, channel: string, response: string) => { try { if (response.indexOf('bot was kicked from the supergroup chat') !== -1) { await ctx.reply(ctx.i18n.t('bot_kicked')); @@ -220,10 +228,10 @@ exports.wizardCommunityWrongPermission = async (ctx, channel, response) => { } }; -exports.sureMessage = async ctx => { +export const sureMessage = async (ctx: MainContext) => { try { await ctx.deleteMessage(); - const id = ctx.match[1]; + const id = ctx.match?.[1]; await ctx.reply(ctx.i18n.t('are_you_sure'), { reply_markup: { inline_keyboard: [ diff --git a/bot/modules/community/scenes.communityAdmin.js b/bot/modules/community/scenes.communityAdmin.ts similarity index 64% rename from bot/modules/community/scenes.communityAdmin.js rename to bot/modules/community/scenes.communityAdmin.ts index 39d19568..02c65ec2 100644 --- a/bot/modules/community/scenes.communityAdmin.js +++ b/bot/modules/community/scenes.communityAdmin.ts @@ -1,10 +1,11 @@ -const { Scenes } = require('telegraf'); +import { Scenes } from 'telegraf'; +import { CommunityContext } from './communityContext'; const CommunityEvents = require('../events/community'); -module.exports = () => { - const scene = new Scenes.WizardScene('COMMUNITY_ADMIN', async ctx => { - const { community } = ctx.scene.state; +const communityAdmin = () => { + const scene = new Scenes.WizardScene('COMMUNITY_ADMIN', async (ctx: CommunityContext) => { + const { community } = ctx.scene.state as any; const str = ctx.i18n.t('community_admin', { community }); await ctx.reply(str, { parse_mode: 'HTML' }); }); @@ -14,13 +15,13 @@ module.exports = () => { await ctx.reply(str, { parse_mode: 'HTML' }); }); - scene.command('/setnpub', async ctx => { + scene.command('/setnpub', async (ctx: CommunityContext) => { try { const NostrLib = require('../nostr/lib'); - const [, npub] = ctx.message.text.trim().split(' '); + const [, npub] = ctx.message!.text.trim().split(' '); const hex = NostrLib.decodeNpub(npub); if (!hex) throw new Error('NpubNotValid'); - const { community } = ctx.scene.state; + const { community } = ctx.scene.state as any; community.nostr_public_key = hex; await community.save(); await ctx.reply(ctx.i18n.t('community_npub_updated', { npub })); @@ -28,10 +29,12 @@ module.exports = () => { } catch (err) { return ctx.reply(ctx.i18n.t('npub_not_valid'), { parse_mode: 'HTML', - disable_web_page_preview: true, - }); + link_preview_options: { is_disabled: true }, + } as any); } }); return scene; -}; +} + +export { communityAdmin }; diff --git a/bot/modules/community/scenes.js b/bot/modules/community/scenes.ts similarity index 80% rename from bot/modules/community/scenes.js rename to bot/modules/community/scenes.ts index e13f16b0..6cbb8bb6 100644 --- a/bot/modules/community/scenes.js +++ b/bot/modules/community/scenes.ts @@ -1,26 +1,22 @@ -const { Scenes } = require('telegraf'); -const { logger } = require('../../../logger'); -const { Community, User, PendingPayment } = require('../../../models'); +import { Scenes } from 'telegraf'; +import { logger } from '../../../logger'; +import { Community, User, PendingPayment } from '../../../models'; +import { IOrderChannel, IUsernameId } from '../../../models/community'; const { isPendingPayment } = require('../../../ln'); -const { - isGroupAdmin, - itemsFromMessage, - removeAtSymbol, -} = require('../../../util'); -const messages = require('../../messages'); -const { isValidInvoice } = require('../../validations'); -const { - createCommunityWizardStatus, - wizardCommunityWrongPermission, -} = require('./messages'); +import { isGroupAdmin, itemsFromMessage, removeAtSymbol } from '../../../util'; +import * as messages from '../../messages'; +import { isValidInvoice } from '../../validations'; +import { createCommunityWizardStatus, wizardCommunityWrongPermission } from './messages'; +import { CommunityContext } from './communityContext'; +import * as commAdmin from './scenes.communityAdmin'; const CURRENCIES = parseInt(process.env.COMMUNITY_CURRENCIES || '10'); -exports.communityAdmin = require('./scenes.communityAdmin')(); +export const communityAdmin = commAdmin.communityAdmin(); -exports.communityWizard = new Scenes.WizardScene( +export const communityWizard = new Scenes.WizardScene( 'COMMUNITY_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { if (ctx.message === undefined) return ctx.scene.leave(); @@ -37,27 +33,27 @@ exports.communityWizard = new Scenes.WizardScene( } = ctx.wizard.state; if (!statusMessage) { - const { text } = createCommunityWizardStatus( + const status = createCommunityWizardStatus( ctx.i18n, ctx.wizard.state ); - const res = await ctx.reply(text); - ctx.wizard.state.currentStatusText = text; + const res = await ctx.reply(status!.text); + ctx.wizard.state.currentStatusText = status!.text; ctx.wizard.state.statusMessage = res; ctx.wizard.state.updateUI = async () => { try { - const { text } = createCommunityWizardStatus( + const status = createCommunityWizardStatus( ctx.i18n, ctx.wizard.state ); - if (ctx.wizard.state.currentStatusText === text) return; + if (ctx.wizard.state.currentStatusText === status!.text) return; await ctx.telegram.editMessageText( res.chat.id, res.message_id, - null, - text + undefined, + status!.text ); - ctx.wizard.state.currentStatusText = text; + ctx.wizard.state.currentStatusText = status!.text; } catch (err) { logger.error(err); } @@ -91,7 +87,7 @@ exports.communityWizard = new Scenes.WizardScene( ); return ctx.scene.leave(); - } catch (error) { + } catch (error: any) { const errString = error.toString(); logger.error(error); ctx.scene.leave(); @@ -108,7 +104,8 @@ exports.communityWizard = new Scenes.WizardScene( delete ctx.wizard.state.handler; } ctx.wizard.selectStep(0); - return ctx.wizard.steps[ctx.wizard.cursor](ctx); + // use ['steps'] syntax as steps is private property and TypeScript would complain + return ctx.wizard['steps'][ctx.wizard.cursor](ctx); } catch (err) { logger.error(err); return ctx.scene.leave(); @@ -117,11 +114,11 @@ exports.communityWizard = new Scenes.WizardScene( ); const createCommunitySteps = { - async name(ctx) { + async name(ctx: CommunityContext) { const prompt = await createCommunityPrompts.name(ctx); - ctx.wizard.state.handler = async ctx => { - const { text } = ctx.message; + ctx.wizard.state.handler = async (ctx: CommunityContext) => { + const text = ctx?.message?.text; if (!text) { await ctx.deleteMessage(); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); @@ -129,13 +126,13 @@ const createCommunitySteps = { ctx.wizard.state.error = null; const name = text.trim(); if (!name) { - ctx.telegram.deleteMessage(ctx.chat.id, ctx.message.message_id); + ctx.telegram.deleteMessage(ctx.chat!.id, ctx.message!.message_id); ctx.wizard.state.error = ctx.i18n.t('wizard_community_enter_name'); return await ctx.wizard.state.updateUI(); } const length = 30; if (name.length > length) { - ctx.telegram.deleteMessage(ctx.chat.id, ctx.message.message_id); + ctx.telegram.deleteMessage(ctx.chat!.id, ctx.message!.message_id); ctx.wizard.state.error = ctx.i18n.t( ctx.i18n.t('wizard_community_too_long_name', { length }) ); @@ -144,19 +141,19 @@ const createCommunitySteps = { ctx.wizard.state.name = name; await ctx.wizard.state.updateUI(); await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); }; return ctx.wizard.next(); }, - async currencies(ctx) { + async currencies(ctx: CommunityContext) { const prompt = await createCommunityPrompts.currencies(ctx); ctx.wizard.state.handler = async ctx => { - const { text } = ctx.message; + const { text } = ctx.message!; if (!text) { await ctx.deleteMessage(); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); @@ -167,8 +164,8 @@ const createCommunitySteps = { const max = CURRENCIES; if (currencies.length > max) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); ctx.wizard.state.error = ctx.i18n.t('max_allowed', { max }); return await ctx.wizard.state.updateUI(); @@ -176,20 +173,20 @@ const createCommunitySteps = { ctx.wizard.state.currencies = currencies; await ctx.wizard.state.updateUI(); await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); }; return ctx.wizard.next(); }, - async group(ctx) { + async group(ctx: CommunityContext) { const prompt = await createCommunityPrompts.group(ctx); ctx.wizard.state.handler = async ctx => { try { - const group = ctx.message.text.trim(); + const group = ctx.message?.text.trim(); if (!group) { await ctx.deleteMessage(); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); @@ -201,8 +198,8 @@ const createCommunitySteps = { if (!isGroupOk.success) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); await wizardCommunityWrongPermission(ctx, group, isGroupOk.message); @@ -212,11 +209,11 @@ const createCommunitySteps = { ctx.wizard.state.group = group; await ctx.wizard.state.updateUI(); await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); - } catch (error) { + } catch (error: any) { ctx.wizard.state.error = error.toString(); return await ctx.wizard.state.updateUI(); } @@ -224,33 +221,33 @@ const createCommunitySteps = { return ctx.wizard.next(); }, - async channels(ctx) { + async channels(ctx: CommunityContext) { ctx.wizard.state.handler = async ctx => { - const { text } = ctx.message; + const text = ctx.message?.text; if (!text) { await ctx.deleteMessage(); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); } const { bot, user } = ctx.wizard.state; const chan = itemsFromMessage(text); - ctx.wizard.state.channels = chan; + //ctx.wizard.state.channels = chan; // ??? if (chan.length > 2) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); ctx.wizard.state.error = ctx.i18n.t( 'wizard_community_one_or_two_channels' ); return await ctx.wizard.state.updateUI(); } - const orderChannels = []; + const orderChannels: IOrderChannel[] = []; if (chan.length === 1) { const isGroupOk = await isGroupAdmin(chan[0], user, bot.telegram); if (!isGroupOk.success) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); await wizardCommunityWrongPermission(ctx, chan[0], isGroupOk.message); @@ -259,14 +256,14 @@ const createCommunitySteps = { const channel = { name: chan[0], type: 'mixed', - }; + } as IOrderChannel; orderChannels.push(channel); } else { let isGroupOk = await isGroupAdmin(chan[0], user, bot.telegram); if (!isGroupOk.success) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); await wizardCommunityWrongPermission(ctx, chan[0], isGroupOk.message); @@ -275,8 +272,8 @@ const createCommunitySteps = { isGroupOk = await isGroupAdmin(chan[1], user, bot.telegram); if (!isGroupOk.success) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); await wizardCommunityWrongPermission(ctx, chan[1], isGroupOk.message); @@ -285,19 +282,19 @@ const createCommunitySteps = { const channel1 = { name: chan[0], type: 'buy', - }; + } as IOrderChannel; const channel2 = { name: chan[1], type: 'sell', - }; + } as IOrderChannel; orderChannels.push(channel1); orderChannels.push(channel2); } ctx.wizard.state.channels = orderChannels; await ctx.wizard.state.updateUI(); await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); return await ctx.telegram.deleteMessage( prompt.chat.id, @@ -307,34 +304,35 @@ const createCommunitySteps = { const prompt = await createCommunityPrompts.channels(ctx); return ctx.wizard.next(); }, - async fee(ctx) { + async fee(ctx: CommunityContext) { ctx.wizard.state.handler = async ctx => { - const { text } = ctx.message; + const text = ctx.message?.text; if (!text) { await ctx.deleteMessage(); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); } - if (isNaN(text)) { + const num = Number(text); + if (isNaN(num)) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); ctx.wizard.state.error = ctx.i18n.t('not_number'); return await ctx.wizard.state.updateUI(); } - if (text < 0 || text > 100) { + if (num < 0 || num > 100) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); ctx.wizard.state.error = ctx.i18n.t('wizard_community_wrong_percent'); return await ctx.wizard.state.updateUI(); } - ctx.wizard.state.fee = text; + ctx.wizard.state.fee = num; await ctx.wizard.state.updateUI(); await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); return await ctx.telegram.deleteMessage( prompt.chat.id, @@ -344,9 +342,9 @@ const createCommunitySteps = { const prompt = await createCommunityPrompts.fee(ctx); return ctx.wizard.next(); }, - async solvers(ctx) { + async solvers(ctx: CommunityContext) { ctx.wizard.state.handler = async ctx => { - const { text } = ctx.message; + const text = ctx.message?.text; if (!text) { await ctx.deleteMessage(); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); @@ -361,13 +359,13 @@ const createCommunitySteps = { solvers.push({ id: user._id, username: user.username, - }); + } as IUsernameId); } } } else { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); ctx.wizard.state.error = ctx.i18n.t( 'wizard_community_must_enter_names' @@ -378,8 +376,8 @@ const createCommunitySteps = { await ctx.wizard.state.updateUI(); await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); return await ctx.telegram.deleteMessage( prompt.chat.id, @@ -389,9 +387,9 @@ const createCommunitySteps = { const prompt = await createCommunityPrompts.solvers(ctx); return ctx.wizard.next(); }, - async disputeChannel(ctx) { + async disputeChannel(ctx: CommunityContext) { ctx.wizard.state.handler = async ctx => { - const channel = ctx.message.text.trim(); + const channel = ctx.message!.text.trim(); if (!channel) { await ctx.deleteMessage(); return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); @@ -401,8 +399,8 @@ const createCommunitySteps = { const isGroupOk = await isGroupAdmin(channel, user, bot.telegram); if (!isGroupOk.success) { await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); await wizardCommunityWrongPermission(ctx, channel, isGroupOk.message); @@ -411,8 +409,8 @@ const createCommunitySteps = { ctx.wizard.state.disputeChannel = channel; await ctx.wizard.state.updateUI(); await ctx.telegram.deleteMessage( - ctx.message.chat.id, - ctx.message.message_id + ctx.message!.chat.id, + ctx.message!.message_id ); return await ctx.telegram.deleteMessage( prompt.chat.id, @@ -425,32 +423,32 @@ const createCommunitySteps = { }; const createCommunityPrompts = { - async name(ctx) { + async name(ctx: CommunityContext) { return ctx.reply(ctx.i18n.t('wizard_community_enter_name')); }, - async currencies(ctx) { + async currencies(ctx: CommunityContext) { return ctx.reply(ctx.i18n.t('wizard_community_enter_currency')); }, - async group(ctx) { + async group(ctx: CommunityContext) { return ctx.reply(ctx.i18n.t('wizard_community_enter_group')); }, - async channels(ctx) { + async channels(ctx: CommunityContext) { return ctx.reply(ctx.i18n.t('wizard_community_enter_order_channels')); }, - async fee(ctx) { + async fee(ctx: CommunityContext) { return ctx.reply(ctx.i18n.t('wizard_community_enter_fee_percent')); }, - async solvers(ctx) { + async solvers(ctx: CommunityContext) { return ctx.reply(ctx.i18n.t('wizard_community_enter_solvers')); }, - async disputeChannel(ctx) { + async disputeChannel(ctx: CommunityContext) { return ctx.reply(ctx.i18n.t('wizard_community_enter_solvers_channel')); }, }; -exports.updateNameCommunityWizard = new Scenes.WizardScene( +export const updateNameCommunityWizard = new Scenes.WizardScene( 'UPDATE_NAME_COMMUNITY_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { community } = ctx.wizard.state; let message = ctx.i18n.t('name') + ': ' + community.name + '\n\n'; @@ -464,7 +462,7 @@ exports.updateNameCommunityWizard = new Scenes.WizardScene( ctx.scene.leave(); } }, - async ctx => { + async (ctx: CommunityContext) => { try { if (ctx.message === undefined) return ctx.scene.leave(); @@ -490,9 +488,9 @@ exports.updateNameCommunityWizard = new Scenes.WizardScene( } ); -exports.updateGroupCommunityWizard = new Scenes.WizardScene( +export const updateGroupCommunityWizard = new Scenes.WizardScene( 'UPDATE_GROUP_COMMUNITY_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { community } = ctx.wizard.state; let message = ctx.i18n.t('group') + ': ' + community.group + '\n\n'; @@ -534,9 +532,9 @@ exports.updateGroupCommunityWizard = new Scenes.WizardScene( } ); -exports.updateCurrenciesCommunityWizard = new Scenes.WizardScene( +export const updateCurrenciesCommunityWizard = new Scenes.WizardScene( 'UPDATE_CURRENCIES_COMMUNITY_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { community } = ctx.wizard.state; const currencies = community.currencies.join(', '); @@ -551,7 +549,7 @@ exports.updateCurrenciesCommunityWizard = new Scenes.WizardScene( ctx.scene.leave(); } }, - async ctx => { + async (ctx: CommunityContext) => { try { if (ctx.message === undefined) { return ctx.scene.leave(); @@ -576,9 +574,9 @@ exports.updateCurrenciesCommunityWizard = new Scenes.WizardScene( } ); -exports.updateChannelsCommunityWizard = new Scenes.WizardScene( +export const updateChannelsCommunityWizard = new Scenes.WizardScene( 'UPDATE_CHANNELS_COMMUNITY_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { community } = ctx.wizard.state; const channels = community.order_channels @@ -605,7 +603,7 @@ exports.updateChannelsCommunityWizard = new Scenes.WizardScene( const { community, bot, user } = ctx.wizard.state; - const orderChannels = []; + const orderChannels: IOrderChannel[] = []; if (chan.length === 1) { const isGroupOk = await isGroupAdmin(chan[0], user, bot.telegram); @@ -620,7 +618,7 @@ exports.updateChannelsCommunityWizard = new Scenes.WizardScene( const channel = { name: chan[0], type: 'mixed', - }; + } as IOrderChannel; orderChannels.push(channel); } else { let isGroupOk = await isGroupAdmin(chan[0], user, bot.telegram); @@ -646,11 +644,11 @@ exports.updateChannelsCommunityWizard = new Scenes.WizardScene( const channel1 = { name: chan[0], type: 'buy', - }; + } as IOrderChannel; const channel2 = { name: chan[1], type: 'sell', - }; + } as IOrderChannel; orderChannels.push(channel1); orderChannels.push(channel2); } @@ -660,7 +658,7 @@ exports.updateChannelsCommunityWizard = new Scenes.WizardScene( } }); - community.order_channels = orderChannels; + community.order_channels = orderChannels as any; await community.save(); await ctx.reply(ctx.i18n.t('operation_successful')); @@ -672,9 +670,9 @@ exports.updateChannelsCommunityWizard = new Scenes.WizardScene( } ); -exports.updateSolversCommunityWizard = new Scenes.WizardScene( +export const updateSolversCommunityWizard = new Scenes.WizardScene( 'UPDATE_SOLVERS_COMMUNITY_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { community } = ctx.wizard.state; const solvers = community.solvers @@ -691,11 +689,11 @@ exports.updateSolversCommunityWizard = new Scenes.WizardScene( ctx.scene.leave(); } }, - async ctx => { + async (ctx: CommunityContext) => { try { if (ctx.message === undefined) return ctx.scene.leave(); - const solvers = []; + const solvers: IUsernameId[] = []; const botUsers = []; const notBotUsers = []; const usernames = itemsFromMessage(ctx.message.text); @@ -704,11 +702,13 @@ exports.updateSolversCommunityWizard = new Scenes.WizardScene( for (let i = 0; i < usernames.length; i++) { const username = removeAtSymbol(usernames[i]); const user = await User.findOne({ username }); + if(user == null) + throw new Error("user not found"); if (user) { solvers.push({ id: user._id, username: user.username, - }); + } as IUsernameId); botUsers.push(username); } else { notBotUsers.push(username); @@ -726,7 +726,7 @@ exports.updateSolversCommunityWizard = new Scenes.WizardScene( ); const { community } = ctx.wizard.state; - community.solvers = solvers; + community.solvers = solvers as any; await community.save(); } @@ -743,9 +743,9 @@ exports.updateSolversCommunityWizard = new Scenes.WizardScene( } ); -exports.updateFeeCommunityWizard = new Scenes.WizardScene( +export const updateFeeCommunityWizard = new Scenes.WizardScene( 'UPDATE_FEE_COMMUNITY_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { community } = ctx.wizard.state; let message = ctx.i18n.t('fee') + ': ' + community.fee + '\n\n'; @@ -765,7 +765,7 @@ exports.updateFeeCommunityWizard = new Scenes.WizardScene( return ctx.scene.leave(); } - const fee = ctx.message.text.trim(); + const fee = Number(ctx.message.text.trim()); if (isNaN(fee)) { return await messages.mustBeANumber(ctx); @@ -788,9 +788,9 @@ exports.updateFeeCommunityWizard = new Scenes.WizardScene( } ); -exports.updateDisputeChannelCommunityWizard = new Scenes.WizardScene( +export const updateDisputeChannelCommunityWizard = new Scenes.WizardScene( 'UPDATE_DISPUTE_CHANNEL_COMMUNITY_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { community } = ctx.wizard.state; let message = @@ -808,7 +808,7 @@ exports.updateDisputeChannelCommunityWizard = new Scenes.WizardScene( ctx.scene.leave(); } }, - async ctx => { + async (ctx: CommunityContext) => { try { if (ctx.message === undefined) return ctx.scene.leave(); @@ -837,9 +837,9 @@ exports.updateDisputeChannelCommunityWizard = new Scenes.WizardScene( } ); -exports.addEarningsInvoiceWizard = new Scenes.WizardScene( +export const addEarningsInvoiceWizard = new Scenes.WizardScene( 'ADD_EARNINGS_INVOICE_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { community } = ctx.wizard.state; if (community.earnings === 0) return ctx.scene.leave(); @@ -853,7 +853,7 @@ exports.addEarningsInvoiceWizard = new Scenes.WizardScene( logger.error(error); } }, - async ctx => { + async (ctx: CommunityContext) => { try { if (ctx.message === undefined) return ctx.scene.leave(); const lnInvoice = ctx.message.text.trim(); @@ -877,6 +877,8 @@ exports.addEarningsInvoiceWizard = new Scenes.WizardScene( return await ctx.reply(ctx.i18n.t('invoice_already_being_paid')); const user = await User.findById(community.creator_id); + if(user === null) + throw new Error("user was not found"); logger.debug(`Creating pending payment for community ${community.id}`); const pp = new PendingPayment({ amount: community.earnings, From 8e1db87047a45b651c93df4c8cff5da7c35daa23 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 7 Aug 2024 10:37:44 +0200 Subject: [PATCH 02/22] bot/modules/dispute: convert to TS Convert bot/modules/disputemodule to TypeScript. --- .../dispute/{actions.js => actions.ts} | 23 +++++-- .../dispute/{commands.js => commands.ts} | 61 +++++++++++-------- bot/modules/dispute/{index.js => index.ts} | 8 ++- .../dispute/{messages.js => messages.ts} | 47 ++++++++------ models/dispute.ts | 4 +- 5 files changed, 86 insertions(+), 57 deletions(-) rename bot/modules/dispute/{actions.js => actions.ts} (67%) rename bot/modules/dispute/{commands.js => commands.ts} (69%) rename bot/modules/dispute/{index.js => index.ts} (58%) rename bot/modules/dispute/{messages.js => messages.ts} (78%) diff --git a/bot/modules/dispute/actions.js b/bot/modules/dispute/actions.ts similarity index 67% rename from bot/modules/dispute/actions.js rename to bot/modules/dispute/actions.ts index d672afbc..48e8d57b 100644 --- a/bot/modules/dispute/actions.js +++ b/bot/modules/dispute/actions.ts @@ -1,16 +1,21 @@ -const { User, Order, Dispute } = require('../../../models'); -const messages = require('./messages'); -const { validateAdmin } = require('../../validations'); +import { User, Order, Dispute } from '../../../models'; +import { MainContext } from '../../start'; +import * as messages from './messages'; +import { validateAdmin } from '../../validations'; const globalMessages = require('../../messages'); -exports.takeDispute = async ctx => { - const tgId = ctx.update.callback_query.from.id; +export const takeDispute = async (ctx: MainContext) : Promise => { + const tgId: string = (ctx.update as any).callback_query.from.id; const admin = await validateAdmin(ctx, tgId); if (!admin) return; - const orderId = ctx.match[1]; + const orderId = ctx.match?.[1]; // We check if this is a solver, the order must be from the same community const order = await Order.findOne({ _id: orderId }); + if(order === null) + throw new Error("order not found"); const dispute = await Dispute.findOne({ order_id: orderId }); + if(dispute === null) + throw new Error("dispute not found"); if (!admin.admin) { if (!order.community_id) return await globalMessages.notAuthorized(ctx, tgId); @@ -20,11 +25,17 @@ exports.takeDispute = async ctx => { } ctx.deleteMessage(); const solver = await User.findOne({ tg_id: tgId }); + if(solver === null) + throw new Error("solver not found"); if (dispute.status === 'RELEASED') return await messages.sellerReleased(ctx, solver); const buyer = await User.findOne({ _id: order.buyer_id }); + if(buyer === null) + throw new Error("buyer not found"); const seller = await User.findOne({ _id: order.seller_id }); + if(seller === null) + throw new Error("seller not found"); const initiator = order.buyer_dispute ? 'buyer' : 'seller'; const buyerDisputes = await Dispute.count({ $or: [{ buyer_id: buyer._id }, { seller_id: buyer._id }], diff --git a/bot/modules/dispute/commands.js b/bot/modules/dispute/commands.ts similarity index 69% rename from bot/modules/dispute/commands.js rename to bot/modules/dispute/commands.ts index 7f48be90..11c819d5 100644 --- a/bot/modules/dispute/commands.js +++ b/bot/modules/dispute/commands.ts @@ -1,27 +1,27 @@ -const { User, Dispute, Order } = require('../../../models'); -const { - validateParams, - validateObjectId, - validateDisputeOrder, -} = require('../../validations'); -const messages = require('./messages'); +import { MainContext } from "../../start"; + +import { User, Dispute, Order } from '../../../models'; +import { validateParams, validateObjectId, validateDisputeOrder } from '../../validations'; +import * as messages from './messages'; const globalMessages = require('../../messages'); -const { logger } = require('../../../logger'); -const { removeAtSymbol } = require('../../../util'); +import { logger } from '../../../logger'; +import { removeAtSymbol } from '../../../util'; -const dispute = async ctx => { +const dispute = async (ctx: MainContext) => { try { const { user } = ctx; - const [orderId] = await validateParams(ctx, 2, '\\<_order id_\\>'); - - if (!orderId) return; + const [orderId] = (await validateParams(ctx, 2, '\\<_order id_\\>'))!; + if (!(await validateObjectId(ctx, orderId))) return; const order = await validateDisputeOrder(ctx, user, orderId); - if (!order) return; + if (order === false) return; // Users can't initiate a dispute before this time - const secsUntilDispute = parseInt(process.env.DISPUTE_START_WINDOW); + const disputStartWindow = process.env.DISPUTE_START_WINDOW; + if(disputStartWindow === undefined) + throw new Error("DISPUTE_START_WINDOW environment variable not defined"); + const secsUntilDispute = parseInt(disputStartWindow); const time = new Date(); time.setSeconds(time.getSeconds() - secsUntilDispute); if (order.taken_at > time) { @@ -29,17 +29,23 @@ const dispute = async ctx => { } const buyer = await User.findOne({ _id: order.buyer_id }); + if(buyer === null) + throw new Error("buyer was not found"); const seller = await User.findOne({ _id: order.seller_id }); - let initiator = 'seller'; + if(seller === null) + throw new Error("seller was not found"); + let initiator: ('seller' | 'buyer') = 'seller'; if (user._id == order.buyer_id) initiator = 'buyer'; - order[`${initiator}_dispute`] = true; - order.previous_dispute_status = order.status; + if(initiator === 'seller') + order.seller_dispute = true; + else + order.buyer_dispute = true; order.status = 'DISPUTE'; const sellerToken = Math.floor(Math.random() * 899 + 100); const buyerToken = Math.floor(Math.random() * 899 + 100); - order.buyer_dispute_token = buyerToken; - order.seller_dispute_token = sellerToken; + order.buyer_dispute_token = String(buyerToken); + order.seller_dispute_token = String(sellerToken); await order.save(); // If this is a non community order, we may ban the user globally @@ -54,11 +60,14 @@ const dispute = async ctx => { (await Dispute.count({ $or: [{ buyer_id: seller._id }, { seller_id: seller._id }], })) + 1; - if (buyerDisputes >= process.env.MAX_DISPUTES) { + const maxDisputes = Number(process.env.MAX_DISPUTES); + // if MAX_DISPUTES is not specified or can't be parsed as number, following + // maxDisputes will be NaN and following conditions will be false + if (buyerDisputes >= maxDisputes) { buyer.banned = true; await buyer.save(); } - if (sellerDisputes >= process.env.MAX_DISPUTES) { + if (sellerDisputes >= maxDisputes) { seller.banned = true; await seller.save(); } @@ -83,15 +92,15 @@ const dispute = async ctx => { } }; -const deleteDispute = async ctx => { +const deleteDispute = async (ctx: MainContext) => { try { const { admin } = ctx; - let [username, orderId] = await validateParams( + let [username, orderId] = (await validateParams( ctx, 3, '\\<_username_\\> \\<_order id_\\>' - ); + ))!; if (!username) return; if (!orderId) return; @@ -140,4 +149,4 @@ const deleteDispute = async ctx => { } }; -module.exports = { dispute, deleteDispute }; +export { dispute, deleteDispute }; diff --git a/bot/modules/dispute/index.js b/bot/modules/dispute/index.ts similarity index 58% rename from bot/modules/dispute/index.js rename to bot/modules/dispute/index.ts index a484a53d..f1a1ea1b 100644 --- a/bot/modules/dispute/index.js +++ b/bot/modules/dispute/index.ts @@ -1,8 +1,10 @@ -const commands = require('./commands'); -const actions = require('./actions'); +import * as commands from './commands'; +import * as actions from './actions'; +import { Telegraf } from 'telegraf'; +import { MainContext } from '../../start'; const { userMiddleware, adminMiddleware } = require('../../middleware/user'); -exports.configure = bot => { +export const configure = (bot: Telegraf) => { bot.command('dispute', userMiddleware, commands.dispute); bot.command('deldispute', adminMiddleware, commands.deleteDispute); bot.action( diff --git a/bot/modules/dispute/messages.js b/bot/modules/dispute/messages.ts similarity index 78% rename from bot/modules/dispute/messages.js rename to bot/modules/dispute/messages.ts index dbc7754c..8882666b 100644 --- a/bot/modules/dispute/messages.js +++ b/bot/modules/dispute/messages.ts @@ -1,11 +1,16 @@ -const { - getDisputeChannel, - getDetailedOrder, - sanitizeMD, -} = require('../../../util'); -const { logger } = require('../../../logger'); +import { getDisputeChannel, getDetailedOrder, sanitizeMD } from '../../../util'; +import { logger } from '../../../logger'; +import { MainContext } from '../../start'; +import { IOrder } from '../../../models/order'; +import { UserDocument } from '../../../models/user'; -exports.beginDispute = async (ctx, initiator, order, buyer, seller) => { +export const beginDispute = async ( + ctx: MainContext, + initiator: ('seller' | 'buyer'), + order: IOrder, + buyer: UserDocument, + seller: UserDocument +) => { try { let initiatorUser = buyer; let counterPartyUser = seller; @@ -50,9 +55,11 @@ exports.beginDispute = async (ctx, initiator, order, buyer, seller) => { } }; -exports.takeDisputeButton = async (ctx, order) => { +export const takeDisputeButton = async (ctx: MainContext, order: IOrder) => { try { const disputeChannel = await getDisputeChannel(order); + if(disputeChannel === undefined) + throw new Error("disputeChannel is undefined") await ctx.telegram.sendMessage(disputeChannel, ctx.i18n.t('new_dispute'), { reply_markup: { inline_keyboard: [ @@ -70,15 +77,15 @@ exports.takeDisputeButton = async (ctx, order) => { } }; -exports.disputeData = async ( - ctx, - buyer, - seller, - order, - initiator, - solver, - buyerDisputes, - sellerDisputes +export const disputeData = async ( + ctx: MainContext, + buyer: UserDocument, + seller: UserDocument, + order: IOrder, + initiator: ('seller' | 'buyer'), + solver: UserDocument, + buyerDisputes: any, + sellerDisputes: any ) => { try { const type = @@ -135,7 +142,7 @@ exports.disputeData = async ( } }; -exports.notFoundDisputeMessage = async ctx => { +export const notFoundDisputeMessage = async (ctx: MainContext) => { try { await ctx.reply(ctx.i18n.t('not_found_dispute')); } catch (error) { @@ -143,7 +150,7 @@ exports.notFoundDisputeMessage = async ctx => { } }; -exports.sellerReleased = async (ctx, solver) => { +export const sellerReleased = async (ctx: MainContext, solver: UserDocument) => { try { await ctx.telegram.sendMessage( solver.tg_id, @@ -154,7 +161,7 @@ exports.sellerReleased = async (ctx, solver) => { } }; -exports.disputeTooSoonMessage = async ctx => { +export const disputeTooSoonMessage = async (ctx: MainContext) => { try { await ctx.reply(ctx.i18n.t('dispute_too_soon')); } catch (error) { diff --git a/models/dispute.ts b/models/dispute.ts index 68f751c4..b682107c 100644 --- a/models/dispute.ts +++ b/models/dispute.ts @@ -2,8 +2,8 @@ import mongoose, { Document, Schema } from 'mongoose'; export interface IDispute extends Document { initiator: string; - seller_id: string; - buyer_id: string; + seller_id: string | null; + buyer_id: string | null; status: string; community_id: string; order_id: string; From a7052b024ede4953b537c98c94d2e0217a59f63b Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 7 Aug 2024 10:57:31 +0200 Subject: [PATCH 03/22] bot/modules/events: convert to TS Convert bot/modules/eventsmodule to TypeScript. --- bot/modules/events/community.js | 14 -------------- bot/modules/events/community.ts | 16 ++++++++++++++++ bot/modules/events/index.js | 16 ---------------- bot/modules/events/index.ts | 26 ++++++++++++++++++++++++++ bot/modules/events/orders.js | 23 ----------------------- bot/modules/events/orders.ts | 24 ++++++++++++++++++++++++ 6 files changed, 66 insertions(+), 53 deletions(-) delete mode 100644 bot/modules/events/community.js create mode 100644 bot/modules/events/community.ts delete mode 100644 bot/modules/events/index.js create mode 100644 bot/modules/events/index.ts delete mode 100644 bot/modules/events/orders.js create mode 100644 bot/modules/events/orders.ts diff --git a/bot/modules/events/community.js b/bot/modules/events/community.js deleted file mode 100644 index d06ff7d6..00000000 --- a/bot/modules/events/community.js +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-check -const Events = require('./index'); - -const TYPES = (exports.TYPES = { - COMMUNITY_UPDATED: 'COMMUNITY_UPDATED', -}); - -exports.communityUpdated = community => { - Events.dispatch({ - type: TYPES.ORDER_CREATED, - payload: community, - }); -}; -exports.onCommunityUpdated = fn => Events.subscribe(TYPES.ORDER_CREATED, fn); diff --git a/bot/modules/events/community.ts b/bot/modules/events/community.ts new file mode 100644 index 00000000..3fccc45d --- /dev/null +++ b/bot/modules/events/community.ts @@ -0,0 +1,16 @@ +// @ts-check +import { ICommunity } from '../../../models/community'; +import * as Events from './index'; +import { TYPES as ORDER_TYPES } from './orders'; + +export const TYPES = { + COMMUNITY_UPDATED: 'COMMUNITY_UPDATED', +}; + +export const communityUpdated = (community: ICommunity) => { + Events.dispatch({ + type: ORDER_TYPES.ORDER_CREATED, + payload: community, + }); +}; +export const onCommunityUpdated = (fn: Events.SubscriptionFunction) => Events.subscribe(ORDER_TYPES.ORDER_CREATED, fn); diff --git a/bot/modules/events/index.js b/bot/modules/events/index.js deleted file mode 100644 index 26ea2daf..00000000 --- a/bot/modules/events/index.js +++ /dev/null @@ -1,16 +0,0 @@ -const subs = {}; - -exports.subscribe = (type, fn) => { - if (typeof fn !== 'function') throw new Error('HandlerNotAFunction'); - subs[type] = subs[type] || []; - subs[type].push(fn); - return () => { - subs[type] = subs[type].filter(sub => sub !== fn); - }; -}; - -exports.dispatch = event => { - const fns = subs[event.type] || []; - const results = fns.map(fn => fn(event.payload)); - return Promise.all(results); -}; diff --git a/bot/modules/events/index.ts b/bot/modules/events/index.ts new file mode 100644 index 00000000..77303d4b --- /dev/null +++ b/bot/modules/events/index.ts @@ -0,0 +1,26 @@ +export type SubscriptionFunction = (arg: any) => any; + +export interface Event { + type: string; + payload: any; +} + +interface Subscriptions { + [name: string]: SubscriptionFunction[]; +} + +const subs: Subscriptions = {}; + +export const subscribe = (type: any, fn: SubscriptionFunction) => { + subs[type] = subs[type] || []; + subs[type].push(fn); + return () => { + subs[type] = subs[type].filter(sub => sub !== fn); + }; +}; + +export const dispatch = (event: Event) => { + const fns = subs[event.type] || []; + const results = fns.map(fn => fn(event.payload)); + return Promise.all(results); +}; diff --git a/bot/modules/events/orders.js b/bot/modules/events/orders.js deleted file mode 100644 index b7789b2b..00000000 --- a/bot/modules/events/orders.js +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-check -const Events = require('./index'); - -const TYPES = (exports.TYPES = { - ORDER_CREATED: 'ORDER_CREATED', - ORDER_UPDATED: 'ORDER_UPDATED', -}); - -exports.orderCreated = order => { - Events.dispatch({ - type: TYPES.ORDER_CREATED, - payload: order, - }); -}; -exports.onOrderCreated = fn => Events.subscribe(TYPES.ORDER_CREATED, fn); - -exports.orderUpdated = order => { - Events.dispatch({ - type: TYPES.ORDER_UPDATED, - payload: order, - }); -}; -exports.onOrderUpdated = fn => Events.subscribe(TYPES.ORDER_UPDATED, fn); diff --git a/bot/modules/events/orders.ts b/bot/modules/events/orders.ts new file mode 100644 index 00000000..a38784cf --- /dev/null +++ b/bot/modules/events/orders.ts @@ -0,0 +1,24 @@ +// @ts-check +import { IOrder } from '../../../models/order'; +import * as Events from './index'; + +export const TYPES = { + ORDER_CREATED: 'ORDER_CREATED', + ORDER_UPDATED: 'ORDER_UPDATED', +}; + +export const orderCreated = (order: IOrder) => { + Events.dispatch({ + type: TYPES.ORDER_CREATED, + payload: order, + }); +}; +export const onOrderCreated = (fn: Events.SubscriptionFunction) => Events.subscribe(TYPES.ORDER_CREATED, fn); + +export const orderUpdated = (order: IOrder) => { + Events.dispatch({ + type: TYPES.ORDER_UPDATED, + payload: order, + }); +}; +export const onOrderUpdated = (fn: Events.SubscriptionFunction) => Events.subscribe(TYPES.ORDER_UPDATED, fn); From 2fea4c860b817765479fc4daaee41d223220ade9 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 7 Aug 2024 11:17:07 +0200 Subject: [PATCH 04/22] bot/modules/language: convert to TS Convert bot/modules/language to TypeScript. --- bot/modules/language/actions.js | 13 ----------- bot/modules/language/actions.ts | 16 +++++++++++++ bot/modules/language/commands.js | 21 ----------------- bot/modules/language/commands.ts | 23 +++++++++++++++++++ bot/modules/language/index.js | 8 ------- bot/modules/language/index.ts | 10 ++++++++ .../language/{messages.js => messages.ts} | 6 +++-- 7 files changed, 53 insertions(+), 44 deletions(-) delete mode 100644 bot/modules/language/actions.js create mode 100644 bot/modules/language/actions.ts delete mode 100644 bot/modules/language/commands.js create mode 100644 bot/modules/language/commands.ts delete mode 100644 bot/modules/language/index.js create mode 100644 bot/modules/language/index.ts rename bot/modules/language/{messages.js => messages.ts} (69%) diff --git a/bot/modules/language/actions.js b/bot/modules/language/actions.js deleted file mode 100644 index 2f994d23..00000000 --- a/bot/modules/language/actions.js +++ /dev/null @@ -1,13 +0,0 @@ -const { User } = require('../../../models'); - -exports.setLanguage = async ctx => { - const tgId = ctx.update.callback_query.from.id; - const user = await User.findOne({ tg_id: tgId }); - if (!user) return; - const code = ctx.match[1]; - ctx.deleteMessage(); - user.lang = code; - ctx.i18n.locale(code); - await user.save(); - await ctx.reply(ctx.i18n.t('operation_successful')); -}; diff --git a/bot/modules/language/actions.ts b/bot/modules/language/actions.ts new file mode 100644 index 00000000..8f1f0f1c --- /dev/null +++ b/bot/modules/language/actions.ts @@ -0,0 +1,16 @@ +import { User } from '../../../models'; +import { MainContext } from '../../start'; + +export const setLanguage = async (ctx: MainContext) => { + const tgId = (ctx.update as any).callback_query.from.id; + const user = await User.findOne({ tg_id: tgId }); + if (user === null) return; + const code = ctx.match?.[1]; + if (code === undefined) + throw new Error("setLanguage: code is undefined"); + ctx.deleteMessage(); + user.lang = code; + ctx.i18n.locale(code); + await user.save(); + await ctx.reply(ctx.i18n.t('operation_successful')); +}; diff --git a/bot/modules/language/commands.js b/bot/modules/language/commands.js deleted file mode 100644 index 4e3e57c0..00000000 --- a/bot/modules/language/commands.js +++ /dev/null @@ -1,21 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { getLanguageFlag } = require('../../../util'); -const { logger } = require('../../../logger'); -const { showFlagsMessage } = require('./messages'); - -exports.setlang = async ctx => { - try { - const flags = []; - fs.readdirSync(path.join(__dirname, '../../../locales')).forEach(file => { - const lang = file.split('.')[0]; - const flag = getLanguageFlag(lang); - if (flag !== undefined) { - flags.push(flag); - } - }); - await showFlagsMessage(ctx, flags, ctx.user.lang); - } catch (error) { - logger.error(error); - } -}; diff --git a/bot/modules/language/commands.ts b/bot/modules/language/commands.ts new file mode 100644 index 00000000..e3f9ab3d --- /dev/null +++ b/bot/modules/language/commands.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import fs from 'fs'; +import { getLanguageFlag } from '../../../util'; +import { logger } from '../../../logger'; +import { showFlagsMessage } from './messages'; +import { ILanguage } from '../../../util/languagesModel'; +import { MainContext } from '../../start'; + +export const setlang = async (ctx: MainContext) => { + try { + const flags: ILanguage[] = []; + fs.readdirSync(path.join(__dirname, '../../../locales')).forEach(file => { + const lang = file.split('.')[0]; + const flag = getLanguageFlag(lang); + if (flag !== undefined) { + flags.push(flag); + } + }); + await showFlagsMessage(ctx, flags, ctx.user.lang); + } catch (error) { + logger.error(error); + } +}; diff --git a/bot/modules/language/index.js b/bot/modules/language/index.js deleted file mode 100644 index f3573b0e..00000000 --- a/bot/modules/language/index.js +++ /dev/null @@ -1,8 +0,0 @@ -const { userMiddleware } = require('../../middleware/user'); -const commands = require('./commands'); -const actions = require('./actions'); - -exports.configure = bot => { - bot.command('setlang', userMiddleware, commands.setlang); - bot.action(/^setLanguage_([a-z]{2})$/, userMiddleware, actions.setLanguage); -}; diff --git a/bot/modules/language/index.ts b/bot/modules/language/index.ts new file mode 100644 index 00000000..4c4292fe --- /dev/null +++ b/bot/modules/language/index.ts @@ -0,0 +1,10 @@ +const { userMiddleware } = require('../../middleware/user'); +import * as commands from './commands'; +import * as actions from './actions'; +import { Telegraf } from 'telegraf'; +import { MainContext } from '../../start'; + +exports.configure = (bot: Telegraf) => { + bot.command('setlang', userMiddleware, ctx => commands.setlang(ctx as unknown as MainContext)); + bot.action(/^setLanguage_([a-z]{2})$/, userMiddleware, ctx => actions.setLanguage(ctx as unknown as MainContext)); +}; diff --git a/bot/modules/language/messages.js b/bot/modules/language/messages.ts similarity index 69% rename from bot/modules/language/messages.js rename to bot/modules/language/messages.ts index 870abade..eb8fbc26 100644 --- a/bot/modules/language/messages.js +++ b/bot/modules/language/messages.ts @@ -1,6 +1,8 @@ -const { logger } = require('../../../logger'); +import { logger } from '../../../logger'; +import { ILanguage } from '../../../util/languagesModel'; +import { MainContext } from '../../start'; -exports.showFlagsMessage = async (ctx, flags, code) => { +export const showFlagsMessage = async (ctx: MainContext, flags: ILanguage[], code: string) => { try { const buttons = []; while (flags.length > 0) { From 4aa80f28f69ace04392bb3ce1c466c393a8097ad Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 7 Aug 2024 11:50:14 +0200 Subject: [PATCH 05/22] bot/modules/nostr: convert to TS Convert bot/modules/nostr to TypeScript. Specified "lib" property in tsconfig.json because otherwise TypeScript compiler complains about usage of `Promise.any` in bot/modules/nostr/index.ts: ``` Property 'any' does not exist on type 'PromiseConstructor'. Do you need to change your target library? Try changing the lib compiler option to 'es2021' or later. ``` --- .../nostr/{commands.js => commands.ts} | 9 +++-- bot/modules/nostr/config.js | 20 ---------- bot/modules/nostr/config.ts | 20 ++++++++++ bot/modules/nostr/{events.js => events.ts} | 23 +++++++---- bot/modules/nostr/index.js | 35 ---------------- bot/modules/nostr/index.ts | 40 +++++++++++++++++++ bot/modules/nostr/{lib.js => lib.ts} | 6 +-- tsconfig.json | 1 + 8 files changed, 85 insertions(+), 69 deletions(-) rename bot/modules/nostr/{commands.js => commands.ts} (68%) delete mode 100644 bot/modules/nostr/config.js create mode 100644 bot/modules/nostr/config.ts rename bot/modules/nostr/{events.js => events.ts} (68%) delete mode 100644 bot/modules/nostr/index.js create mode 100644 bot/modules/nostr/index.ts rename bot/modules/nostr/{lib.js => lib.ts} (56%) diff --git a/bot/modules/nostr/commands.js b/bot/modules/nostr/commands.ts similarity index 68% rename from bot/modules/nostr/commands.js rename to bot/modules/nostr/commands.ts index 0861bdf5..1ecb3e07 100644 --- a/bot/modules/nostr/commands.js +++ b/bot/modules/nostr/commands.ts @@ -1,8 +1,9 @@ -const Nostr = require('nostr-tools'); -const { logger } = require('../../../logger'); -const Config = require('./config'); +import Nostr from 'nostr-tools'; +import { logger } from '../../../logger'; +import * as Config from './config'; +import { MainContext } from '../../start'; -exports.info = async ctx => { +export const info = async (ctx: MainContext) => { try { const publicKey = Config.getPublicKey(); if (!publicKey) return; diff --git a/bot/modules/nostr/config.js b/bot/modules/nostr/config.js deleted file mode 100644 index 11fc59f2..00000000 --- a/bot/modules/nostr/config.js +++ /dev/null @@ -1,20 +0,0 @@ -const { getPublicKey, generateSecretKey } = require('nostr-tools/pure'); -const { SimplePool } = require('nostr-tools/pool'); - -const sk = process.env.NOSTR_SK || generateSecretKey(); -const pk = getPublicKey(sk); - -exports.getPrivateKey = () => sk; -exports.getPublicKey = () => pk; - -const pool = (exports.pool = new SimplePool()); -const relays = (env => { - if (!env.RELAYS) return []; - return env.RELAYS.split(','); -})(process.env); - -exports.addRelay = relay => { - relays.push(relay); - relays.map(relay => pool.ensureRelay(relay)); -}; -exports.getRelays = () => relays; diff --git a/bot/modules/nostr/config.ts b/bot/modules/nostr/config.ts new file mode 100644 index 00000000..b0c60a7e --- /dev/null +++ b/bot/modules/nostr/config.ts @@ -0,0 +1,20 @@ +const notsrPure = require('nostr-tools/pure'); +const { SimplePool } = require('nostr-tools/pool'); + +const sk = process.env.NOSTR_SK || notsrPure.generateSecretKey(); +const pk = notsrPure.getPublicKey(sk); + +export const getPrivateKey = () => sk; +export const getPublicKey = () => pk; + +export const pool = new SimplePool(); +const relays = (env => { + if (!env.RELAYS) return []; + return env.RELAYS.split(','); +})(process.env); + +export const addRelay = (relay: string) => { + relays.push(relay); + relays.map(relay => pool.ensureRelay(relay)); +}; +export const getRelays = () => relays; diff --git a/bot/modules/nostr/events.js b/bot/modules/nostr/events.ts similarity index 68% rename from bot/modules/nostr/events.js rename to bot/modules/nostr/events.ts index a9af29ec..98deee95 100644 --- a/bot/modules/nostr/events.js +++ b/bot/modules/nostr/events.ts @@ -1,24 +1,31 @@ const { finalizeEvent, verifyEvent } = require('nostr-tools/pure'); -const Config = require('./config'); +import * as Config from './config'; -const { Community } = require('../../../models'); -const { toKebabCase, removeAtSymbol } = require('../../../util'); +import { Community } from '../../../models'; +import { toKebabCase, removeAtSymbol } from '../../../util'; +import { IOrder } from '../../../models/order'; /// All events broadcasted are Parameterized Replaceable Events, /// the event kind must be between 30000 and 39999 const kind = 38383; -const orderToTags = async order => { +const orderToTags = async (order: IOrder) => { + const orderPublishedExpirationWindow = process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW; + if(orderPublishedExpirationWindow === undefined) + throw new Error("Environment variable ORDER_PUBLISHED_EXPIRATION_WINDOW is not defined"); const expiration = Math.floor(Date.now() / 1000) + - parseInt(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW); + parseInt(orderPublishedExpirationWindow); const fiat_amount = ['fa']; if (order.fiat_amount === undefined) { fiat_amount.push(order.min_amount.toString(), order.max_amount.toString()); } else { fiat_amount.push(order.fiat_amount.toString()); } - const channel = removeAtSymbol(process.env.CHANNEL); + const channelEnvVar = process.env.CHANNEL; + if(channelEnvVar === undefined) + throw new Error("Environment variable CHANNEL is not defined") + const channel = removeAtSymbol(channelEnvVar); let source = `https://t.me/${channel}/${order.tg_channel_message1}`; const tags = []; tags.push(['d', order.id]); @@ -31,6 +38,8 @@ const orderToTags = async order => { tags.push(['premium', order.price_margin.toString()]); if (order.community_id) { const community = await Community.findById(order.community_id); + if(community === null) + throw new Error("community was not found"); const group = removeAtSymbol(community.group); source = `https://t.me/${group}/${order.tg_channel_message1}`; tags.push(['community_id', order.community_id]); @@ -45,7 +54,7 @@ const orderToTags = async order => { return tags; }; -exports.createOrderEvent = async order => { +export const createOrderEvent = async (order: IOrder) => { const myPrivKey = Config.getPrivateKey(); if (order.is_public === false) { return; diff --git a/bot/modules/nostr/index.js b/bot/modules/nostr/index.js deleted file mode 100644 index b1ee0364..00000000 --- a/bot/modules/nostr/index.js +++ /dev/null @@ -1,35 +0,0 @@ -require('websocket-polyfill'); -const { logger } = require('../../../logger'); -const Config = require('./config'); -const { createOrderEvent } = require('./events'); -const Commands = require('./commands'); - -exports.configure = bot => { - bot.command('/nostr', Commands.info); - - if (!Config.getRelays().length) { - ['wss://nostr-pub.wellorder.net', 'wss://relay.damus.io'].map( - Config.addRelay - ); - } - - const CommunityEvents = require('../events/community'); - CommunityEvents.onCommunityUpdated(async community => { - // todo: notify users - }); - - const OrderEvents = require('../events/orders'); - - OrderEvents.onOrderUpdated(async order => { - try { - const event = await createOrderEvent(order); - if (event) { - await Promise.any(Config.pool.publish(Config.getRelays(), event)); - } - - return event; - } catch (err) { - logger.error(err); - } - }); -}; diff --git a/bot/modules/nostr/index.ts b/bot/modules/nostr/index.ts new file mode 100644 index 00000000..1c786df0 --- /dev/null +++ b/bot/modules/nostr/index.ts @@ -0,0 +1,40 @@ +require('websocket-polyfill'); // is it needed? +import { logger } from '../../../logger'; +import * as Config from './config'; +import { createOrderEvent } from './events'; +import * as Commands from './commands'; +import { Telegraf } from 'telegraf'; +import { MainContext } from '../../start'; +import { IOrder } from '../../../models/order'; + +export const configure = (bot: Telegraf) => { + bot.command('/nostr', Commands.info); + + if (!Config.getRelays().length) { + ['wss://nostr-pub.wellorder.net', 'wss://relay.damus.io'].map( + Config.addRelay + ); + } + + // I don't know why these requires are here and not at the top of the file, + // so I leave them as they are instead of converting to imports. + const CommunityEvents = require('../events/community'); + CommunityEvents.onCommunityUpdated(async (community: any) => { + // todo: notify users + }); + + const OrderEvents = require('../events/orders'); + + OrderEvents.onOrderUpdated(async (order: IOrder) => { + try { + const event = await createOrderEvent(order); + if (event) { + await Promise.any(Config.pool.publish(Config.getRelays(), event)); + } + + return event; + } catch (err) { + logger.error(err); + } + }); +}; diff --git a/bot/modules/nostr/lib.js b/bot/modules/nostr/lib.ts similarity index 56% rename from bot/modules/nostr/lib.js rename to bot/modules/nostr/lib.ts index 346bd847..b0421e55 100644 --- a/bot/modules/nostr/lib.js +++ b/bot/modules/nostr/lib.ts @@ -1,11 +1,11 @@ -const Nostr = require('nostr-tools'); +import Nostr from 'nostr-tools'; -exports.decodeNpub = npub => { +export const decodeNpub = (npub: string) => { try { const { type, data } = Nostr.nip19.decode(npub); if (type === 'npub') return data; } catch (err) {} }; -exports.encodeNpub = hex => { +export const encodeNpub = (hex: string) => { return Nostr.nip19.npubEncode(hex); }; diff --git a/tsconfig.json b/tsconfig.json index bf119471..ecc59192 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "esModuleInterop": true, "resolveJsonModule": true, "downlevelIteration": true, + "lib":["ES2021", "DOM"], "outDir": "./dist", "rootDir": ".", "allowJs": true, From 529a917be4ea7e5ce6f49b735726af745940521c Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 7 Aug 2024 13:34:06 +0200 Subject: [PATCH 06/22] bot/modules/user: convert to TS Convert bot/modules/user to TypeScript. --- bot/modules/community/communityContext.ts | 2 + bot/modules/user/{index.js => index.ts} | 12 ++- bot/modules/user/scenes/index.js | 1 - bot/modules/user/scenes/index.ts | 3 + .../user/scenes/{settings.js => settings.ts} | 78 +++++++++++-------- 5 files changed, 60 insertions(+), 36 deletions(-) rename bot/modules/user/{index.js => index.ts} (53%) delete mode 100644 bot/modules/user/scenes/index.js create mode 100644 bot/modules/user/scenes/index.ts rename bot/modules/user/scenes/{settings.js => settings.ts} (50%) diff --git a/bot/modules/community/communityContext.ts b/bot/modules/community/communityContext.ts index b47656a0..7c891bfe 100644 --- a/bot/modules/community/communityContext.ts +++ b/bot/modules/community/communityContext.ts @@ -25,6 +25,8 @@ export interface CommunityWizardState { bot: Telegraf; message: Message.TextMessage | undefined; error?: any; + feedback?: any; + language: string; updateUI: (() => Promise); handler?: ((ctx: CommunityContext) => Promise); } diff --git a/bot/modules/user/index.js b/bot/modules/user/index.ts similarity index 53% rename from bot/modules/user/index.js rename to bot/modules/user/index.ts index ac279d18..e6b412fc 100644 --- a/bot/modules/user/index.js +++ b/bot/modules/user/index.ts @@ -1,15 +1,21 @@ // @ts-check const { userMiddleware } = require('../../middleware/user'); -const Scenes = (exports.Scenes = require('./scenes')); +import { Telegraf } from 'telegraf'; +import Scenes from './scenes'; +import { CommunityContext } from '../community/communityContext'; -exports.configure = bot => { +export const configure = (bot: Telegraf) => { bot.command('/settings', userMiddleware, async ctx => { try { const { user } = ctx; await ctx.scene.enter(Scenes.Settings.id, { user }); - } catch (err) { + } catch (err: any) { ctx.reply(err.message); } }); }; + +export { + Scenes +} diff --git a/bot/modules/user/scenes/index.js b/bot/modules/user/scenes/index.js deleted file mode 100644 index fe02c8bf..00000000 --- a/bot/modules/user/scenes/index.js +++ /dev/null @@ -1 +0,0 @@ -exports.Settings = require('./settings'); diff --git a/bot/modules/user/scenes/index.ts b/bot/modules/user/scenes/index.ts new file mode 100644 index 00000000..a7b51e55 --- /dev/null +++ b/bot/modules/user/scenes/index.ts @@ -0,0 +1,3 @@ +import Settings from './settings'; + +export default { Settings } diff --git a/bot/modules/user/scenes/settings.js b/bot/modules/user/scenes/settings.ts similarity index 50% rename from bot/modules/user/scenes/settings.js rename to bot/modules/user/scenes/settings.ts index 36cdbbfb..5bfab963 100644 --- a/bot/modules/user/scenes/settings.js +++ b/bot/modules/user/scenes/settings.ts @@ -1,25 +1,31 @@ -const { Scenes } = require('telegraf'); -const { Community } = require('../../../../models'); -const { getLanguageFlag } = require('../../../../util'); -const NostrLib = require('../../nostr/lib'); +import { Scenes } from 'telegraf'; +import { Community } from '../../../../models'; +import { getLanguageFlag } from '../../../../util'; +import * as NostrLib from '../../nostr/lib'; +import { CommunityContext, CommunityWizardState } from '../../community/communityContext'; +import { Message } from 'telegraf/typings/core/types/typegram'; function make() { - const resetMessage = async (ctx, next) => { - delete ctx.scene.state.feedback; - delete ctx.scene.state.error; + const resetMessage = async (ctx: CommunityContext, next: () => void) => { + const state = ctx.scene.state as CommunityWizardState; + delete state.feedback; + delete state.error; next(); }; - async function mainData(ctx) { - const { user } = ctx.scene.state; + async function mainData(ctx: CommunityContext) { + const state = ctx.scene.state as CommunityWizardState; + const { user } = state; const data = { user, - language: getLanguageFlag(ctx.scene.state.language), + language: getLanguageFlag(state.language), npub: '', community: '', lightning_address: '', }; if (user.default_community_id) { const community = await Community.findById(user.default_community_id); + if(community == null) + throw new Error("community not found") data.community = community.group; } if (user.nostr_public_key) { @@ -32,10 +38,14 @@ function make() { return data; } - async function updateMessage(ctx) { + async function updateMessage(ctx: CommunityContext) { try { - ctx.i18n.locale(ctx.scene.state.language); // i18n locale resets if user executes unknown action - const { message, error } = ctx.scene.state; + const state = ctx.scene.state as CommunityWizardState; + ctx.i18n.locale(state.language); // i18n locale resets if user executes unknown action + const { message, error } = state; + + if(message === undefined) + throw new Error("message is undefined"); const errorText = (error => { if (!error) return; @@ -45,7 +55,7 @@ function make() { if (!feedback) return; if (typeof feedback === 'string') return feedback; return ctx.i18n.t(feedback.i18n, feedback); - })(ctx.scene.state.feedback); + })(state.feedback); const extras = [errorText, feedbackText].filter(e => e); const main = ctx.i18n.t('user_settings', await mainData(ctx)); @@ -57,30 +67,31 @@ function make() { const msg = await ctx.telegram.editMessageText( message.chat.id, message.message_id, - null, + undefined, str, { parse_mode: 'HTML', - disable_web_page_preview: true, - } + link_preview_options: { is_disabled: true }, + } as any ); - ctx.scene.state.message = msg; - ctx.scene.state.message.text = str; + state.message = msg as Message.TextMessage; + state.message.text = str; } catch (err) {} } - async function initHandler(ctx) { + async function initHandler(ctx: CommunityContext) { try { - const { user } = ctx.scene.state; - ctx.scene.state.language = user.lang || ctx.from?.language_code; + const state = ctx.scene.state as CommunityWizardState; + const { user } = state; + state.language = user.lang || ctx.from?.language_code; const str = ctx.i18n.t('user_settings', await mainData(ctx)); const msg = await ctx.reply(str, { parse_mode: 'HTML' }); - ctx.scene.state.message = msg; - ctx.scene.state.message.text = str; + state.message = msg; + state.message.text = str; } catch (err) {} } - const scene = new Scenes.WizardScene('USER_SETTINGS', async ctx => { - ctx.user = ctx.scene.state.user; - const { state } = ctx.scene; + const scene = new Scenes.WizardScene('USER_SETTINGS', async (ctx: CommunityContext) => { + const state = ctx.scene.state as CommunityWizardState; + ctx.user = state.user; if (!state.message) return initHandler(ctx); await ctx.deleteMessage(); state.error = { @@ -89,22 +100,25 @@ function make() { await updateMessage(ctx); }); - scene.command('/setnpub', resetMessage, async ctx => { + scene.command('/setnpub', resetMessage, async (ctx: CommunityContext) => { try { await ctx.deleteMessage(); + const state = ctx.scene.state as CommunityWizardState; + if(ctx.message === undefined) + throw new Error("ctx.message is undefined"); const [, npub] = ctx.message.text.trim().split(' '); const hex = NostrLib.decodeNpub(npub); if (!hex) throw new Error('NpubNotValid'); - const user = ctx.scene.state.user; + const user = state.user; user.nostr_public_key = hex; await user.save(); - ctx.scene.state.feedback = { + state.feedback = { i18n: 'user_npub_updated', npub, }; await updateMessage(ctx); } catch (err) { - ctx.scene.state.error = { + (ctx.scene.state as CommunityWizardState).error = { i18n: 'npub_not_valid', }; await updateMessage(ctx); @@ -114,4 +128,4 @@ function make() { return scene; } -module.exports = make(); +export default make(); From ad220ab28527c8d6b8fc21130fa8e014f85c2daf Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 7 Aug 2024 14:52:58 +0200 Subject: [PATCH 07/22] bot/middleware: convert to TS Convert bot/middleware to TypeScript. --- bot/middleware/{commands.js => commands.ts} | 8 +++++--- bot/middleware/index.js | 15 --------------- bot/middleware/index.ts | 11 +++++++++++ bot/middleware/{stage.js => stage.ts} | 13 +++++++------ bot/middleware/{user.js => user.ts} | 14 ++++++-------- 5 files changed, 29 insertions(+), 32 deletions(-) rename bot/middleware/{commands.js => commands.ts} (86%) delete mode 100644 bot/middleware/index.js create mode 100644 bot/middleware/index.ts rename bot/middleware/{stage.js => stage.ts} (77%) rename bot/middleware/{user.js => user.ts} (53%) diff --git a/bot/middleware/commands.js b/bot/middleware/commands.ts similarity index 86% rename from bot/middleware/commands.js rename to bot/middleware/commands.ts index 06479f55..75e68b63 100644 --- a/bot/middleware/commands.js +++ b/bot/middleware/commands.ts @@ -1,4 +1,6 @@ -const commandArgs = () => (ctx, next) => { +import { CommunityContext } from "../modules/community/communityContext"; + +const commandArgs = () => (ctx: CommunityContext, next: () => void) => { if (ctx.message && ctx.message.text) { const text = ctx.message.text; if (text.startsWith('/')) { @@ -12,7 +14,7 @@ const commandArgs = () => (ctx, next) => { command = match[1]; } let next_arg = ['', '', match[2]]; - while ((next_arg = re_next_arg.exec(next_arg[2]))) { + while ((next_arg = re_next_arg.exec(next_arg[2])!)) { let quoted_arg = next_arg[1]; let unquoted_arg = ''; while (quoted_arg.length > 0) { @@ -49,4 +51,4 @@ const commandArgs = () => (ctx, next) => { return next(); }; -module.exports = commandArgs; +export default commandArgs; diff --git a/bot/middleware/index.js b/bot/middleware/index.js deleted file mode 100644 index 67626ac4..00000000 --- a/bot/middleware/index.js +++ /dev/null @@ -1,15 +0,0 @@ -const commandArgsMiddleware = require('./commands'); -const { stageMiddleware } = require('./stage'); -const { - userMiddleware, - adminMiddleware, - superAdminMiddleware, -} = require('./user'); - -module.exports = { - commandArgsMiddleware, - stageMiddleware, - userMiddleware, - adminMiddleware, - superAdminMiddleware, -}; diff --git a/bot/middleware/index.ts b/bot/middleware/index.ts new file mode 100644 index 00000000..152556ac --- /dev/null +++ b/bot/middleware/index.ts @@ -0,0 +1,11 @@ +import commandArgsMiddleware from './commands'; +import { stageMiddleware } from './stage'; +import { userMiddleware, adminMiddleware, superAdminMiddleware } from './user'; + +export { + commandArgsMiddleware, + stageMiddleware, + userMiddleware, + adminMiddleware, + superAdminMiddleware, +}; diff --git a/bot/middleware/stage.js b/bot/middleware/stage.ts similarity index 77% rename from bot/middleware/stage.js rename to bot/middleware/stage.ts index 145d46c1..a1f90ce0 100644 --- a/bot/middleware/stage.js +++ b/bot/middleware/stage.ts @@ -1,15 +1,16 @@ // @ts-check -const { Scenes } = require('telegraf'); -const CommunityModule = require('../modules/community'); +import { Scenes } from 'telegraf'; +import * as CommunityModule from '../modules/community'; const OrdersModule = require('../modules/orders'); -const UserModule = require('../modules/user'); +import * as UserModule from '../modules/user'; +import { CommunityContext } from '../modules/community/communityContext'; const { addInvoiceWizard, addFiatAmountWizard, addInvoicePHIWizard, } = require('../scenes'); -exports.stageMiddleware = () => { +export const stageMiddleware = () => { const scenes = [ addInvoiceWizard, addFiatAmountWizard, @@ -34,8 +35,8 @@ exports.stageMiddleware = () => { return stage.middleware(); }; -function addGenericCommands(scene) { - scene.command('exit', async ctx => { +function addGenericCommands(scene: Scenes.WizardScene) { + scene.command('exit', async (ctx) => { await ctx.scene.leave(); const text = ctx.i18n.t('wizard_exit'); await ctx.reply(text); diff --git a/bot/middleware/user.js b/bot/middleware/user.ts similarity index 53% rename from bot/middleware/user.js rename to bot/middleware/user.ts index 68098982..03ce55ac 100644 --- a/bot/middleware/user.js +++ b/bot/middleware/user.ts @@ -1,10 +1,8 @@ -const { - validateUser, - validateAdmin, - validateSuperAdmin, -} = require('../validations'); +import { MainContext } from "../start"; -exports.userMiddleware = async (ctx, next) => { +import { validateUser, validateAdmin, validateSuperAdmin } from '../validations'; + +export const userMiddleware = async (ctx: MainContext, next: () => void) => { const user = await validateUser(ctx, false); if (!user) return false; ctx.i18n.locale(user.lang); @@ -13,7 +11,7 @@ exports.userMiddleware = async (ctx, next) => { next(); }; -exports.adminMiddleware = async (ctx, next) => { +export const adminMiddleware = async (ctx: MainContext, next: () => void) => { const admin = await validateAdmin(ctx); if (!admin) return false; ctx.i18n.locale(admin.lang); @@ -22,7 +20,7 @@ exports.adminMiddleware = async (ctx, next) => { next(); }; -exports.superAdminMiddleware = async (ctx, next) => { +export const superAdminMiddleware = async (ctx: MainContext, next: () => void) => { const admin = await validateSuperAdmin(ctx); if (!admin) return false; ctx.i18n.locale(admin.lang); From fc03b903b5d1ca5616e553dc4dec99d72dc59825 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 8 Aug 2024 10:03:15 +0200 Subject: [PATCH 08/22] bot,models: convert bot to TS Convert bot module (except bot/modules/orders) to TypeScript. Had to make several changes to IOrder model to allow some fields to have null or undefined value, as there is code that assigns these values. Also changed signature of several functions in bot/messages.ts so that `bot` argument now has `MainContext` type instead of `Telegraf`, because that's what gets passed by code calling those functions. --- bot/{commands.js => commands.ts} | 135 ++++++++++++--------- bot/{index.js => index.ts} | 0 bot/messages.ts | 62 +++++----- bot/modules/community/communityContext.ts | 7 +- bot/modules/dispute/commands.ts | 2 +- bot/{ordersActions.js => ordersActions.ts} | 102 +++++++++++----- bot/{scenes.js => scenes.ts} | 68 +++++++---- bot/validations.ts | 6 +- models/order.ts | 12 +- 9 files changed, 241 insertions(+), 153 deletions(-) rename bot/{commands.js => commands.ts} (83%) rename bot/{index.js => index.ts} (100%) rename bot/{ordersActions.js => ordersActions.ts} (73%) rename bot/{scenes.js => scenes.ts} (77%) diff --git a/bot/commands.js b/bot/commands.ts similarity index 83% rename from bot/commands.js rename to bot/commands.ts index ae768f2a..912be936 100644 --- a/bot/commands.js +++ b/bot/commands.ts @@ -1,29 +1,27 @@ -const { - validateFiatSentOrder, - validateReleaseOrder, -} = require('./validations'); +import { validateFiatSentOrder, validateReleaseOrder } from './validations'; const { createHoldInvoice, subscribeInvoice, cancelHoldInvoice, settleHoldInvoice, } = require('../ln'); -const { Order, User, Dispute } = require('../models'); -const messages = require('./messages'); -const { - getBtcFiatPrice, - deleteOrderFromChannel, - getUserI18nContext, - getFee, -} = require('../util'); -const ordersActions = require('./ordersActions'); +import { Order, User, Dispute } from '../models'; +import * as messages from './messages'; +import { getBtcFiatPrice, deleteOrderFromChannel, getUserI18nContext, getFee } from '../util'; +import * as ordersActions from './ordersActions'; const OrderEvents = require('./modules/events/orders'); const { removeLightningPrefix } = require('../util'); -const { resolvLightningAddress } = require('../lnurl/lnurl-pay'); -const { logger } = require('../logger'); +import { resolvLightningAddress } from '../lnurl/lnurl-pay'; +import { logger } from '../logger'; +import { Telegraf } from 'telegraf'; +import { IOrder } from '../models/order'; +import { UserDocument } from '../models/user'; +import { MainContext } from './start'; +import { CommunityContext } from './modules/community/communityContext'; +import { Types } from 'mongoose'; -const waitPayment = async (ctx, bot, buyer, seller, order, buyerInvoice) => { +const waitPayment = async (ctx: MainContext, bot: MainContext, buyer: UserDocument, seller: UserDocument, order: IOrder, buyerInvoice: any) => { try { // If there is not fiat amount the function don't do anything if (order.fiat_amount === undefined) { @@ -64,13 +62,15 @@ const waitPayment = async (ctx, bot, buyer, seller, order, buyerInvoice) => { }); order.hash = hash; order.secret = secret; - order.taken_at = Date.now(); + order.taken_at = new Date(); order.status = 'WAITING_PAYMENT'; // We monitor the invoice to know when the seller makes the payment await subscribeInvoice(bot, hash); // We pass the buyer for rate and age calculations const buyer = await User.findById(order.buyer_id); + if(buyer === null) + throw new Error("buyer was not found"); // We send the hold invoice to the seller await messages.invoicePaymentRequestMessage( ctx, @@ -88,12 +88,12 @@ const waitPayment = async (ctx, bot, buyer, seller, order, buyerInvoice) => { } }; -const addInvoice = async (ctx, bot, order) => { +const addInvoice = async (ctx: CommunityContext, bot: MainContext, order: IOrder | null) => { try { ctx.deleteMessage(); ctx.scene.leave(); if (!order) { - const orderId = ctx.update.callback_query.message.text; + const orderId = (ctx.update as any).callback_query.message.text; if (!orderId) return; order = await Order.findOne({ _id: orderId }); if (!order) return; @@ -105,6 +105,8 @@ const addInvoice = async (ctx, bot, order) => { } const buyer = await User.findOne({ _id: order.buyer_id }); + if (buyer === null) + throw new Error("buyer was not found"); if (order.fiat_amount === undefined) { ctx.scene.enter('ADD_FIAT_AMOUNT_WIZARD_SCENE_ID', { @@ -115,9 +117,11 @@ const addInvoice = async (ctx, bot, order) => { return; } - let amount = order.amount; + let amount: number | undefined = order.amount; if (amount === 0) { amount = await getBtcFiatPrice(order.fiat_code, order.fiat_amount); + if(amount === undefined) + throw new Error("amount is undefined"); const marginPercent = order.price_margin / 100; amount = amount - amount * marginPercent; amount = Math.floor(amount); @@ -132,6 +136,8 @@ const addInvoice = async (ctx, bot, order) => { } await order.save(); const seller = await User.findOne({ _id: order.seller_id }); + if (seller === null) + throw new Error("seller was not found"); if (buyer.lightning_address) { const laRes = await resolvLightningAddress( @@ -171,21 +177,25 @@ const addInvoice = async (ctx, bot, order) => { } }; -const rateUser = async (ctx, bot, rating, orderId) => { +const rateUser = async (ctx: CommunityContext, bot: MainContext, rating: number, orderId: string) => { try { ctx.deleteMessage(); ctx.scene.leave(); - const callerId = ctx.from.id; + const callerId = ctx.from?.id; if (!orderId) return; const order = await Order.findOne({ _id: orderId }); - if (!order) return; + if (order === null) return; const buyer = await User.findOne({ _id: order.buyer_id }); + if (buyer === null) + throw new Error("buyer was not found"); const seller = await User.findOne({ _id: order.seller_id }); + if (seller === null) + throw new Error("seller was not found"); let targetUser = buyer; - if (callerId == buyer.tg_id) { + if (String(callerId) == buyer?.tg_id) { targetUser = seller; } @@ -201,7 +211,7 @@ const rateUser = async (ctx, bot, rating, orderId) => { } }; -const saveUserReview = async (targetUser, rating) => { +const saveUserReview = async (targetUser: UserDocument, rating: number) => { try { let totalReviews = targetUser.total_reviews ? targetUser.total_reviews @@ -230,29 +240,29 @@ const saveUserReview = async (targetUser, rating) => { } }; -const cancelAddInvoice = async (ctx, order, job) => { +const cancelAddInvoice = async (ctx: CommunityContext, order: IOrder | null, job?: any) => { try { let userAction = false; - let userTgId = false; + let userTgId = null; if (!job) { ctx.deleteMessage(); ctx.scene.leave(); userAction = true; - userTgId = ctx.from.id; - if (!order) { - const orderId = !!ctx && ctx.update.callback_query.message.text; + userTgId = String(ctx.from.id); + if (order === null) { + const orderId = !!ctx && (ctx.update as any).callback_query.message.text; if (!orderId) return; order = await Order.findOne({ _id: orderId }); - if (!order) return; } } + if (order === null) return; // We make sure the seller can't send us sats now await cancelHoldInvoice({ hash: order.hash }); const user = await User.findOne({ _id: order.buyer_id }); - if (!user) return; + if (user == null) return; const i18nCtx = await getUserI18nContext(user); // Buyers only can cancel orders with status WAITING_BUYER_INVOICE @@ -260,6 +270,8 @@ const cancelAddInvoice = async (ctx, order, job) => { return await messages.genericErrorMessage(ctx, user, i18nCtx); const sellerUser = await User.findOne({ _id: order.seller_id }); + if(sellerUser === null) + throw new Error("sellerUser was not found"); const buyerUser = await User.findOne({ _id: order.buyer_id }); const sellerTgId = sellerUser.tg_id; // If order creator cancels it, it will not be republished @@ -345,14 +357,14 @@ const cancelAddInvoice = async (ctx, order, job) => { } }; -const showHoldInvoice = async (ctx, bot, order) => { +const showHoldInvoice = async (ctx: CommunityContext, bot: MainContext, order: IOrder | null) => { try { ctx.deleteMessage(); if (!order) { - const orderId = ctx.update.callback_query.message.text; + const orderId = (ctx.update as any).callback_query.message.text; if (!orderId) return; order = await Order.findOne({ _id: orderId }); - if (!order) return; + if (order === null) return; } const user = await User.findOne({ _id: order.seller_id }); @@ -383,6 +395,8 @@ const showHoldInvoice = async (ctx, bot, order) => { let amount; if (order.amount === 0) { amount = await getBtcFiatPrice(order.fiat_code, order.fiat_amount); + if(amount === undefined) + throw new Error("amount is undefined"); const marginPercent = order.price_margin / 100; amount = amount - amount * marginPercent; amount = Math.floor(amount); @@ -412,22 +426,22 @@ const showHoldInvoice = async (ctx, bot, order) => { } }; -const cancelShowHoldInvoice = async (ctx, order, job) => { +const cancelShowHoldInvoice = async (ctx: CommunityContext, order: IOrder | null, job?: any) => { try { let userAction = false; - let userTgId = false; + let userTgId = null; if (!job) { ctx.deleteMessage(); ctx.scene.leave(); userAction = true; - userTgId = ctx.from.id; - if (!order) { - const orderId = !!ctx && ctx.update.callback_query.message.text; + userTgId = String(ctx.from.id); + if (order === null) { + const orderId = !!ctx && (ctx.update as any).callback_query.message.text; if (!orderId) return; order = await Order.findOne({ _id: orderId }); - if (!order) return; } } + if (order === null) return; // We make sure the seller can't send us sats now await cancelHoldInvoice({ hash: order.hash }); @@ -440,6 +454,8 @@ const cancelShowHoldInvoice = async (ctx, order, job) => { return await messages.genericErrorMessage(ctx, user, i18nCtx); const buyerUser = await User.findOne({ _id: order.buyer_id }); + if(buyerUser === null) + throw new Error("buyerUser was not found"); const sellerUser = await User.findOne({ _id: order.seller_id }); const buyerTgId = buyerUser.tg_id; // If order creator cancels it, it will not be republished @@ -533,17 +549,19 @@ const cancelShowHoldInvoice = async (ctx, order, job) => { * @param {*} order * @returns */ -const addInvoicePHI = async (ctx, bot, orderId) => { +const addInvoicePHI = async (ctx: CommunityContext, bot: MainContext, orderId: string) => { try { ctx.deleteMessage(); const order = await Order.findOne({ _id: orderId }); + if (order === null) + throw new Error("order was not found"); // orders with status PAID_HOLD_INVOICE are released payments if (order.status !== 'PAID_HOLD_INVOICE' && order.status !== 'FROZEN') { return; } const buyer = await User.findOne({ _id: order.buyer_id }); - if (!buyer) return; + if (buyer === null) return; if (order.amount === 0) { await messages.genericErrorMessage(bot, buyer, ctx.i18n); return; @@ -555,16 +573,16 @@ const addInvoicePHI = async (ctx, bot, orderId) => { } }; -const cancelOrder = async (ctx, orderId, user) => { +const cancelOrder = async (ctx: CommunityContext, orderId: string, user: UserDocument | null) => { try { - if (!user) { - const tgUser = ctx.update.callback_query.from; + if (user === null) { + const tgUser = (ctx.update as any).callback_query.from; if (!tgUser) return; user = await User.findOne({ tg_id: tgUser.id }); // If user didn't initialize the bot we can't do anything - if (!user) return; + if (user == null) return; } if (user.banned) return await messages.bannedUserErrorMessage(ctx, user); const order = await ordersActions.getOrder(ctx, user, orderId); @@ -622,18 +640,23 @@ const cancelOrder = async (ctx, orderId, user) => { initiator = 'seller'; counterParty = 'buyer'; } + if (counterPartyUser == null) + throw new Error("counterPartyUser was not found"); - if (order[`${initiator}_cooperativecancel`]) + const initiatorCooperativeCancelProperty = + initiator == 'seller' ? 'seller_cooperativecancel' : 'buyer_cooperativecancel'; + + if (order[initiatorCooperativeCancelProperty]) return await messages.shouldWaitCooperativeCancelMessage( ctx, initiatorUser ); - order[`${initiator}_cooperativecancel`] = true; + order[initiatorCooperativeCancelProperty] = true; const i18nCtxCP = await getUserI18nContext(counterPartyUser); // If the counter party already requested a cooperative cancel order - if (order[`${counterParty}_cooperativecancel`]) { + if (counterParty == 'seller' ? order.seller_cooperativecancel : order.buyer_cooperativecancel) { // If we already have a holdInvoice we cancel it and return the money if (order.hash) await cancelHoldInvoice({ hash: order.hash }); @@ -676,10 +699,10 @@ const cancelOrder = async (ctx, orderId, user) => { } }; -const fiatSent = async (ctx, orderId, user) => { +const fiatSent = async (ctx: MainContext, orderId: string, user: UserDocument | null) => { try { if (!user) { - const tgUser = ctx.update.callback_query.from; + const tgUser = (ctx.update as any).callback_query.from; if (!tgUser) return; user = await User.findOne({ tg_id: tgUser.id }); @@ -693,6 +716,8 @@ const fiatSent = async (ctx, orderId, user) => { order.status = 'FIAT_SENT'; const seller = await User.findOne({ _id: order.seller_id }); + if (seller === null) + throw new Error("seller was not found"); await order.save(); // We sent messages to both parties // We need to create i18n context for each user @@ -710,10 +735,10 @@ const fiatSent = async (ctx, orderId, user) => { } }; -const release = async (ctx, orderId, user) => { +const release = async (ctx: MainContext, orderId: string, user: UserDocument | null) => { try { if (!user) { - const tgUser = ctx.update.callback_query.from; + const tgUser = (ctx.update as any).callback_query.from; if (!tgUser) return; user = await User.findOne({ tg_id: tgUser.id }); @@ -738,7 +763,7 @@ const release = async (ctx, orderId, user) => { } }; -module.exports = { +export { rateUser, saveUserReview, cancelAddInvoice, diff --git a/bot/index.js b/bot/index.ts similarity index 100% rename from bot/index.js rename to bot/index.ts diff --git a/bot/messages.ts b/bot/messages.ts index 076ca17d..986ce806 100644 --- a/bot/messages.ts +++ b/bot/messages.ts @@ -45,7 +45,7 @@ const startMessage = async (ctx: MainContext) => { } }; -const initBotErrorMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { +const initBotErrorMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { // Correct way to handle errors: https://github.com/telegraf/telegraf/issues/1757 await bot.telegram.sendMessage(user.tg_id, ctx.i18n.t('init_bot_error')).catch((error) => { if ( @@ -103,7 +103,7 @@ const invoicePaymentRequestMessage = async ( } }; -const pendingSellMessage = async (ctx: Telegraf, user: UserDocument, order: IOrder, channel: string, i18n: I18nContext) => { +const pendingSellMessage = async (ctx: MainContext, user: UserDocument, order: IOrder, channel: string, i18n: I18nContext) => { try { const orderExpirationWindow = Number(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW) / 60 / 60; @@ -124,7 +124,7 @@ const pendingSellMessage = async (ctx: Telegraf, user: UserDocument } }; -const pendingBuyMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, channel: string, i18n: I18nContext) => { +const pendingBuyMessage = async (bot: MainContext, user: UserDocument, order: IOrder, channel: string, i18n: I18nContext) => { try { const orderExpirationWindow = Number(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW) / 60 / 60; @@ -268,7 +268,7 @@ const invoiceInvalidMessage = async (ctx: MainContext) => { } }; -const invalidOrderMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { +const invalidOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { try { await bot.telegram.sendMessage(user.tg_id, ctx.i18n.t('order_id_invalid')); } catch (error) { @@ -276,7 +276,7 @@ const invalidOrderMessage = async (ctx: MainContext, bot: Telegraf, } }; -const invalidTypeOrderMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument, type: IOrder["type"]) => { +const invalidTypeOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument, type: IOrder["type"]) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -287,7 +287,7 @@ const invalidTypeOrderMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { +const alreadyTakenOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -298,7 +298,7 @@ const alreadyTakenOrderMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { +const invalidDataMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { try { await bot.telegram.sendMessage(user.tg_id, ctx.i18n.t('invalid_data')); } catch (error) { @@ -306,7 +306,7 @@ const invalidDataMessage = async (ctx: MainContext, bot: Telegraf, } }; -const genericErrorMessage = async (bot: Telegraf, user: UserDocument, i18n: I18nContext) => { +const genericErrorMessage = async (bot: MainContext, user: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage(user.tg_id, i18n.t('generic_error')); } catch (error) { @@ -314,7 +314,7 @@ const genericErrorMessage = async (bot: Telegraf, user: UserDocumen } }; -const beginTakeBuyMessage = async (ctx: MainContext, bot: Telegraf, seller: UserDocument, order: IOrder) => { +const beginTakeBuyMessage = async (ctx: MainContext, bot: MainContext, seller: UserDocument, order: IOrder) => { try { const expirationTime = Number(process.env.HOLD_INVOICE_EXPIRATION_WINDOW) / 60; @@ -380,7 +380,7 @@ const showHoldInvoiceMessage = async ( }; const onGoingTakeBuyMessage = async ( - bot: Telegraf, + bot: MainContext, seller: UserDocument, buyer: UserDocument, order: IOrder, @@ -423,7 +423,7 @@ const onGoingTakeBuyMessage = async ( } }; -const beginTakeSellMessage = async (ctx: MainContext, bot: Telegraf, buyer: UserDocument, order: IOrder) => { +const beginTakeSellMessage = async (ctx: MainContext, bot: MainContext, buyer: UserDocument, order: IOrder) => { try { const holdInvoiceExpiration = holdInvoiceExpirationInSecs(); const orderExpiration = @@ -457,7 +457,7 @@ const beginTakeSellMessage = async (ctx: MainContext, bot: Telegraf }; const onGoingTakeSellMessage = async ( - bot: Telegraf, + bot: MainContext, sellerUser: UserDocument, buyerUser: UserDocument, order: IOrder, @@ -497,7 +497,7 @@ const onGoingTakeSellMessage = async ( const takeSellWaitingSellerToPayMessage = async ( ctx: MainContext, - bot: Telegraf, + bot: MainContext, buyerUser: UserDocument, order: IOrder ) => { @@ -512,7 +512,7 @@ const takeSellWaitingSellerToPayMessage = async ( }; const releasedSatsMessage = async ( - bot: Telegraf, + bot: MainContext, sellerUser: UserDocument, buyerUser: UserDocument, i18nBuyer: I18nContext, @@ -578,11 +578,11 @@ const notOrderMessage = async (ctx: MainContext) => { }; const publishBuyOrderMessage = async ( - bot: Telegraf, + bot: MainContext, user: UserDocument, order: IOrder, i18n: I18nContext, - messageToUser: boolean + messageToUser: boolean = false ) => { try { let publishMessage = `⚡️🍊⚡️\n${order.description}\n`; @@ -614,7 +614,7 @@ const publishBuyOrderMessage = async ( }; const publishSellOrderMessage = async ( - ctx: Telegraf, + ctx: MainContext, user: UserDocument, order: IOrder, i18n: I18nContext, @@ -830,7 +830,7 @@ const notValidIdMessage = async (ctx: MainContext) => { } }; -const addInvoiceMessage = async (ctx: MainContext, bot: Telegraf, buyer: UserDocument, seller: UserDocument, order: IOrder) => { +const addInvoiceMessage = async (ctx: MainContext, bot: MainContext, buyer: UserDocument, seller: UserDocument, order: IOrder) => { try { await bot.telegram.sendMessage( buyer.tg_id, @@ -852,7 +852,7 @@ const addInvoiceMessage = async (ctx: MainContext, bot: Telegraf, b } }; -const sendBuyerInfo2SellerMessage = async (bot: Telegraf, buyer: UserDocument, seller: UserDocument, order: IOrder, i18n: I18nContext) => { +const sendBuyerInfo2SellerMessage = async (bot: MainContext, buyer: UserDocument, seller: UserDocument, order: IOrder, i18n: I18nContext) => { try { await bot.telegram.sendMessage( seller.tg_id, @@ -869,7 +869,7 @@ const sendBuyerInfo2SellerMessage = async (bot: Telegraf, buyer: Us } }; -const cantTakeOwnOrderMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { +const cantTakeOwnOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -900,7 +900,7 @@ const notOrdersMessage = async (ctx: MainContext) => { } }; -const notRateForCurrency = async (bot: Telegraf, user: UserDocument, i18n: I18nContext) => { +const notRateForCurrency = async (bot: MainContext, user: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1092,7 +1092,7 @@ const counterPartyWantsCooperativeCancelMessage = async ( } }; -const invoicePaymentFailedMessage = async (bot: Telegraf, user: UserDocument, i18n: I18nContext) => { +const invoicePaymentFailedMessage = async (bot: MainContext, user: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1106,7 +1106,7 @@ const invoicePaymentFailedMessage = async (bot: Telegraf, user: Use } }; -const userCantTakeMoreThanOneWaitingOrderMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { +const userCantTakeMoreThanOneWaitingOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1157,7 +1157,7 @@ const showInfoMessage = async (ctx: MainContext, user: UserDocument, config: ICo } }; -const buyerReceivedSatsMessage = async (bot: Telegraf, buyerUser: UserDocument, sellerUser: UserDocument, i18n: I18nContext) => { +const buyerReceivedSatsMessage = async (bot: MainContext, buyerUser: UserDocument, sellerUser: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage( buyerUser.tg_id, @@ -1182,7 +1182,7 @@ const listCurrenciesResponse = async (ctx: MainContext, currencies: Array } }; -const priceApiFailedMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { +const priceApiFailedMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1378,7 +1378,7 @@ const toSellerExpiredOrderMessage = async (bot: Telegraf, user: Use } }; -const toBuyerDidntAddInvoiceMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { +const toBuyerDidntAddInvoiceMessage = async (bot: MainContext, user: UserDocument, order: IOrder, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1389,7 +1389,7 @@ const toBuyerDidntAddInvoiceMessage = async (bot: Telegraf, user: U } }; -const toSellerBuyerDidntAddInvoiceMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { +const toSellerBuyerDidntAddInvoiceMessage = async (bot: MainContext, user: UserDocument, order: IOrder, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1401,7 +1401,7 @@ const toSellerBuyerDidntAddInvoiceMessage = async (bot: Telegraf, u }; const toAdminChannelBuyerDidntAddInvoiceMessage = async ( - bot: Telegraf, + bot: MainContext, user: UserDocument, order: IOrder, i18n: I18nContext @@ -1419,7 +1419,7 @@ const toAdminChannelBuyerDidntAddInvoiceMessage = async ( } }; -const toSellerDidntPayInvoiceMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { +const toSellerDidntPayInvoiceMessage = async (bot: MainContext, user: UserDocument, order: IOrder, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1430,7 +1430,7 @@ const toSellerDidntPayInvoiceMessage = async (bot: Telegraf, user: } }; -const toBuyerSellerDidntPayInvoiceMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { +const toBuyerSellerDidntPayInvoiceMessage = async (bot: MainContext, user: UserDocument, order: IOrder, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1442,7 +1442,7 @@ const toBuyerSellerDidntPayInvoiceMessage = async (bot: Telegraf, u }; const toAdminChannelSellerDidntPayInvoiceMessage = async ( - bot: Telegraf, + bot: MainContext, user: UserDocument, order: IOrder, i18n: I18nContext diff --git a/bot/modules/community/communityContext.ts b/bot/modules/community/communityContext.ts index 7c891bfe..8218566b 100644 --- a/bot/modules/community/communityContext.ts +++ b/bot/modules/community/communityContext.ts @@ -3,6 +3,8 @@ import { SceneContextScene, WizardContextWizard, WizardSessionData } from 'teleg import { Update, Message } from 'telegraf/typings/core/types/typegram'; import { Scenes, Telegraf } from 'telegraf'; import { ICommunity, IOrderChannel, IUsernameId } from '../../../models/community'; +import { IOrder } from '../../../models/order'; +import { UserDocument } from '../../../models/user'; export interface CommunityContext extends MainContext { scene: SceneContextScene; @@ -22,7 +24,10 @@ export interface CommunityWizardState { statusMessage: any; currentStatusText: string; community: ICommunity; - bot: Telegraf; + order: IOrder; + buyer: UserDocument; + seller: UserDocument; + bot: CommunityContext; message: Message.TextMessage | undefined; error?: any; feedback?: any; diff --git a/bot/modules/dispute/commands.ts b/bot/modules/dispute/commands.ts index 11c819d5..df3d7690 100644 --- a/bot/modules/dispute/commands.ts +++ b/bot/modules/dispute/commands.ts @@ -24,7 +24,7 @@ const dispute = async (ctx: MainContext) => { const secsUntilDispute = parseInt(disputStartWindow); const time = new Date(); time.setSeconds(time.getSeconds() - secsUntilDispute); - if (order.taken_at > time) { + if (order.taken_at !== null && order.taken_at > time) { return await messages.disputeTooSoonMessage(ctx); } diff --git a/bot/ordersActions.js b/bot/ordersActions.ts similarity index 73% rename from bot/ordersActions.js rename to bot/ordersActions.ts index ba25bb39..29e84247 100644 --- a/bot/ordersActions.js +++ b/bot/ordersActions.ts @@ -1,22 +1,52 @@ const { ObjectId } = require('mongoose').Types; -const { Order, Community } = require('../models'); -const messages = require('./messages'); -const { - getCurrency, - numberFormat, - getBtcExchangePrice, - getFee, - getUserAge, - getStars, -} = require('../util'); -const { logger } = require('../logger'); +import { Order, Community } from '../models'; +import * as messages from './messages'; +import { getCurrency, numberFormat, getBtcExchangePrice, getFee, getUserAge, getStars } from '../util'; +import { logger } from '../logger'; +import { I18nContext } from '@grammyjs/i18n'; +import { UserDocument } from '../models/user'; +import { MainContext } from './start'; +import { IOrder } from '../models/order'; +import { IFiat } from '../util/fiatModel'; const OrderEvents = require('./modules/events/orders'); +interface CreateOrderArguments { + type: string; + amount: string; + fiatAmount: number[]; + fiatCode: string; + paymentMethod: string; + status: string; + priceMargin: any; // ? + range_parent_id: string; + tgChatId: string; + tgOrderMessage: string; + community_id: string; +} + +interface BuildDescriptionArguments { + user: UserDocument; + type: string; + amount: number; + fiatAmount: number[]; + fiatCode: string; + paymentMethod: string; + priceMargin: any; // ? + priceFromAPI: boolean; + currency: IFiat; +} + +interface FiatAmountData { + fiat_amount?: number; + min_amount?: number; + max_amount?: number; +} + const createOrder = async ( - i18n, - bot, - user, + i18n: I18nContext, + bot: MainContext, + user: UserDocument, { type, amount, @@ -29,22 +59,30 @@ const createOrder = async ( tgChatId, tgOrderMessage, community_id, - } + }: CreateOrderArguments ) => { try { - amount = parseInt(amount); + const amountAsNumber = parseInt(amount); let isPublic = true; if (community_id) { const community = await Community.findById(community_id); + if(community == null) + throw new Error("community is null"); isPublic = community.public; } - const fee = await getFee(amount, community_id); + const fee = await getFee(amountAsNumber, community_id); + if(process.env.MAX_FEE === undefined) + throw new Error("Environment variable MAX_FEE is not defined"); + if(process.env.FEE_PERCENT === undefined) + throw new Error("Environment variable FEE_PERCENT is not defined"); // Global fee values at the moment of the order creation // We will need this to calculate the final amount const botFee = parseFloat(process.env.MAX_FEE); const communityFee = parseFloat(process.env.FEE_PERCENT); const currency = getCurrency(fiatCode); - const priceFromAPI = !amount; + if(currency == null) + throw new Error("currency is null"); + const priceFromAPI = !amountAsNumber; if (priceFromAPI && !currency.price) { await messages.notRateForCurrency(bot, user, i18n); @@ -55,7 +93,7 @@ const createOrder = async ( const baseOrderData = { ...fiatAmountData, - amount, + amountAsNumber, fee, bot_fee: botFee, community_fee: communityFee, @@ -71,7 +109,7 @@ const createOrder = async ( description: buildDescription(i18n, { user, type, - amount, + amount: amountAsNumber, fiatAmount, fiatCode, paymentMethod, @@ -109,8 +147,8 @@ const createOrder = async ( } }; -const getFiatAmountData = fiatAmount => { - const response = {}; +const getFiatAmountData = (fiatAmount: number[]) => { + const response: FiatAmountData = {}; if (fiatAmount.length === 2) { response.min_amount = fiatAmount[0]; response.max_amount = fiatAmount[1]; @@ -122,7 +160,7 @@ const getFiatAmountData = fiatAmount => { }; const buildDescription = ( - i18n, + i18n: I18nContext, { user, type, @@ -133,7 +171,7 @@ const buildDescription = ( priceMargin, priceFromAPI, currency, - } + } : BuildDescriptionArguments ) => { try { const action = type === 'sell' ? i18n.t('selling') : i18n.t('buying'); @@ -171,9 +209,11 @@ const buildDescription = ( i18n.t('rate') + `: ${process.env.FIAT_RATE_NAME} ${priceMarginText}\n`; } else { const exchangePrice = getBtcExchangePrice(fiatAmount[0], amount); + if (exchangePrice == null) + throw new Error("exchangePrice is null"); tasaText = i18n.t('price') + - `: ${numberFormat(fiatCode, exchangePrice.toFixed(2))}\n`; + `: ${numberFormat(fiatCode, Number(exchangePrice.toFixed(2)))}\n`; } let rateText = '\n'; @@ -200,7 +240,7 @@ const buildDescription = ( } }; -const getOrder = async (ctx, user, orderId) => { +const getOrder = async (ctx: MainContext, user: UserDocument, orderId: string) => { try { if (!ObjectId.isValid(orderId)) { await messages.notValidIdMessage(ctx); @@ -225,9 +265,9 @@ const getOrder = async (ctx, user, orderId) => { } }; -const getOrders = async (user, status) => { +const getOrders = async (user: UserDocument, status: string) => { try { - const where = { + const where: any = { $and: [ { $or: [{ buyer_id: user._id }, { seller_id: user._id }], @@ -256,11 +296,11 @@ const getOrders = async (user, status) => { } }; -const getNewRangeOrderPayload = async order => { +const getNewRangeOrderPayload = async (order: IOrder) => { try { let newMaxAmount = 0; - if (order.max_amount !== undefined) { + if (order.max_amount !== undefined && order.fiat_amount !== undefined) { newMaxAmount = order.max_amount - order.fiat_amount; } @@ -290,7 +330,7 @@ const getNewRangeOrderPayload = async order => { } }; -module.exports = { +export { createOrder, getOrder, getOrders, diff --git a/bot/scenes.js b/bot/scenes.ts similarity index 77% rename from bot/scenes.js rename to bot/scenes.ts index 76b190f4..c88a0f2e 100644 --- a/bot/scenes.js +++ b/bot/scenes.ts @@ -1,23 +1,33 @@ -const { Scenes } = require('telegraf'); +import { Scenes } from 'telegraf'; const { parsePaymentRequest } = require('invoices'); -const { isValidInvoice, validateLightningAddress } = require('./validations'); -const { Order, PendingPayment } = require('../models'); -const { waitPayment, addInvoice, showHoldInvoice } = require('./commands'); -const { getCurrency, getUserI18nContext } = require('../util'); -const messages = require('./messages'); +import { isValidInvoice, validateLightningAddress } from './validations'; +import { Order, PendingPayment } from '../models'; +import { waitPayment, addInvoice, showHoldInvoice } from './commands'; +import { getCurrency, getUserI18nContext } from '../util'; +import * as messages from './messages'; const { isPendingPayment } = require('../ln'); -const { logger } = require('../logger'); -const { resolvLightningAddress } = require('../lnurl/lnurl-pay'); +import { logger } from '../logger'; +import { resolvLightningAddress } from '../lnurl/lnurl-pay'; +import { CommunityContext } from './modules/community/communityContext'; +const OrderEvents = require('./modules/events/orders'); + +interface InvoiceParseResult { + invoice?: any; + success?: boolean +} const addInvoiceWizard = new Scenes.WizardScene( 'ADD_INVOICE_WIZARD_SCENE_ID', async ctx => { try { - const { order } = ctx.wizard.state; - const expirationTime = - parseInt(process.env.HOLD_INVOICE_EXPIRATION_WINDOW) / 60; + const communityCtx = ctx as CommunityContext; + const { order } = communityCtx.wizard.state; + const holdInvoiceExpirationWindow = process.env.HOLD_INVOICE_EXPIRATION_WINDOW; + if(holdInvoiceExpirationWindow === undefined) + throw new Error("Enviroment variable HOLD_INVOICE_EXPIRATION_WINDOW not defined"); + const expirationTime = parseInt(holdInvoiceExpirationWindow) / 60; await messages.wizardAddInvoiceInitMessage( - ctx, + communityCtx, order, order.fiat_code, expirationTime @@ -30,23 +40,26 @@ const addInvoiceWizard = new Scenes.WizardScene( logger.error(error); } }, - async ctx => { + async (ctx: CommunityContext) => { try { if (ctx.message === undefined) return ctx.scene.leave(); - if (ctx.message.document) + if ((ctx.message as any).document) return await ctx.reply(ctx.i18n.t('must_enter_text')); let { bot, buyer, seller, order } = ctx.wizard.state; // We get an updated order from the DB - order = await Order.findOne({ _id: order._id }); - if (!order) { + const updatedOrder = await Order.findOne({ _id: order._id }); + if(updatedOrder === null) { await ctx.reply(ctx.i18n.t('generic_error')); return ctx.scene.leave(); } + else { + order = updatedOrder; + } let lnInvoice = ctx.message.text.trim(); const isValidLN = await validateLightningAddress(lnInvoice); - let res = {}; + let res: InvoiceParseResult = {}; if (isValidLN) { const laRes = await resolvLightningAddress( lnInvoice, @@ -84,7 +97,7 @@ const addInvoiceWizard = new Scenes.WizardScene( const addInvoicePHIWizard = new Scenes.WizardScene( 'ADD_INVOICE_PHI_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { buyer, order } = ctx.wizard.state; const i18nCtx = await getUserI18nContext(buyer); @@ -95,23 +108,26 @@ const addInvoicePHIWizard = new Scenes.WizardScene( logger.error(error); } }, - async ctx => { + async (ctx: CommunityContext) => { try { if (ctx.message === undefined) return ctx.scene.leave(); - if (ctx.message.document) + if ((ctx.message as any).document) return await ctx.reply(ctx.i18n.t('must_enter_text')); let { buyer, order } = ctx.wizard.state; // We get an updated order from the DB - order = await Order.findOne({ _id: order._id }); - if (!order) { + const updatedOrder = await Order.findOne({ _id: order._id }); + if (updatedOrder === null) { await ctx.reply(ctx.i18n.t('generic_error')); return ctx.scene.leave(); } + else { + order = updatedOrder; + } let lnInvoice = ctx.message.text.trim(); const isValidLN = await validateLightningAddress(lnInvoice); - let res = {}; + let res: InvoiceParseResult = {}; if (isValidLN) { const laRes = await resolvLightningAddress( lnInvoice, @@ -167,7 +183,7 @@ const addInvoicePHIWizard = new Scenes.WizardScene( const addFiatAmountWizard = new Scenes.WizardScene( 'ADD_FIAT_AMOUNT_WIZARD_SCENE_ID', - async ctx => { + async (ctx: CommunityContext) => { try { const { order } = ctx.wizard.state; const action = @@ -199,6 +215,8 @@ const addFiatAmountWizard = new Scenes.WizardScene( order.fiat_amount = fiatAmount; const currency = getCurrency(order.fiat_code); + if (currency === null) + throw new Error("currency is null"); await messages.wizardAddFiatAmountCorrectMessage( ctx, currency, @@ -218,7 +236,7 @@ const addFiatAmountWizard = new Scenes.WizardScene( } ); -module.exports = { +export { addInvoiceWizard, addFiatAmountWizard, addInvoicePHIWizard, diff --git a/bot/validations.ts b/bot/validations.ts index 40819adf..0b60b16e 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -434,7 +434,7 @@ const isValidInvoice = async (ctx: MainContext, lnInvoice: string) => { }; -const validateTakeSellOrder = async (ctx: MainContext, bot: Telegraf, user: UserDocument, order: IOrder) => { +const validateTakeSellOrder = async (ctx: MainContext, bot: MainContext, user: UserDocument, order: IOrder) => { try { if (!order) { await messages.invalidOrderMessage(ctx, bot, user); @@ -463,7 +463,7 @@ const validateTakeSellOrder = async (ctx: MainContext, bot: Telegraf, user: UserDocument, order: IOrder) => { +const validateTakeBuyOrder = async (ctx: MainContext, bot: MainContext, user: UserDocument, order: IOrder) => { try { if (!order) { await messages.invalidOrderMessage(ctx, bot, user); @@ -649,7 +649,7 @@ const validateObjectId = async (ctx: MainContext, id: string) => { } }; -const validateUserWaitingOrder = async (ctx: MainContext, bot: Telegraf, user: UserDocument) => { +const validateUserWaitingOrder = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { try { // If is a seller let where: FilterQuery = { diff --git a/models/order.ts b/models/order.ts index 8441aa9d..9f4da8b3 100644 --- a/models/order.ts +++ b/models/order.ts @@ -9,11 +9,11 @@ export interface IOrder extends Document { bot_fee: number; community_fee: number; routing_fee: number; - hash: string; - secret: string; + hash: string | null; + secret: string | null; creator_id: string; - seller_id: string; - buyer_id: string; + seller_id: string | null; + buyer_id: string | null; buyer_invoice: string; buyer_dispute_token: string; seller_dispute_token: string; @@ -26,12 +26,12 @@ export interface IOrder extends Document { previous_dispute_status: string; status: string; type: string; - fiat_amount: number; + fiat_amount?: number; fiat_code: string; payment_method: string; created_at: Date; invoice_held_at: Date; - taken_at: Date; + taken_at: Date | null; tg_chat_id: string; tg_order_message: string; tg_channel_message1: string | null; From dfae3bea4c92449fa8cc1d19f2741980c3d798fb Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 8 Aug 2024 14:49:32 +0200 Subject: [PATCH 09/22] bot: introduced HasTelegram type Introduced HasTelegram type for use in functions that have `bot` argument, which can be either `Context` or `Telegraf`, but only `telegram` property is used. --- bot/messages.ts | 14 +++++++------- bot/start.ts | 6 +++++- bot/validations.ts | 6 +++--- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bot/messages.ts b/bot/messages.ts index 986ce806..83b7e2e4 100644 --- a/bot/messages.ts +++ b/bot/messages.ts @@ -15,7 +15,7 @@ const { } = require('../util'); const OrderEvents = require('./modules/events/orders'); import { logger } from "../logger"; -import { MainContext } from './start'; +import { HasTelegram, MainContext } from './start'; import { UserDocument } from '../models/user' import { IOrder } from '../models/order' import { Telegraf } from 'telegraf'; @@ -268,7 +268,7 @@ const invoiceInvalidMessage = async (ctx: MainContext) => { } }; -const invalidOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { +const invalidOrderMessage = async (ctx: MainContext, bot: HasTelegram, user: UserDocument) => { try { await bot.telegram.sendMessage(user.tg_id, ctx.i18n.t('order_id_invalid')); } catch (error) { @@ -276,7 +276,7 @@ const invalidOrderMessage = async (ctx: MainContext, bot: MainContext, user: Use } }; -const invalidTypeOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument, type: IOrder["type"]) => { +const invalidTypeOrderMessage = async (ctx: MainContext, bot: HasTelegram, user: UserDocument, type: IOrder["type"]) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -287,7 +287,7 @@ const invalidTypeOrderMessage = async (ctx: MainContext, bot: MainContext, user: } }; -const alreadyTakenOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { +const alreadyTakenOrderMessage = async (ctx: MainContext, bot: HasTelegram, user: UserDocument) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -314,7 +314,7 @@ const genericErrorMessage = async (bot: MainContext, user: UserDocument, i18n: I } }; -const beginTakeBuyMessage = async (ctx: MainContext, bot: MainContext, seller: UserDocument, order: IOrder) => { +const beginTakeBuyMessage = async (ctx: MainContext, bot: HasTelegram, seller: UserDocument, order: IOrder) => { try { const expirationTime = Number(process.env.HOLD_INVOICE_EXPIRATION_WINDOW) / 60; @@ -423,7 +423,7 @@ const onGoingTakeBuyMessage = async ( } }; -const beginTakeSellMessage = async (ctx: MainContext, bot: MainContext, buyer: UserDocument, order: IOrder) => { +const beginTakeSellMessage = async (ctx: MainContext, bot: HasTelegram, buyer: UserDocument, order: IOrder) => { try { const holdInvoiceExpiration = holdInvoiceExpirationInSecs(); const orderExpiration = @@ -869,7 +869,7 @@ const sendBuyerInfo2SellerMessage = async (bot: MainContext, buyer: UserDocument } }; -const cantTakeOwnOrderMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { +const cantTakeOwnOrderMessage = async (ctx: MainContext, bot: HasTelegram, user: UserDocument) => { try { await bot.telegram.sendMessage( user.tg_id, diff --git a/bot/start.ts b/bot/start.ts index 876d4278..422c9676 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -1,4 +1,4 @@ -import { Telegraf, session, Context } from 'telegraf'; +import { Telegraf, session, Context, Telegram } from 'telegraf'; import { I18n, I18nContext } from '@grammyjs/i18n'; import { Message } from 'typegram' import { UserDocument } from '../models/user' @@ -79,6 +79,10 @@ export interface OrderQuery { seller_id?: string; } +export interface HasTelegram { + telegram: Telegram; +} + const askForConfirmation = async (user: UserDocument, command: string) => { try { let orders: any[] = []; diff --git a/bot/validations.ts b/bot/validations.ts index 0b60b16e..ea6ee77c 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -1,4 +1,4 @@ -import { MainContext, OrderQuery, ctxUpdateAssertMsg } from "./start"; +import { HasTelegram, MainContext, OrderQuery, ctxUpdateAssertMsg } from "./start"; import { ICommunity, IUsernameId } from "../models/community"; import { FilterQuery } from "mongoose"; import { UserDocument } from "../models/user"; @@ -434,7 +434,7 @@ const isValidInvoice = async (ctx: MainContext, lnInvoice: string) => { }; -const validateTakeSellOrder = async (ctx: MainContext, bot: MainContext, user: UserDocument, order: IOrder) => { +const validateTakeSellOrder = async (ctx: MainContext, bot: HasTelegram, user: UserDocument, order: IOrder) => { try { if (!order) { await messages.invalidOrderMessage(ctx, bot, user); @@ -463,7 +463,7 @@ const validateTakeSellOrder = async (ctx: MainContext, bot: MainContext, user: U } }; -const validateTakeBuyOrder = async (ctx: MainContext, bot: MainContext, user: UserDocument, order: IOrder) => { +const validateTakeBuyOrder = async (ctx: MainContext, bot: HasTelegram, user: UserDocument, order: IOrder) => { try { if (!order) { await messages.invalidOrderMessage(ctx, bot, user); From d9556d19ead824118084e0dd239c8440d7ab5c1a Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 8 Aug 2024 13:08:02 +0200 Subject: [PATCH 10/22] bot/modules/orders: convert to TS Convert bot/modules/orders module to TypeScript. --- bot/modules/community/communityContext.ts | 10 ++- bot/modules/community/messages.ts | 4 +- .../orders/{commands.js => commands.ts} | 87 +++++++++++-------- bot/modules/orders/{index.js => index.ts} | 39 ++++----- .../orders/{messages.js => messages.ts} | 23 ++--- bot/modules/orders/{scenes.js => scenes.ts} | 62 ++++++------- .../orders/{takeOrder.js => takeOrder.ts} | 31 +++---- bot/ordersActions.ts | 20 ++--- 8 files changed, 145 insertions(+), 131 deletions(-) rename bot/modules/orders/{commands.js => commands.ts} (69%) rename bot/modules/orders/{index.js => index.ts} (66%) rename bot/modules/orders/{messages.js => messages.ts} (74%) rename bot/modules/orders/{scenes.js => scenes.ts} (85%) rename bot/modules/orders/{takeOrder.js => takeOrder.ts} (72%) diff --git a/bot/modules/community/communityContext.ts b/bot/modules/community/communityContext.ts index 8218566b..9975e880 100644 --- a/bot/modules/community/communityContext.ts +++ b/bot/modules/community/communityContext.ts @@ -12,12 +12,18 @@ export interface CommunityContext extends MainContext { message: (Update.New & Update.NonChannel & Message.TextMessage) | undefined; } +// This type is catch-all for any wizard state and probably should be split into several +// more specialized types. export interface CommunityWizardState { name: string; - currencies: any; + currency: string; + currencies: string[]; group: any; channels: IOrderChannel[]; fee: number; + sats: number; + fiatAmount: number[]; + priceMargin: number; solvers: IUsernameId[]; disputeChannel: any; user: any; @@ -27,6 +33,8 @@ export interface CommunityWizardState { order: IOrder; buyer: UserDocument; seller: UserDocument; + type: string; + method: string; bot: CommunityContext; message: Message.TextMessage | undefined; error?: any; diff --git a/bot/modules/community/messages.ts b/bot/modules/community/messages.ts index 386d3129..47aa7a89 100644 --- a/bot/modules/community/messages.ts +++ b/bot/modules/community/messages.ts @@ -7,9 +7,9 @@ import { MainContext } from '../../start'; export const createCommunityWizardStatus = (i18n: I18nContext, state: CommunityWizardState) => { try { - let { name, currencies, group } = state; + let { name, group } = state; name = state.name || '__'; - currencies = state.currencies && state.currencies.join(', '); + let currencies = state.currencies && state.currencies.join(', '); currencies = currencies || '__'; group = state.group || '__'; let channels = diff --git a/bot/modules/orders/commands.js b/bot/modules/orders/commands.ts similarity index 69% rename from bot/modules/orders/commands.js rename to bot/modules/orders/commands.ts index a62fce99..105f59b9 100644 --- a/bot/modules/orders/commands.js +++ b/bot/modules/orders/commands.ts @@ -1,25 +1,32 @@ // @ts-check -const { logger } = require('../../../logger'); -const { Community, Order } = require('../../../models'); -const { isFloat } = require('../../../util'); -const { - validateBuyOrder, - isBannedFromCommunity, - validateSeller, - validateSellOrder, - validateParams, -} = require('../../validations'); -const messages = require('../../messages'); -const ordersActions = require('../../ordersActions'); -const { deletedCommunityMessage } = require('./messages'); -const { takebuy, takesell, takebuyValidation } = require('./takeOrder'); - -const Scenes = require('./scenes'); - -const buyWizard = async ctx => enterWizard(ctx, ctx.user, 'buy'); -const sellWizard = async ctx => enterWizard(ctx, ctx.user, 'sell'); - -const sell = async ctx => { +import { logger } from '../../../logger'; +import { Community, Order } from '../../../models'; +import { isFloat } from '../../../util'; +import { validateBuyOrder, isBannedFromCommunity, validateSeller, validateSellOrder, validateParams } from '../../validations'; +import * as messages from '../../messages'; +import * as ordersActions from '../../ordersActions'; +import { deletedCommunityMessage } from './messages'; +import { takebuy, takesell, takebuyValidation } from './takeOrder'; + +import * as Scenes from './scenes'; +import { CommunityContext } from '../community/communityContext'; +import { MainContext } from '../../start'; +import { UserDocument } from '../../../models/user'; +import { ICommunity } from '../../../models/community'; +import { Chat } from 'telegraf/typings/core/types/typegram'; + +interface EnterWizardState { + community?: ICommunity; + currency?: string; + currencies?: string[]; + type: string; + user: UserDocument; +} + +const buyWizard = async (ctx: CommunityContext) => enterWizard(ctx, ctx.user, 'buy'); +const sellWizard = async (ctx: CommunityContext) => enterWizard(ctx, ctx.user, 'sell'); + +const sell = async (ctx: MainContext) => { try { const user = ctx.user; if (await isMaxPending(user)) @@ -42,10 +49,10 @@ const sell = async ctx => { let community = null; // If this message came from a group // We check if the there is a community for it - if (ctx.message.chat.type !== 'private') { + if (ctx.message?.chat.type !== 'private') { // Allow find communities case insensitive const regex = new RegExp( - ['^', '@' + ctx.message.chat.username, '$'].join(''), + ['^', '@' + (ctx.message?.chat as Chat.UserNameChat).username, '$'].join(''), 'i' ); community = await Community.findOne({ group: regex }); @@ -56,7 +63,7 @@ const sell = async ctx => { communityId = user.default_community_id; community = await Community.findOne({ _id: communityId }); if (!community) { - user.default_community_id = null; + user.default_community_id = undefined; await user.save(); return deletedCommunityMessage(ctx); } @@ -90,7 +97,7 @@ const sell = async ctx => { } }; -const buy = async ctx => { +const buy = async (ctx: MainContext) => { try { const user = ctx.user; if (await isMaxPending(user)) @@ -108,10 +115,10 @@ const buy = async ctx => { let community = null; // If this message came from a group // We check if the there is a community for it - if (ctx.message.chat.type !== 'private') { + if (ctx.message?.chat.type !== 'private') { // Allow find communities case insensitive const regex = new RegExp( - ['^', '@' + ctx.message.chat.username, '$'].join(''), + ['^', '@' + (ctx.message?.chat as Chat.UserNameChat).username, '$'].join(''), 'i' ); community = await Community.findOne({ group: regex }); @@ -124,7 +131,7 @@ const buy = async ctx => { communityId = user.default_community_id; community = await Community.findOne({ _id: communityId }); if (!community) { - user.default_community_id = null; + user.default_community_id = undefined; await user.save(); return deletedCommunityMessage(ctx); } @@ -158,13 +165,15 @@ const buy = async ctx => { } }; -async function enterWizard(ctx, user, type) { - const state = { +async function enterWizard(ctx: CommunityContext, user: UserDocument, type: string) { + const state: EnterWizardState = { type, user, }; if (user.default_community_id) { const comm = await Community.findById(user.default_community_id); + if(comm === null) + throw new Error("comm not found"); state.community = comm; state.currencies = comm.currencies; if (comm.currencies.length === 1) { @@ -178,21 +187,27 @@ async function enterWizard(ctx, user, type) { await ctx.scene.enter(Scenes.CREATE_ORDER, state); } -const isMaxPending = async user => { +const isMaxPending = async (user: UserDocument) => { const pendingOrders = await Order.count({ status: 'PENDING', creator_id: user._id, }); + const maxPendingOrders = process.env.MAX_PENDING_ORDERS; + if(maxPendingOrders === undefined) + throw new Error("Environment variable MAX_PENDING_ORDERS is not defined"); // We don't let users create too PENDING many orders - if (pendingOrders >= parseInt(process.env.MAX_PENDING_ORDERS)) { + if (pendingOrders >= parseInt(maxPendingOrders)) { return true; } return false; }; -const takeOrder = async ctx => { +const takeOrder = async (ctx: MainContext) => { try { - const [orderId] = await validateParams(ctx, 2, '\\<_order id_\\>'); + const validateParamsResult = await validateParams(ctx, 2, '\\<_order id_\\>'); + if(validateParamsResult === null || validateParamsResult.length === 0) + throw new Error("validateParams failed"); + const [orderId] = validateParamsResult; const order = await Order.findOne({ _id: orderId, status: 'PENDING', @@ -211,7 +226,7 @@ const takeOrder = async ctx => { return takesell(ctx, ctx, orderId); } } - } catch (err) { + } catch (err: any) { switch (err.message) { case 'OrderNotFound': return ctx.reply(ctx.i18n.t('order_not_found')); @@ -221,4 +236,4 @@ const takeOrder = async ctx => { } }; -module.exports = { buyWizard, sellWizard, buy, sell, isMaxPending, takeOrder }; +export { buyWizard, sellWizard, buy, sell, isMaxPending, takeOrder }; diff --git a/bot/modules/orders/index.js b/bot/modules/orders/index.ts similarity index 66% rename from bot/modules/orders/index.js rename to bot/modules/orders/index.ts index 46321ece..65fd4c99 100644 --- a/bot/modules/orders/index.js +++ b/bot/modules/orders/index.ts @@ -1,25 +1,18 @@ // @ts-check -const { userMiddleware } = require('../../middleware/user'); -const { logger } = require('../../../logger'); -const ordersActions = require('../../ordersActions'); +import { userMiddleware } from '../../middleware/user'; +import { logger } from '../../../logger'; +import * as ordersActions from '../../ordersActions'; -const commands = require('./commands'); -const messages = require('./messages'); -const { - tooManyPendingOrdersMessage, - notOrdersMessage, -} = require('../../messages'); -const { - takeOrderActionValidation, - takeOrderValidation, - takesell, - takebuyValidation, - takebuy, -} = require('./takeOrder'); -const { extractId } = require('../../../util'); -exports.Scenes = require('./scenes'); +import * as commands from './commands'; +import * as messages from './messages'; +import { tooManyPendingOrdersMessage, notOrdersMessage } from '../../messages'; +import { takeOrderActionValidation, takeOrderValidation, takesell, takebuyValidation, takebuy } from './takeOrder'; +import { extractId } from '../../../util'; +import { Telegraf } from 'telegraf'; +import { CommunityContext } from '../community/communityContext'; +export * as Scenes from './scenes'; -exports.configure = bot => { +export const configure = (bot: Telegraf) => { bot.command( 'takeorder', userMiddleware, @@ -58,6 +51,8 @@ exports.configure = bot => { bot.command('listorders', userMiddleware, async ctx => { try { const orders = await ordersActions.getOrders(ctx.user); + if (orders === undefined) + throw new Error("orders is undefined"); if (orders && orders.length === 0) { return await notOrdersMessage(ctx); } @@ -78,7 +73,7 @@ exports.configure = bot => { takeOrderActionValidation, takeOrderValidation, async ctx => { - const text = ctx.update.callback_query.message.text; + const text = (ctx.update as any).callback_query.message.text; const orderId = extractId(text); if (!orderId) return; await takesell(ctx, bot, orderId); @@ -91,9 +86,9 @@ exports.configure = bot => { takeOrderValidation, takebuyValidation, async ctx => { - const text = ctx.update.callback_query.message.text; + const text: string = (ctx.update as any).callback_query.message.text; const orderId = extractId(text); - if (!orderId) return; + if (orderId === null) return; await takebuy(ctx, bot, orderId); } ); diff --git a/bot/modules/orders/messages.js b/bot/modules/orders/messages.ts similarity index 74% rename from bot/modules/orders/messages.js rename to bot/modules/orders/messages.ts index b6dcec4d..50bf66c2 100644 --- a/bot/modules/orders/messages.js +++ b/bot/modules/orders/messages.ts @@ -1,11 +1,12 @@ -const { - getOrderChannel, - sanitizeMD, - getTimeToExpirationOrder, -} = require('../../../util'); -const { logger } = require('../../../logger'); +import { getOrderChannel, sanitizeMD, getTimeToExpirationOrder } from '../../../util'; +import { logger } from '../../../logger'; +import { IOrder } from '../../../models/order'; +import { I18nContext } from '@grammyjs/i18n'; +import { MainContext } from '../../start'; +import { ExtraReplyMessage } from 'telegraf/typings/telegram-types'; +import { CommunityWizardState } from '../community/communityContext'; -exports.listOrdersResponse = async (orders, i18n) => { +export const listOrdersResponse = async (orders: IOrder[], i18n: I18nContext) => { const tasks = orders.map(async order => { const channel = await getOrderChannel(order); let amount = '\\-'; @@ -19,7 +20,7 @@ exports.listOrdersResponse = async (orders, i18n) => { sanitizeMD(order.max_amount), ].join(''); - if (typeof order.amount !== 'undefined') amount = order.amount; + if (typeof order.amount !== 'undefined') amount = String(order.amount); const timeToExpire = getTimeToExpirationOrder(order, i18n); const details = [ [''].join(''), @@ -42,11 +43,11 @@ exports.listOrdersResponse = async (orders, i18n) => { text: body, extra: { parse_mode: 'MarkdownV2', - }, + } as ExtraReplyMessage, }; }; -exports.createOrderWizardStatus = (i18n, state) => { +export const createOrderWizardStatus = (i18n: I18nContext, state: CommunityWizardState) => { const { type, priceMargin } = state; const action = type === 'sell' ? i18n.t('selling') : i18n.t('buying'); const sats = state.sats ? state.sats + ' ' : ''; @@ -73,7 +74,7 @@ exports.createOrderWizardStatus = (i18n, state) => { return { text }; }; -exports.deletedCommunityMessage = async ctx => { +export const deletedCommunityMessage = async (ctx: MainContext) => { try { await ctx.reply(ctx.i18n.t('community_deleted')); } catch (error) { diff --git a/bot/modules/orders/scenes.js b/bot/modules/orders/scenes.ts similarity index 85% rename from bot/modules/orders/scenes.js rename to bot/modules/orders/scenes.ts index 349622c5..0da3b9f9 100644 --- a/bot/modules/orders/scenes.js +++ b/bot/modules/orders/scenes.ts @@ -1,23 +1,21 @@ -const { Scenes, Markup } = require('telegraf'); -const { logger } = require('../../../logger'); -const { getCurrency } = require('../../../util'); -const ordersActions = require('../../ordersActions'); -const { - publishBuyOrderMessage, - publishSellOrderMessage, -} = require('../../messages'); -const messages = require('./messages'); +import { Scenes, Markup } from 'telegraf'; +import { logger } from '../../../logger'; +import { getCurrency } from '../../../util'; +import * as ordersActions from '../../ordersActions'; +import { publishBuyOrderMessage, publishSellOrderMessage } from '../../messages'; +import * as messages from './messages'; +import { CommunityContext } from '../community/communityContext'; -const CREATE_ORDER = (exports.CREATE_ORDER = 'CREATE_ORDER_WIZARD'); +export const CREATE_ORDER = 'CREATE_ORDER_WIZARD'; -exports.middleware = () => { +export const middleware = () => { const stage = new Scenes.Stage([createOrder]); return stage.middleware(); }; const createOrder = (exports.createOrder = new Scenes.WizardScene( CREATE_ORDER, - async ctx => { + async (ctx: CommunityContext) => { try { const { user, @@ -48,7 +46,7 @@ const createOrder = (exports.createOrder = new Scenes.WizardScene( await ctx.telegram.editMessageText( res.chat.id, res.message_id, - null, + undefined, text ); ctx.wizard.state.currentStatusText = text; @@ -95,7 +93,8 @@ const createOrder = (exports.createOrder = new Scenes.WizardScene( delete ctx.wizard.state.handler; } await ctx.wizard.selectStep(0); - return ctx.wizard.steps[ctx.wizard.cursor](ctx); + // use ["steps"] syntax as steps is private property + return ctx.wizard["steps"][ctx.wizard.cursor](ctx); } catch (err) { logger.error(err); return ctx.scene.leave(); @@ -104,7 +103,7 @@ const createOrder = (exports.createOrder = new Scenes.WizardScene( )); const createOrderSteps = { - async currency(ctx) { + async currency(ctx: CommunityContext) { const prompt = await createOrderPrompts.currency(ctx); const deletePrompt = () => ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); @@ -122,7 +121,7 @@ const createOrderSteps = { await ctx.wizard.state.updateUI(); } else { if (!ctx.callbackQuery) return; - const currency = ctx.callbackQuery.data; + const currency: string = (ctx.callbackQuery as any).data; ctx.wizard.state.currency = currency; await ctx.wizard.state.updateUI(); } @@ -130,7 +129,7 @@ const createOrderSteps = { }; return ctx.wizard.next(); }, - async fiatAmount(ctx) { + async fiatAmount(ctx: CommunityContext) { ctx.wizard.state.handler = async ctx => { await createOrderHandlers.fiatAmount(ctx); return await ctx.telegram.deleteMessage( @@ -141,7 +140,7 @@ const createOrderSteps = { const prompt = await createOrderPrompts.fiatAmount(ctx); return ctx.wizard.next(); }, - async method(ctx) { + async method(ctx: CommunityContext) { ctx.wizard.state.handler = async ctx => { if (ctx.message === undefined) return ctx.scene.leave(); const { text } = ctx.message; @@ -157,7 +156,7 @@ const createOrderSteps = { const prompt = await ctx.reply(ctx.i18n.t('enter_payment_method')); return ctx.wizard.next(); }, - async priceMargin(ctx) { + async priceMargin(ctx: CommunityContext) { const prompt = await createOrderPrompts.priceMargin(ctx); ctx.wizard.state.handler = async ctx => { ctx.wizard.state.error = null; @@ -165,16 +164,17 @@ const createOrderSteps = { if (ctx.message === undefined) return ctx.scene.leave(); const { text } = ctx.message; if (!text) return; + const num = Number(text); await ctx.deleteMessage(); - if (isNaN(text)) { + if (isNaN(num)) { ctx.wizard.state.error = ctx.i18n.t('not_number'); return await ctx.wizard.state.updateUI(); } - ctx.wizard.state.priceMargin = parseInt(text); + ctx.wizard.state.priceMargin = Math.floor(num); await ctx.wizard.state.updateUI(); } else { - ctx.wizard.state.priceMargin = parseInt(ctx.callbackQuery.data); + ctx.wizard.state.priceMargin = parseInt((ctx.callbackQuery as any).data); await ctx.wizard.state.updateUI(); } return await ctx.telegram.deleteMessage( @@ -184,7 +184,7 @@ const createOrderSteps = { }; return ctx.wizard.next(); }, - async sats(ctx) { + async sats(ctx: CommunityContext) { const prompt = await createOrderPrompts.sats(ctx); ctx.wizard.state.handler = async ctx => { const ret = await createOrderHandlers.sats(ctx); @@ -199,7 +199,7 @@ const createOrderSteps = { }; const createOrderPrompts = { - async priceMargin(ctx) { + async priceMargin(ctx: CommunityContext) { const margin = ['-5', '-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4', '+5']; const buttons = margin.map(m => Markup.button.callback(m + '%', m)); const rows = []; @@ -221,7 +221,7 @@ const createOrderPrompts = { Markup.inlineKeyboard(rows) ); }, - async currency(ctx) { + async currency(ctx: CommunityContext) { const { currencies } = ctx.wizard.state; if (!currencies) return ctx.reply(ctx.i18n.t('enter_currency')); const buttons = currencies.map(currency => @@ -238,11 +238,11 @@ const createOrderPrompts = { Markup.inlineKeyboard(rows) ); }, - async fiatAmount(ctx) { + async fiatAmount(ctx: CommunityContext) { const { currency } = ctx.wizard.state; return ctx.reply(ctx.i18n.t('enter_currency_amount', { currency })); }, - async sats(ctx) { + async sats(ctx: CommunityContext) { const button = Markup.button.callback( ctx.i18n.t('market_price'), 'marketPrice' @@ -255,7 +255,7 @@ const createOrderPrompts = { }; const createOrderHandlers = { - async fiatAmount(ctx) { + async fiatAmount(ctx: CommunityContext) { if (ctx.message === undefined) return ctx.scene.leave(); ctx.wizard.state.error = null; await ctx.deleteMessage(); @@ -292,13 +292,13 @@ const createOrderHandlers = { return true; }, - async sats(ctx) { + async sats(ctx: CommunityContext) { if (ctx.callbackQuery) { ctx.wizard.state.sats = 0; await ctx.wizard.state.updateUI(); return true; } - const input = ctx.message.text; + const input = Number(ctx.message?.text); await ctx.deleteMessage(); if (isNaN(input)) { ctx.wizard.state.error = ctx.i18n.t('not_number'); @@ -310,7 +310,7 @@ const createOrderHandlers = { await ctx.wizard.state.updateUI(); return; } - ctx.wizard.state.sats = parseInt(input); + ctx.wizard.state.sats = Math.floor(input); await ctx.wizard.state.updateUI(); return true; }, diff --git a/bot/modules/orders/takeOrder.js b/bot/modules/orders/takeOrder.ts similarity index 72% rename from bot/modules/orders/takeOrder.js rename to bot/modules/orders/takeOrder.ts index 03a175f3..0939e23f 100644 --- a/bot/modules/orders/takeOrder.js +++ b/bot/modules/orders/takeOrder.ts @@ -1,28 +1,23 @@ // @ts-check -const { logger } = require('../../../logger'); -const { Order } = require('../../../models'); -const { deleteOrderFromChannel } = require('../../../util'); -const messages = require('../../messages'); -const { - validateUserWaitingOrder, - isBannedFromCommunity, - validateTakeSellOrder, - validateSeller, - validateObjectId, - validateTakeBuyOrder, -} = require('../../validations'); +import { Telegraf } from 'telegraf'; +import { logger } from '../../../logger'; +import { Order } from '../../../models'; +import { deleteOrderFromChannel } from '../../../util'; +import * as messages from '../../messages'; +import { HasTelegram, MainContext } from '../../start'; +import { validateUserWaitingOrder, isBannedFromCommunity, validateTakeSellOrder, validateSeller, validateObjectId, validateTakeBuyOrder } from '../../validations'; const OrderEvents = require('../../modules/events/orders'); -exports.takeOrderActionValidation = async (ctx, next) => { +export const takeOrderActionValidation = async (ctx: MainContext, next: () => void) => { try { - const text = ctx.update.callback_query.message.text; + const text = (ctx.update as any).callback_query.message.text; if (!text) return; next(); } catch (err) { logger.error(err); } }; -exports.takeOrderValidation = async (ctx, next) => { +export const takeOrderValidation = async (ctx: MainContext, next: () => void) => { try { const { user } = ctx; if (!(await validateUserWaitingOrder(ctx, ctx, user))) return; @@ -31,7 +26,7 @@ exports.takeOrderValidation = async (ctx, next) => { logger.error(err); } }; -exports.takebuyValidation = async (ctx, next) => { +export const takebuyValidation = async (ctx: MainContext, next: () => void) => { try { // Sellers with orders in status = FIAT_SENT, have to solve the order const isOnFiatSentStatus = await validateSeller(ctx, ctx.user); @@ -41,7 +36,7 @@ exports.takebuyValidation = async (ctx, next) => { logger.error(err); } }; -exports.takebuy = async (ctx, bot, orderId) => { +export const takebuy = async (ctx: MainContext, bot: HasTelegram, orderId: string) => { try { if (!orderId) return; const { user } = ctx; @@ -68,7 +63,7 @@ exports.takebuy = async (ctx, bot, orderId) => { logger.error(error); } }; -exports.takesell = async (ctx, bot, orderId) => { +export const takesell = async (ctx: MainContext, bot: HasTelegram, orderId: string) => { try { const { user } = ctx; if (!orderId) return; diff --git a/bot/ordersActions.ts b/bot/ordersActions.ts index 29e84247..48761db3 100644 --- a/bot/ordersActions.ts +++ b/bot/ordersActions.ts @@ -13,15 +13,15 @@ const OrderEvents = require('./modules/events/orders'); interface CreateOrderArguments { type: string; - amount: string; + amount: number; fiatAmount: number[]; fiatCode: string; paymentMethod: string; status: string; priceMargin: any; // ? - range_parent_id: string; - tgChatId: string; - tgOrderMessage: string; + range_parent_id?: string; + tgChatId?: string; + tgOrderMessage?: string; community_id: string; } @@ -62,7 +62,7 @@ const createOrder = async ( }: CreateOrderArguments ) => { try { - const amountAsNumber = parseInt(amount); + amount = Math.floor(amount); let isPublic = true; if (community_id) { const community = await Community.findById(community_id); @@ -70,7 +70,7 @@ const createOrder = async ( throw new Error("community is null"); isPublic = community.public; } - const fee = await getFee(amountAsNumber, community_id); + const fee = await getFee(amount, community_id); if(process.env.MAX_FEE === undefined) throw new Error("Environment variable MAX_FEE is not defined"); if(process.env.FEE_PERCENT === undefined) @@ -82,7 +82,7 @@ const createOrder = async ( const currency = getCurrency(fiatCode); if(currency == null) throw new Error("currency is null"); - const priceFromAPI = !amountAsNumber; + const priceFromAPI = !amount; if (priceFromAPI && !currency.price) { await messages.notRateForCurrency(bot, user, i18n); @@ -93,7 +93,7 @@ const createOrder = async ( const baseOrderData = { ...fiatAmountData, - amountAsNumber, + amount, fee, bot_fee: botFee, community_fee: communityFee, @@ -109,7 +109,7 @@ const createOrder = async ( description: buildDescription(i18n, { user, type, - amount: amountAsNumber, + amount, fiatAmount, fiatCode, paymentMethod, @@ -265,7 +265,7 @@ const getOrder = async (ctx: MainContext, user: UserDocument, orderId: string) = } }; -const getOrders = async (user: UserDocument, status: string) => { +const getOrders = async (user: UserDocument, status?: string) => { try { const where: any = { $and: [ From f6d23115266491f620d14483701f00bed123cb89 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Mon, 12 Aug 2024 11:57:17 +0200 Subject: [PATCH 11/22] bot, jobs: convert requires to imports Converted requires to imports where applicable and fixed type errors resulting from it. --- app.ts | 7 +- bot/commands.ts | 27 ++-- bot/index.ts | 20 +-- bot/messages.ts | 75 ++++++----- bot/middleware/stage.ts | 8 +- bot/middleware/user.ts | 8 +- bot/modules/community/index.ts | 2 +- .../community/scenes.communityAdmin.ts | 2 +- bot/modules/dispute/actions.ts | 2 +- bot/modules/dispute/commands.ts | 3 +- bot/modules/dispute/index.ts | 6 +- bot/modules/language/index.ts | 5 +- bot/modules/nostr/index.ts | 4 +- bot/modules/orders/index.ts | 1 + bot/modules/orders/scenes.ts | 4 +- bot/modules/orders/takeOrder.ts | 2 +- bot/modules/user/index.ts | 2 +- bot/ordersActions.ts | 2 +- bot/scenes.ts | 2 +- bot/start.ts | 121 +++++++++--------- jobs/cancel_orders.ts | 13 +- jobs/communities.ts | 4 +- jobs/delete_published_orders.ts | 9 +- jobs/node_info.ts | 6 +- jobs/pending_payments.ts | 7 +- 25 files changed, 174 insertions(+), 168 deletions(-) diff --git a/app.ts b/app.ts index 4855e257..37d2527d 100644 --- a/app.ts +++ b/app.ts @@ -1,11 +1,12 @@ import "dotenv/config"; import { SocksProxyAgent } from "socks-proxy-agent"; -import { MainContext, start } from "./bot/start"; +import { start } from "./bot/start"; import { connect as mongoConnect } from './db_connect' const { resubscribeInvoices } = require('./ln'); import { logger } from "./logger"; import { Telegraf } from "telegraf"; -const { delay } = require('./util'); +import { delay } from './util'; +import { CommunityContext } from "./bot/modules/community/communityContext"; (async () => { process.on('unhandledRejection', e => { @@ -24,7 +25,7 @@ const { delay } = require('./util'); mongoose.connection .once('open', async () => { logger.info('Connected to Mongo instance.'); - let options: Partial> = { handlerTimeout: 60000 }; + let options: Partial> = { handlerTimeout: 60000 }; if (process.env.SOCKS_PROXY_HOST) { const agent = new SocksProxyAgent(process.env.SOCKS_PROXY_HOST); options = { diff --git a/bot/commands.ts b/bot/commands.ts index 912be936..cce4fd61 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -7,21 +7,20 @@ const { } = require('../ln'); import { Order, User, Dispute } from '../models'; import * as messages from './messages'; -import { getBtcFiatPrice, deleteOrderFromChannel, getUserI18nContext, getFee } from '../util'; +import { getBtcFiatPrice, deleteOrderFromChannel, getUserI18nContext, getFee, removeLightningPrefix } from '../util'; import * as ordersActions from './ordersActions'; -const OrderEvents = require('./modules/events/orders'); -const { removeLightningPrefix } = require('../util'); +import * as OrderEvents from './modules/events/orders'; import { resolvLightningAddress } from '../lnurl/lnurl-pay'; import { logger } from '../logger'; import { Telegraf } from 'telegraf'; import { IOrder } from '../models/order'; import { UserDocument } from '../models/user'; -import { MainContext } from './start'; +import { HasTelegram, MainContext } from './start'; import { CommunityContext } from './modules/community/communityContext'; import { Types } from 'mongoose'; -const waitPayment = async (ctx: MainContext, bot: MainContext, buyer: UserDocument, seller: UserDocument, order: IOrder, buyerInvoice: any) => { +const waitPayment = async (ctx: MainContext, bot: HasTelegram, buyer: UserDocument, seller: UserDocument, order: IOrder, buyerInvoice: any) => { try { // If there is not fiat amount the function don't do anything if (order.fiat_amount === undefined) { @@ -88,7 +87,7 @@ const waitPayment = async (ctx: MainContext, bot: MainContext, buyer: UserDocume } }; -const addInvoice = async (ctx: CommunityContext, bot: MainContext, order: IOrder | null) => { +const addInvoice = async (ctx: CommunityContext, bot: HasTelegram, order: IOrder | null = null) => { try { ctx.deleteMessage(); ctx.scene.leave(); @@ -177,7 +176,7 @@ const addInvoice = async (ctx: CommunityContext, bot: MainContext, order: IOrder } }; -const rateUser = async (ctx: CommunityContext, bot: MainContext, rating: number, orderId: string) => { +const rateUser = async (ctx: CommunityContext, bot: HasTelegram, rating: number, orderId: string) => { try { ctx.deleteMessage(); ctx.scene.leave(); @@ -240,7 +239,7 @@ const saveUserReview = async (targetUser: UserDocument, rating: number) => { } }; -const cancelAddInvoice = async (ctx: CommunityContext, order: IOrder | null, job?: any) => { +const cancelAddInvoice = async (ctx: CommunityContext, order: IOrder | null = null, job?: any) => { try { let userAction = false; let userTgId = null; @@ -357,7 +356,7 @@ const cancelAddInvoice = async (ctx: CommunityContext, order: IOrder | null, job } }; -const showHoldInvoice = async (ctx: CommunityContext, bot: MainContext, order: IOrder | null) => { +const showHoldInvoice = async (ctx: CommunityContext, bot: HasTelegram, order: IOrder | null = null) => { try { ctx.deleteMessage(); if (!order) { @@ -426,7 +425,7 @@ const showHoldInvoice = async (ctx: CommunityContext, bot: MainContext, order: I } }; -const cancelShowHoldInvoice = async (ctx: CommunityContext, order: IOrder | null, job?: any) => { +const cancelShowHoldInvoice = async (ctx: CommunityContext, order: IOrder | null = null, job?: any) => { try { let userAction = false; let userTgId = null; @@ -549,7 +548,7 @@ const cancelShowHoldInvoice = async (ctx: CommunityContext, order: IOrder | null * @param {*} order * @returns */ -const addInvoicePHI = async (ctx: CommunityContext, bot: MainContext, orderId: string) => { +const addInvoicePHI = async (ctx: CommunityContext, bot: HasTelegram, orderId: string) => { try { ctx.deleteMessage(); const order = await Order.findOne({ _id: orderId }); @@ -573,7 +572,7 @@ const addInvoicePHI = async (ctx: CommunityContext, bot: MainContext, orderId: s } }; -const cancelOrder = async (ctx: CommunityContext, orderId: string, user: UserDocument | null) => { +const cancelOrder = async (ctx: CommunityContext, orderId: string, user: UserDocument | null = null) => { try { if (user === null) { const tgUser = (ctx.update as any).callback_query.from; @@ -699,7 +698,7 @@ const cancelOrder = async (ctx: CommunityContext, orderId: string, user: UserDoc } }; -const fiatSent = async (ctx: MainContext, orderId: string, user: UserDocument | null) => { +const fiatSent = async (ctx: MainContext, orderId: string, user: UserDocument | null = null) => { try { if (!user) { const tgUser = (ctx.update as any).callback_query.from; @@ -735,7 +734,7 @@ const fiatSent = async (ctx: MainContext, orderId: string, user: UserDocument | } }; -const release = async (ctx: MainContext, orderId: string, user: UserDocument | null) => { +const release = async (ctx: MainContext, orderId: string, user: UserDocument | null = null) => { try { if (!user) { const tgUser = (ctx.update as any).callback_query.from; diff --git a/bot/index.ts b/bot/index.ts index b4d84c05..ac8a9a7d 100644 --- a/bot/index.ts +++ b/bot/index.ts @@ -1,10 +1,6 @@ -const { initialize, start } = require('./start'); -const { - createOrder, - getOrder, - getNewRangeOrderPayload, -} = require('./ordersActions'); -const { +import { initialize, start } from './start'; +import { createOrder, getOrder, getNewRangeOrderPayload } from './ordersActions'; +import { validateSellOrder, validateBuyOrder, validateUser, @@ -13,8 +9,8 @@ const { validateTakeBuyOrder, validateReleaseOrder, validateDisputeOrder, -} = require('./validations'); -const { +} from './validations'; +import { startMessage, initBotErrorMessage, invoicePaymentRequestMessage, @@ -36,12 +32,11 @@ const { onGoingTakeBuyMessage, pendingSellMessage, pendingBuyMessage, - beginDisputeMessage, notOrderMessage, customMessage, -} = require('./messages'); +} from './messages'; -module.exports = { +export { initialize, start, createOrder, @@ -75,7 +70,6 @@ module.exports = { onGoingTakeBuyMessage, pendingSellMessage, pendingBuyMessage, - beginDisputeMessage, notOrderMessage, customMessage, getNewRangeOrderPayload, diff --git a/bot/messages.ts b/bot/messages.ts index 83b7e2e4..20cb1a02 100644 --- a/bot/messages.ts +++ b/bot/messages.ts @@ -1,6 +1,6 @@ import { TelegramError } from 'telegraf' import QR from 'qrcode'; -const { +import { getCurrency, numberFormat, getDetailedOrder, @@ -12,8 +12,8 @@ const { decimalRound, getUserAge, getStars, -} = require('../util'); -const OrderEvents = require('./modules/events/orders'); +} from '../util'; +import * as OrderEvents from './modules/events/orders'; import { logger } from "../logger"; import { HasTelegram, MainContext } from './start'; import { UserDocument } from '../models/user' @@ -24,6 +24,7 @@ import { IConfig } from '../models/config'; import { IPendingPayment } from '../models/pending_payment'; import { PayViaPaymentRequestResult } from 'lightning'; import { IFiat } from '../util/fiatModel'; +import { CommunityContext } from './modules/community/communityContext'; const startMessage = async (ctx: MainContext) => { try { @@ -65,10 +66,10 @@ const invoicePaymentRequestMessage = async ( buyer: UserDocument ) => { try { - let currency = getCurrency(order.fiat_code); - currency = - !!currency && !!currency.symbol_native - ? currency.symbol_native + const currencyObject = getCurrency(order.fiat_code); + const currency = + !!currencyObject && !!currencyObject.symbol_native + ? currencyObject.symbol_native : order.fiat_code; const expirationTime = Number(process.env.HOLD_INVOICE_EXPIRATION_WINDOW) / 60; @@ -195,7 +196,7 @@ const expiredInvoiceMessage = async (ctx: MainContext) => { } }; -const expiredInvoiceOnPendingMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { +const expiredInvoiceOnPendingMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { try { await bot.telegram.sendMessage(user.tg_id, i18n.t('invoice_expired_long')); await bot.telegram.sendMessage( @@ -298,7 +299,7 @@ const alreadyTakenOrderMessage = async (ctx: MainContext, bot: HasTelegram, user } }; -const invalidDataMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { +const invalidDataMessage = async (ctx: MainContext, bot: HasTelegram, user: UserDocument) => { try { await bot.telegram.sendMessage(user.tg_id, ctx.i18n.t('invalid_data')); } catch (error) { @@ -306,7 +307,7 @@ const invalidDataMessage = async (ctx: MainContext, bot: MainContext, user: User } }; -const genericErrorMessage = async (bot: MainContext, user: UserDocument, i18n: I18nContext) => { +const genericErrorMessage = async (bot: HasTelegram, user: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage(user.tg_id, i18n.t('generic_error')); } catch (error) { @@ -351,15 +352,15 @@ const showHoldInvoiceMessage = async ( fiatAmount: IOrder["fiat_amount"] ) => { try { - let currency = getCurrency(fiatCode); - currency = - !!currency && !!currency.symbol_native - ? currency.symbol_native + const currencyObject = getCurrency(fiatCode); + const currency = + !!currencyObject && !!currencyObject.symbol_native + ? currencyObject.symbol_native : fiatCode; await ctx.reply( ctx.i18n.t('pay_invoice', { amount: numberFormat(fiatCode, amount), - fiatAmount: numberFormat(fiatCode, fiatAmount), + fiatAmount: numberFormat(fiatCode, fiatAmount!), currency, }) ); @@ -471,7 +472,7 @@ const onGoingTakeSellMessage = async ( orderId: order.id, currency: order.fiat_code, sellerUsername: sellerUser.username, - fiatAmount: numberFormat(order.fiat_code, order.fiat_amount), + fiatAmount: numberFormat(order.fiat_code, order.fiat_amount!), paymentMethod: order.payment_method, }) ); @@ -497,7 +498,7 @@ const onGoingTakeSellMessage = async ( const takeSellWaitingSellerToPayMessage = async ( ctx: MainContext, - bot: MainContext, + bot: HasTelegram, buyerUser: UserDocument, order: IOrder ) => { @@ -532,7 +533,7 @@ const releasedSatsMessage = async ( } }; -const rateUserMessage = async (bot: Telegraf, caller: UserDocument, order: IOrder, i18n: I18nContext) => { +const rateUserMessage = async (bot: Telegraf, caller: UserDocument, order: IOrder, i18n: I18nContext) => { try { const starButtons = []; for (let num = 5; num > 0; num--) { @@ -589,6 +590,8 @@ const publishBuyOrderMessage = async ( publishMessage += `:${order._id}:`; const channel = await getOrderChannel(order); + if (channel === undefined) + throw new Error("channel is undefined"); // We send the message to the channel const message1 = await bot.telegram.sendMessage(channel, publishMessage, { reply_markup: { @@ -624,6 +627,8 @@ const publishSellOrderMessage = async ( let publishMessage = `⚡️🍊⚡️\n${order.description}\n`; publishMessage += `:${order._id}:`; const channel = await getOrderChannel(order); + if (channel === undefined) + throw new Error("channel is undefined"); // We send the message to the channel const message1 = await ctx.telegram.sendMessage(channel, publishMessage, { reply_markup: { @@ -658,6 +663,8 @@ const customMessage = async (ctx: MainContext, message: string) => { const checkOrderMessage = async (ctx: MainContext, order: IOrder, buyer: UserDocument, seller: UserDocument) => { try { let message = getDetailedOrder(ctx.i18n, order, buyer, seller); + if (message === undefined) + throw new Error("message is undefined"); message += `\n\n`; await ctx.reply(message, { parse_mode: 'MarkdownV2' }); } catch (error) { @@ -830,7 +837,7 @@ const notValidIdMessage = async (ctx: MainContext) => { } }; -const addInvoiceMessage = async (ctx: MainContext, bot: MainContext, buyer: UserDocument, seller: UserDocument, order: IOrder) => { +const addInvoiceMessage = async (ctx: MainContext, bot: HasTelegram, buyer: UserDocument, seller: UserDocument, order: IOrder) => { try { await bot.telegram.sendMessage( buyer.tg_id, @@ -838,7 +845,7 @@ const addInvoiceMessage = async (ctx: MainContext, bot: MainContext, buyer: User orderId: order.id, currency: order.fiat_code, sellerUsername: seller.username, - fiatAmount: numberFormat(order.fiat_code, order.fiat_amount), + fiatAmount: numberFormat(order.fiat_code, order.fiat_amount!), paymentMethod: order.payment_method, }) ); @@ -852,7 +859,7 @@ const addInvoiceMessage = async (ctx: MainContext, bot: MainContext, buyer: User } }; -const sendBuyerInfo2SellerMessage = async (bot: MainContext, buyer: UserDocument, seller: UserDocument, order: IOrder, i18n: I18nContext) => { +const sendBuyerInfo2SellerMessage = async (bot: HasTelegram, buyer: UserDocument, seller: UserDocument, order: IOrder, i18n: I18nContext) => { try { await bot.telegram.sendMessage( seller.tg_id, @@ -860,7 +867,7 @@ const sendBuyerInfo2SellerMessage = async (bot: MainContext, buyer: UserDocument currency: order.fiat_code, orderId: order.id, buyerUsername: buyer.username, - fiatAmount: numberFormat(order.fiat_code, order.fiat_amount), + fiatAmount: numberFormat(order.fiat_code, order.fiat_amount!), paymentMethod: order.payment_method, }) ); @@ -998,7 +1005,7 @@ const successCancelAllOrdersMessage = async (ctx: MainContext) => { } }; -const successCancelOrderByAdminMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument, order: IOrder) => { +const successCancelOrderByAdminMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument, order: IOrder) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1017,7 +1024,7 @@ const successCompleteOrderMessage = async (ctx: MainContext, order: IOrder) => { } }; -const successCompleteOrderByAdminMessage = async (ctx: MainContext, bot: Telegraf, user: UserDocument, order: IOrder) => { +const successCompleteOrderByAdminMessage = async (ctx: MainContext, bot: HasTelegram, user: UserDocument, order: IOrder) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1182,7 +1189,7 @@ const listCurrenciesResponse = async (ctx: MainContext, currencies: Array } }; -const priceApiFailedMessage = async (ctx: MainContext, bot: MainContext, user: UserDocument) => { +const priceApiFailedMessage = async (ctx: MainContext, bot: HasTelegram, user: UserDocument) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1242,7 +1249,7 @@ const wizardAddInvoiceInitMessage = async ( expirationTime, satsAmount: numberFormat(order.fiat_code, order.amount), currency, - fiatAmount: numberFormat(order.fiat_code, order.fiat_amount), + fiatAmount: numberFormat(order.fiat_code, order.fiat_amount!), }) ); } catch (error) { @@ -1302,7 +1309,7 @@ const wizardAddFiatAmountMessage = async (ctx: MainContext, currency: string, ac ctx.i18n.t('wizard_add_fiat_amount', { action, currency, - fiatAmount: numberFormat(order.fiat_code, order.fiat_amount), + fiatAmount: numberFormat(order.fiat_code, order.fiat_amount!), minAmount: numberFormat(order.fiat_code, order.min_amount), maxAmount: numberFormat(order.fiat_code, order.max_amount), }) @@ -1339,7 +1346,7 @@ const wizardAddFiatAmountCorrectMessage = async (ctx: MainContext, currency: IFi } }; -const expiredOrderMessage = async (bot: Telegraf, order: IOrder, buyerUser: UserDocument, sellerUser: UserDocument, i18n: I18nContext) => { +const expiredOrderMessage = async (bot: HasTelegram, order: IOrder, buyerUser: UserDocument, sellerUser: UserDocument, i18n: I18nContext) => { try { const detailedOrder = getDetailedOrder(i18n, order, buyerUser, sellerUser); await bot.telegram.sendMessage( @@ -1356,7 +1363,7 @@ const expiredOrderMessage = async (bot: Telegraf, order: IOrder, bu } }; -const toBuyerExpiredOrderMessage = async (bot: Telegraf, user: UserDocument, i18n: I18nContext) => { +const toBuyerExpiredOrderMessage = async (bot: HasTelegram, user: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1367,7 +1374,7 @@ const toBuyerExpiredOrderMessage = async (bot: Telegraf, user: User } }; -const toSellerExpiredOrderMessage = async (bot: Telegraf, user: UserDocument, i18n: I18nContext) => { +const toSellerExpiredOrderMessage = async (bot: HasTelegram, user: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1461,7 +1468,7 @@ const toAdminChannelSellerDidntPayInvoiceMessage = async ( }; const toAdminChannelPendingPaymentSuccessMessage = async ( - bot: Telegraf, + bot: Telegraf, user: UserDocument, order: IOrder, pending: IPendingPayment, @@ -1485,7 +1492,7 @@ const toAdminChannelPendingPaymentSuccessMessage = async ( }; const toBuyerPendingPaymentSuccessMessage = async ( - bot: Telegraf, + bot: Telegraf, user: UserDocument, order: IOrder, payment: PayViaPaymentRequestResult, @@ -1505,7 +1512,7 @@ const toBuyerPendingPaymentSuccessMessage = async ( } }; -const toBuyerPendingPaymentFailedMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { +const toBuyerPendingPaymentFailedMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { try { const attempts = process.env.PAYMENT_ATTEMPTS; await bot.telegram.sendMessage( @@ -1532,7 +1539,7 @@ const toBuyerPendingPaymentFailedMessage = async (bot: Telegraf, us }; const toAdminChannelPendingPaymentFailedMessage = async ( - bot: Telegraf, + bot: Telegraf, user: UserDocument, order: IOrder, pending: IPendingPayment, diff --git a/bot/middleware/stage.ts b/bot/middleware/stage.ts index a1f90ce0..3a3baf16 100644 --- a/bot/middleware/stage.ts +++ b/bot/middleware/stage.ts @@ -1,14 +1,10 @@ // @ts-check import { Scenes } from 'telegraf'; import * as CommunityModule from '../modules/community'; -const OrdersModule = require('../modules/orders'); +import * as OrdersModule from '../modules/orders'; import * as UserModule from '../modules/user'; import { CommunityContext } from '../modules/community/communityContext'; -const { - addInvoiceWizard, - addFiatAmountWizard, - addInvoicePHIWizard, -} = require('../scenes'); +import { addInvoiceWizard, addFiatAmountWizard, addInvoicePHIWizard } from '../scenes'; export const stageMiddleware = () => { const scenes = [ diff --git a/bot/middleware/user.ts b/bot/middleware/user.ts index 03ce55ac..15ff3265 100644 --- a/bot/middleware/user.ts +++ b/bot/middleware/user.ts @@ -1,8 +1,8 @@ -import { MainContext } from "../start"; +import { CommunityContext } from "../modules/community/communityContext"; import { validateUser, validateAdmin, validateSuperAdmin } from '../validations'; -export const userMiddleware = async (ctx: MainContext, next: () => void) => { +export const userMiddleware = async (ctx: CommunityContext, next: () => void) => { const user = await validateUser(ctx, false); if (!user) return false; ctx.i18n.locale(user.lang); @@ -11,7 +11,7 @@ export const userMiddleware = async (ctx: MainContext, next: () => void) => { next(); }; -export const adminMiddleware = async (ctx: MainContext, next: () => void) => { +export const adminMiddleware = async (ctx: CommunityContext, next: () => void) => { const admin = await validateAdmin(ctx); if (!admin) return false; ctx.i18n.locale(admin.lang); @@ -20,7 +20,7 @@ export const adminMiddleware = async (ctx: MainContext, next: () => void) => { next(); }; -export const superAdminMiddleware = async (ctx: MainContext, next: () => void) => { +export const superAdminMiddleware = async (ctx: CommunityContext, next: () => void) => { const admin = await validateSuperAdmin(ctx); if (!admin) return false; ctx.i18n.locale(admin.lang); diff --git a/bot/modules/community/index.ts b/bot/modules/community/index.ts index 048faf82..94ddd861 100644 --- a/bot/modules/community/index.ts +++ b/bot/modules/community/index.ts @@ -1,6 +1,6 @@ // @ts-check import { Telegraf } from 'telegraf'; -const { userMiddleware } = require('../../middleware/user'); +import { userMiddleware } from '../../middleware/user'; import * as actions from './actions'; import * as commands from './commands'; import { earningsMessage, updateCommunityMessage, sureMessage } from './messages'; diff --git a/bot/modules/community/scenes.communityAdmin.ts b/bot/modules/community/scenes.communityAdmin.ts index 02c65ec2..c13e22ce 100644 --- a/bot/modules/community/scenes.communityAdmin.ts +++ b/bot/modules/community/scenes.communityAdmin.ts @@ -1,7 +1,7 @@ import { Scenes } from 'telegraf'; import { CommunityContext } from './communityContext'; -const CommunityEvents = require('../events/community'); +import * as CommunityEvents from '../events/community'; const communityAdmin = () => { const scene = new Scenes.WizardScene('COMMUNITY_ADMIN', async (ctx: CommunityContext) => { diff --git a/bot/modules/dispute/actions.ts b/bot/modules/dispute/actions.ts index 48e8d57b..59f68c4a 100644 --- a/bot/modules/dispute/actions.ts +++ b/bot/modules/dispute/actions.ts @@ -2,7 +2,7 @@ import { User, Order, Dispute } from '../../../models'; import { MainContext } from '../../start'; import * as messages from './messages'; import { validateAdmin } from '../../validations'; -const globalMessages = require('../../messages'); +import * as globalMessages from '../../messages'; export const takeDispute = async (ctx: MainContext) : Promise => { const tgId: string = (ctx.update as any).callback_query.from.id; diff --git a/bot/modules/dispute/commands.ts b/bot/modules/dispute/commands.ts index df3d7690..dd7ce41e 100644 --- a/bot/modules/dispute/commands.ts +++ b/bot/modules/dispute/commands.ts @@ -3,8 +3,9 @@ import { MainContext } from "../../start"; import { User, Dispute, Order } from '../../../models'; import { validateParams, validateObjectId, validateDisputeOrder } from '../../validations'; import * as messages from './messages'; -const globalMessages = require('../../messages'); +import * as globalMessages from '../../messages'; import { logger } from '../../../logger'; +import * as OrderEvents from '../../modules/events/orders'; import { removeAtSymbol } from '../../../util'; const dispute = async (ctx: MainContext) => { diff --git a/bot/modules/dispute/index.ts b/bot/modules/dispute/index.ts index f1a1ea1b..e181110b 100644 --- a/bot/modules/dispute/index.ts +++ b/bot/modules/dispute/index.ts @@ -1,10 +1,10 @@ import * as commands from './commands'; import * as actions from './actions'; import { Telegraf } from 'telegraf'; -import { MainContext } from '../../start'; -const { userMiddleware, adminMiddleware } = require('../../middleware/user'); +import { userMiddleware, adminMiddleware } from '../../middleware/user'; +import { CommunityContext } from '../community/communityContext'; -export const configure = (bot: Telegraf) => { +export const configure = (bot: Telegraf) => { bot.command('dispute', userMiddleware, commands.dispute); bot.command('deldispute', adminMiddleware, commands.deleteDispute); bot.action( diff --git a/bot/modules/language/index.ts b/bot/modules/language/index.ts index 4c4292fe..b9de4924 100644 --- a/bot/modules/language/index.ts +++ b/bot/modules/language/index.ts @@ -1,10 +1,11 @@ -const { userMiddleware } = require('../../middleware/user'); +import { userMiddleware } from '../../middleware/user'; import * as commands from './commands'; import * as actions from './actions'; import { Telegraf } from 'telegraf'; import { MainContext } from '../../start'; +import { CommunityContext } from '../community/communityContext'; -exports.configure = (bot: Telegraf) => { +export const configure = (bot: Telegraf) => { bot.command('setlang', userMiddleware, ctx => commands.setlang(ctx as unknown as MainContext)); bot.action(/^setLanguage_([a-z]{2})$/, userMiddleware, ctx => actions.setLanguage(ctx as unknown as MainContext)); }; diff --git a/bot/modules/nostr/index.ts b/bot/modules/nostr/index.ts index 1c786df0..30641f29 100644 --- a/bot/modules/nostr/index.ts +++ b/bot/modules/nostr/index.ts @@ -4,10 +4,10 @@ import * as Config from './config'; import { createOrderEvent } from './events'; import * as Commands from './commands'; import { Telegraf } from 'telegraf'; -import { MainContext } from '../../start'; import { IOrder } from '../../../models/order'; +import { CommunityContext } from '../community/communityContext'; -export const configure = (bot: Telegraf) => { +export const configure = (bot: Telegraf) => { bot.command('/nostr', Commands.info); if (!Config.getRelays().length) { diff --git a/bot/modules/orders/index.ts b/bot/modules/orders/index.ts index 65fd4c99..b825bd4c 100644 --- a/bot/modules/orders/index.ts +++ b/bot/modules/orders/index.ts @@ -10,6 +10,7 @@ import { takeOrderActionValidation, takeOrderValidation, takesell, takebuyValida import { extractId } from '../../../util'; import { Telegraf } from 'telegraf'; import { CommunityContext } from '../community/communityContext'; +import { MainContext } from '../../start'; export * as Scenes from './scenes'; export const configure = (bot: Telegraf) => { diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 0da3b9f9..126ac58b 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -13,7 +13,7 @@ export const middleware = () => { return stage.middleware(); }; -const createOrder = (exports.createOrder = new Scenes.WizardScene( +export const createOrder = new Scenes.WizardScene( CREATE_ORDER, async (ctx: CommunityContext) => { try { @@ -100,7 +100,7 @@ const createOrder = (exports.createOrder = new Scenes.WizardScene( return ctx.scene.leave(); } } -)); +); const createOrderSteps = { async currency(ctx: CommunityContext) { diff --git a/bot/modules/orders/takeOrder.ts b/bot/modules/orders/takeOrder.ts index 0939e23f..ba8b8cce 100644 --- a/bot/modules/orders/takeOrder.ts +++ b/bot/modules/orders/takeOrder.ts @@ -6,7 +6,7 @@ import { deleteOrderFromChannel } from '../../../util'; import * as messages from '../../messages'; import { HasTelegram, MainContext } from '../../start'; import { validateUserWaitingOrder, isBannedFromCommunity, validateTakeSellOrder, validateSeller, validateObjectId, validateTakeBuyOrder } from '../../validations'; -const OrderEvents = require('../../modules/events/orders'); +import * as OrderEvents from '../../modules/events/orders'; export const takeOrderActionValidation = async (ctx: MainContext, next: () => void) => { try { diff --git a/bot/modules/user/index.ts b/bot/modules/user/index.ts index e6b412fc..0842196b 100644 --- a/bot/modules/user/index.ts +++ b/bot/modules/user/index.ts @@ -1,5 +1,5 @@ // @ts-check -const { userMiddleware } = require('../../middleware/user'); +import { userMiddleware } from '../../middleware/user'; import { Telegraf } from 'telegraf'; import Scenes from './scenes'; diff --git a/bot/ordersActions.ts b/bot/ordersActions.ts index 48761db3..410d6f2b 100644 --- a/bot/ordersActions.ts +++ b/bot/ordersActions.ts @@ -9,7 +9,7 @@ import { MainContext } from './start'; import { IOrder } from '../models/order'; import { IFiat } from '../util/fiatModel'; -const OrderEvents = require('./modules/events/orders'); +import * as OrderEvents from './modules/events/orders'; interface CreateOrderArguments { type: string; diff --git a/bot/scenes.ts b/bot/scenes.ts index c88a0f2e..477e4fd4 100644 --- a/bot/scenes.ts +++ b/bot/scenes.ts @@ -9,7 +9,7 @@ const { isPendingPayment } = require('../ln'); import { logger } from '../logger'; import { resolvLightningAddress } from '../lnurl/lnurl-pay'; import { CommunityContext } from './modules/community/communityContext'; -const OrderEvents = require('./modules/events/orders'); +import * as OrderEvents from './modules/events/orders'; interface InvoiceParseResult { invoice?: any; diff --git a/bot/start.ts b/bot/start.ts index 422c9676..4ccf6466 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -3,9 +3,9 @@ import { I18n, I18nContext } from '@grammyjs/i18n'; import { Message } from 'typegram' import { UserDocument } from '../models/user' import { FilterQuery } from 'mongoose'; -const OrderEvents = require('./modules/events/orders'); +import * as OrderEvents from './modules/events/orders'; import { limit } from "@grammyjs/ratelimiter" -const schedule = require('node-schedule'); +import schedule from 'node-schedule'; import { Order, User, @@ -15,21 +15,21 @@ import { Config, } from '../models'; import { getCurrenciesWithPrice, deleteOrderFromChannel, removeAtSymbol } from '../util'; -const { +import { commandArgsMiddleware, stageMiddleware, userMiddleware, adminMiddleware, superAdminMiddleware, -} = require('./middleware'); -const ordersActions = require('./ordersActions'); -const CommunityModule = require('./modules/community'); -const LanguageModule = require('./modules/language'); -const NostrModule = require('./modules/nostr'); -const OrdersModule = require('./modules/orders'); -const UserModule = require('./modules/user'); -const DisputeModule = require('./modules/dispute'); -const { +} from './middleware'; +import * as ordersActions from './ordersActions'; +import * as CommunityModule from './modules/community'; +import * as LanguageModule from './modules/language'; +import * as NostrModule from './modules/nostr'; +import * as OrdersModule from './modules/orders'; +import * as UserModule from './modules/user'; +import * as DisputeModule from './modules/dispute'; +import { rateUser, cancelAddInvoice, addInvoice, @@ -39,7 +39,7 @@ const { cancelOrder, fiatSent, release, -} = require('./commands'); +} from './commands'; const { settleHoldInvoice, cancelHoldInvoice, @@ -47,12 +47,12 @@ const { subscribeInvoice, getInvoice, } = require('../ln'); -const { +import { validateUser, validateParams, validateObjectId, validateLightningAddress, -} = require('./validations'); +} from './validations'; import * as messages from './messages'; import { attemptPendingPayments, @@ -65,6 +65,7 @@ import { } from '../jobs'; import { logger } from "../logger"; import { ICommunity, IUsernameId } from '../models/community'; +import { CommunityContext } from './modules/community/communityContext'; export interface MainContext extends Context { match: Array | null; @@ -153,14 +154,14 @@ https://github.com/telegraf/telegraf/issues/1319#issuecomment-766360594 */ export const ctxUpdateAssertMsg = "ctx.update.message.text is not available."; -const initialize = (botToken: string, options: Partial>): Telegraf => { +const initialize = (botToken: string, options: Partial>): Telegraf => { const i18n = new I18n({ defaultLanguageOnMissing: true, // implies allowMissing = true directory: 'locales', useSession: true, }); - const bot = new Telegraf(botToken, options); + const bot = new Telegraf(botToken, options); bot.catch(err => { logger.error(err); }); @@ -218,7 +219,7 @@ const initialize = (botToken: string, options: Partial => { try { - const [val] = await validateParams(ctx, 2, '\\<_on/off_\\>'); + const [val] = (await validateParams(ctx, 2, '\\<_on/off_\\>'))!; if (!val) return; let config = await Config.findOne(); if (config === null) { @@ -289,7 +290,7 @@ const initialize = (botToken: string, options: Partial { try { - const [orderId] = await validateParams(ctx, 2, '\\<_order id_\\>'); + const [orderId] = (await validateParams(ctx, 2, '\\<_order id_\\>'))!; if (!orderId) return; if (!(await validateObjectId(ctx, orderId))) return; @@ -324,7 +325,9 @@ const initialize = (botToken: string, options: Partial { try { - const [orderId] = await validateParams(ctx, 2, '\\<_order id_\\>'); + const validatedParams = (await validateParams(ctx, 2, '\\<_order id_\\>'))!; + if (validatedParams == null) return; + const [orderId] = validatedParams; if (!orderId) return; if (!(await validateObjectId(ctx, orderId))) return; @@ -391,7 +394,7 @@ const initialize = (botToken: string, options: Partial { + bot.command('cancel', userMiddleware, async (ctx: CommunityContext) => { try { if (!('message' in ctx.update) || !('text' in ctx.update.message)){ throw new Error(ctxUpdateAssertMsg); @@ -416,13 +419,13 @@ const initialize = (botToken: string, options: Partial { + bot.command('cancelall', userMiddleware, async (ctx: CommunityContext) => { try { - const pending_orders = await ordersActions.getOrders(ctx.user, 'PENDING'); - const seller_orders = await ordersActions.getOrders(ctx.user, 'WAITING_BUYER_INVOICE'); - const buyer_orders = await ordersActions.getOrders(ctx.user, 'WAITING_PAYMENT'); + const pending_orders = await ordersActions.getOrders(ctx.user, 'PENDING') || []; + const seller_orders = await ordersActions.getOrders(ctx.user, 'WAITING_BUYER_INVOICE') || []; + const buyer_orders = await ordersActions.getOrders(ctx.user, 'WAITING_PAYMENT') || []; - const orders = [...pending_orders, ...seller_orders, ...buyer_orders] + const orders = [...pending_orders, ...seller_orders, ...buyer_orders]; if (orders.length === 0) { return await messages.notOrdersMessage(ctx); @@ -452,7 +455,7 @@ const initialize = (botToken: string, options: Partial { + bot.command('settleorder', adminMiddleware, async (ctx: CommunityContext) => { try { - const [orderId] = await validateParams(ctx, 2, '\\<_order id_\\>'); + const [orderId] = (await validateParams(ctx, 2, '\\<_order id_\\>'))!; if (!orderId) return; if (!(await validateObjectId(ctx, orderId))) return; @@ -527,7 +530,7 @@ const initialize = (botToken: string, options: Partial { try { - const [orderId] = await validateParams(ctx, 2, '\\<_order id_\\>'); + const [orderId] = (await validateParams(ctx, 2, '\\<_order id_\\>'))!; if (!orderId) return; if (!(await validateObjectId(ctx, orderId))) return; const order = await Order.findOne({ _id: orderId }); @@ -545,7 +548,7 @@ const initialize = (botToken: string, options: Partial { try { - const [orderId] = await validateParams(ctx, 2, '\\<_order id_\\>'); + const [orderId] = (await validateParams(ctx, 2, '\\<_order id_\\>'))!; if (!orderId) return; if (!(await validateObjectId(ctx, orderId))) return; const order = await Order.findOne({ _id: orderId }); @@ -568,7 +571,7 @@ const initialize = (botToken: string, options: Partial { try { - const [hash] = await validateParams(ctx, 2, '\\<_hash_\\>'); + const [hash] = (await validateParams(ctx, 2, '\\<_hash_\\>'))!; if (!hash) return; @@ -632,11 +635,11 @@ const initialize = (botToken: string, options: Partial { try { - let [username] = await validateParams( + let [username] = (await validateParams( ctx, 2, '\\<_username or telegram ID_\\>' - ); + ))!; if (!username) return; @@ -676,11 +679,11 @@ const initialize = (botToken: string, options: Partial { try { - let [username] = await validateParams( + let [username] = (await validateParams( ctx, 2, '\\<_username or telegram ID_\\>' - ); + ))!; if (!username) return; @@ -719,11 +722,11 @@ const initialize = (botToken: string, options: Partial { try { - const [lightningAddress] = await validateParams( + const [lightningAddress] = (await validateParams( ctx, 2, '\\<_lightningAddress / off_\\>' - ); + ))!; if (!lightningAddress) return; if (lightningAddress === 'off') { @@ -758,37 +761,37 @@ const initialize = (botToken: string, options: Partial { + bot.action('addInvoiceBtn', userMiddleware, async (ctx: CommunityContext) => { await addInvoice(ctx, bot); }); - bot.action('cancelAddInvoiceBtn', userMiddleware, async (ctx: MainContext) => { + bot.action('cancelAddInvoiceBtn', userMiddleware, async (ctx: CommunityContext) => { await cancelAddInvoice(ctx); }); - bot.action('showHoldInvoiceBtn', userMiddleware, async (ctx: MainContext) => { + bot.action('showHoldInvoiceBtn', userMiddleware, async (ctx: CommunityContext) => { await showHoldInvoice(ctx, bot); }); - bot.action('cancelShowHoldInvoiceBtn', userMiddleware, async (ctx: MainContext) => { + bot.action('cancelShowHoldInvoiceBtn', userMiddleware, async (ctx: CommunityContext) => { await cancelShowHoldInvoice(ctx); }); - bot.action(/^showStarBtn\(([1-5]),(\w{24})\)$/, userMiddleware, async (ctx: MainContext) => { + bot.action(/^showStarBtn\(([1-5]),(\w{24})\)$/, userMiddleware, async (ctx: CommunityContext) => { if (ctx.match === null) { throw new Error("ctx.match should not be null"); } - await rateUser(ctx, bot, ctx.match[1], ctx.match[2]); + await rateUser(ctx, bot, Number(ctx.match[1]), ctx.match[2]); }); - bot.action(/^addInvoicePHIBtn_([0-9a-f]{24})$/, userMiddleware, async (ctx: MainContext) => { + bot.action(/^addInvoicePHIBtn_([0-9a-f]{24})$/, userMiddleware, async (ctx: CommunityContext) => { if (ctx.match === null) { throw new Error("ctx.match should not be null"); } await addInvoicePHI(ctx, bot, ctx.match[1]); }); - bot.action(/^setinvoice_([0-9a-f]{24})$/, userMiddleware, async (ctx: MainContext) => { + bot.action(/^setinvoice_([0-9a-f]{24})$/, userMiddleware, async (ctx: CommunityContext) => { if (ctx.match === null) { throw new Error("ctx.match should not be null"); } @@ -796,7 +799,7 @@ const initialize = (botToken: string, options: Partial { + bot.action(/^cancel_([0-9a-f]{24})$/, userMiddleware, async (ctx: CommunityContext) => { if (ctx.match === null) { throw new Error("ctx.match should not be null"); } @@ -804,7 +807,7 @@ const initialize = (botToken: string, options: Partial { + bot.action(/^fiatsent_([0-9a-f]{24})$/, userMiddleware, async (ctx: CommunityContext) => { if (ctx.match === null) { throw new Error("ctx.match should not be null"); } @@ -812,7 +815,7 @@ const initialize = (botToken: string, options: Partial { + bot.action(/^release_([0-9a-f]{24})$/, userMiddleware, async (ctx: CommunityContext) => { if (ctx.match === null) { throw new Error("ctx.match should not be null"); } @@ -822,7 +825,7 @@ const initialize = (botToken: string, options: Partial { try { - const [orderId] = await validateParams(ctx, 2, '\\<_order id_\\>'); + const [orderId] = (await validateParams(ctx, 2, '\\<_order id_\\>'))!; if (!orderId) return; if (!(await validateObjectId(ctx, orderId))) return; const order = await Order.findOne({ @@ -877,12 +880,12 @@ const initialize = (botToken: string, options: Partial { try { - let [show] = await validateParams(ctx, 2, '_yes/no_'); - if (!show) return; - show = show === 'yes'; + const [showString] = (await validateParams(ctx, 2, '_yes/no_'))!; + if (!showString) return; + const show = showString === 'yes'; ctx.user.show_username = show; await ctx.user.save(); - messages.updateUserSettingsMessage(ctx, 'showusername', show); + messages.updateUserSettingsMessage(ctx, 'showusername', String(show)); } catch (error) { logger.error(error); } @@ -890,12 +893,12 @@ const initialize = (botToken: string, options: Partial { try { - let [show] = await validateParams(ctx, 2, '_yes/no_'); - if (!show) return; - show = show === 'yes'; + const [showString] = (await validateParams(ctx, 2, '_yes/no_'))!; + if (!showString) return; + const show = showString === 'yes'; ctx.user.show_volume_traded = show; await ctx.user.save(); - messages.updateUserSettingsMessage(ctx, 'showvolume', show); + messages.updateUserSettingsMessage(ctx, 'showvolume', String(show)); } catch (error) { logger.error(error); } @@ -932,7 +935,7 @@ const initialize = (botToken: string, options: Partial>): Telegraf => { +const start = (botToken: string, options: Partial>): Telegraf => { const bot = initialize(botToken, options); bot.launch(); diff --git a/jobs/cancel_orders.ts b/jobs/cancel_orders.ts index 686b3dec..05e1887f 100644 --- a/jobs/cancel_orders.ts +++ b/jobs/cancel_orders.ts @@ -1,13 +1,14 @@ import { Telegraf } from "telegraf"; -import { MainContext } from "../bot/start"; +import { HasTelegram, MainContext } from "../bot/start"; import { User, Order } from "../models"; -const { cancelShowHoldInvoice, cancelAddInvoice } = require('../bot/commands'); +import { cancelShowHoldInvoice, cancelAddInvoice } from '../bot/commands'; import * as messages from "../bot/messages"; import { getUserI18nContext, holdInvoiceExpirationInSecs } from '../util'; import { logger } from "../logger"; -const OrderEvents = require('../bot/modules/events/orders'); +import { CommunityContext } from "../bot/modules/community/communityContext"; +import * as OrderEvents from '../bot/modules/events/orders'; -const cancelOrders = async (bot: Telegraf) => { +const cancelOrders = async (bot: HasTelegram) => { try { const holdInvoiceTime = new Date(); holdInvoiceTime.setSeconds( @@ -30,9 +31,9 @@ const cancelOrders = async (bot: Telegraf) => { }); for (const order of waitingPaymentOrders) { if (order.status === 'WAITING_PAYMENT') { - await cancelShowHoldInvoice(bot, order, true); + await cancelShowHoldInvoice(bot as CommunityContext, order, true); } else { - await cancelAddInvoice(bot, order, true); + await cancelAddInvoice(bot as CommunityContext, order, true); } } // We get the expired order where the seller sent the sats but never released the order diff --git a/jobs/communities.ts b/jobs/communities.ts index a3599506..1956617a 100644 --- a/jobs/communities.ts +++ b/jobs/communities.ts @@ -1,10 +1,10 @@ import { Telegraf } from "telegraf"; -import { MainContext } from "../bot/start"; import { Order, Community } from '../models'; import { logger } from "../logger"; +import { CommunityContext } from "../bot/modules/community/communityContext"; -const deleteCommunity = async (bot: Telegraf) => { +const deleteCommunity = async (bot: Telegraf) => { try { const communities = await Community.find(); for (const community of communities) { diff --git a/jobs/delete_published_orders.ts b/jobs/delete_published_orders.ts index 4182840d..935449d5 100644 --- a/jobs/delete_published_orders.ts +++ b/jobs/delete_published_orders.ts @@ -1,11 +1,12 @@ import { Telegraf } from "telegraf"; -import { MainContext } from "../bot/start"; import { Order } from '../models'; -const { deleteOrderFromChannel } = require('../util'); +import { deleteOrderFromChannel } from '../util'; import { logger } from '../logger'; +import { CommunityContext } from "../bot/modules/community/communityContext"; +import { IOrder } from "../models/order"; -const deleteOrders = async (bot: Telegraf) => { +const deleteOrders = async (bot: Telegraf) => { try { const windowTime = new Date(); windowTime.setSeconds( @@ -21,7 +22,7 @@ const deleteOrders = async (bot: Telegraf) => { logger.info( `Pending order Id: ${order._id} expired after ${process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW} seconds, deleting it from database and channel` ); - const orderCloned = order.toObject(); + const orderCloned = order.toObject() as IOrder; // We remove the order from the database first, then we remove the message from the channel await order.remove(); // We delete the messages related to that order from the channel diff --git a/jobs/node_info.ts b/jobs/node_info.ts index 22e82eb3..2fc20b05 100644 --- a/jobs/node_info.ts +++ b/jobs/node_info.ts @@ -1,11 +1,11 @@ import { Telegraf } from "telegraf"; -import { MainContext } from "../bot/start"; import { Config } from '../models'; +import { CommunityContext } from "../bot/modules/community/communityContext"; const { getInfo } = require('../ln'); -const { logger } = require('../logger'); +import { logger } from '../logger'; -const info = async (bot: Telegraf) => { +const info = async (bot: Telegraf) => { try { let config = await Config.findOne({}); if (config === null) { diff --git a/jobs/pending_payments.ts b/jobs/pending_payments.ts index b3b9a091..9f14cf35 100644 --- a/jobs/pending_payments.ts +++ b/jobs/pending_payments.ts @@ -6,9 +6,10 @@ 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'); +import { CommunityContext } from '../bot/modules/community/communityContext'; +import { orderUpdated } from '../bot/modules/events/orders'; -export const 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 }, @@ -118,7 +119,7 @@ export const attemptPendingPayments = async (bot: Telegraf): Promis } }; -export const attemptCommunitiesPendingPayments = async (bot: Telegraf): Promise => { +export const attemptCommunitiesPendingPayments = async (bot: Telegraf): Promise => { const pendingPayments = await PendingPayment.find({ paid: false, attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, From 78e87c44a82001422bf33ac6e5afb2622b849fc8 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 5 Dec 2024 13:20:38 +0100 Subject: [PATCH 12/22] tests/bot: convert requires to imports --- tests/bot/bot.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bot/bot.spec.ts b/tests/bot/bot.spec.ts index 146edb66..bebac146 100644 --- a/tests/bot/bot.spec.ts +++ b/tests/bot/bot.spec.ts @@ -4,7 +4,7 @@ const sinon = require('sinon'); const { expect } = require('chai'); const proxyquire = require('proxyquire'); -const { initialize } = require('../../bot'); +import { initialize } from '../../bot'; import { User, Order } from '../../models'; import { getCurrenciesWithPrice } from '../../util'; import { mockUpdatesResponseForCurrencies } from './mocks/currenciesResponse'; From 51d844773f9d3e87644ff49eb4be80edf7a12794 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Mon, 12 Aug 2024 13:47:25 +0200 Subject: [PATCH 13/22] ln,bot: convert ln to TS Convert ln module to TypeScript. --- app.ts | 2 +- bot/messages.ts | 24 +++++----- bot/ordersActions.ts | 4 +- ln/{connect.js => connect.ts} | 12 ++--- ln/{hold_invoice.js => hold_invoice.ts} | 26 +++++----- ln/index.js | 26 ---------- ln/index.ts | 26 ++++++++++ ln/info.js | 13 ----- ln/info.ts | 13 +++++ ln/{pay_request.js => pay_request.ts} | 48 +++++++++++++------ ...be_invoices.js => resubscribe_invoices.ts} | 22 +++++---- ...scribe_invoice.js => subscribe_invoice.ts} | 38 ++++++++++----- ln/{subscribe_probe.js => subscribe_probe.ts} | 10 ++-- 13 files changed, 149 insertions(+), 115 deletions(-) rename ln/{connect.js => connect.ts} (86%) rename ln/{hold_invoice.js => hold_invoice.ts} (54%) delete mode 100644 ln/index.js create mode 100644 ln/index.ts delete mode 100644 ln/info.js create mode 100644 ln/info.ts rename ln/{pay_request.js => pay_request.ts} (70%) rename ln/{resubscribe_invoices.js => resubscribe_invoices.ts} (59%) rename ln/{subscribe_invoice.js => subscribe_invoice.ts} (76%) rename ln/{subscribe_probe.js => subscribe_probe.ts} (66%) diff --git a/app.ts b/app.ts index 37d2527d..7ef381a9 100644 --- a/app.ts +++ b/app.ts @@ -2,7 +2,7 @@ import "dotenv/config"; import { SocksProxyAgent } from "socks-proxy-agent"; import { start } from "./bot/start"; import { connect as mongoConnect } from './db_connect' -const { resubscribeInvoices } = require('./ln'); +import { resubscribeInvoices } from './ln'; import { logger } from "./logger"; import { Telegraf } from "telegraf"; import { delay } from './util'; diff --git a/bot/messages.ts b/bot/messages.ts index 20cb1a02..6c9d67fd 100644 --- a/bot/messages.ts +++ b/bot/messages.ts @@ -104,7 +104,7 @@ const invoicePaymentRequestMessage = async ( } }; -const pendingSellMessage = async (ctx: MainContext, user: UserDocument, order: IOrder, channel: string, i18n: I18nContext) => { +const pendingSellMessage = async (ctx: HasTelegram, user: UserDocument, order: IOrder, channel: string, i18n: I18nContext) => { try { const orderExpirationWindow = Number(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW) / 60 / 60; @@ -125,7 +125,7 @@ const pendingSellMessage = async (ctx: MainContext, user: UserDocument, order: I } }; -const pendingBuyMessage = async (bot: MainContext, user: UserDocument, order: IOrder, channel: string, i18n: I18nContext) => { +const pendingBuyMessage = async (bot: HasTelegram, user: UserDocument, order: IOrder, channel: string, i18n: I18nContext) => { try { const orderExpirationWindow = Number(process.env.ORDER_PUBLISHED_EXPIRATION_WINDOW) / 60 / 60; @@ -196,7 +196,7 @@ const expiredInvoiceMessage = async (ctx: MainContext) => { } }; -const expiredInvoiceOnPendingMessage = async (bot: Telegraf, user: UserDocument, order: IOrder, i18n: I18nContext) => { +const expiredInvoiceOnPendingMessage = async (bot: HasTelegram, user: UserDocument, order: IOrder, i18n: I18nContext) => { try { await bot.telegram.sendMessage(user.tg_id, i18n.t('invoice_expired_long')); await bot.telegram.sendMessage( @@ -381,7 +381,7 @@ const showHoldInvoiceMessage = async ( }; const onGoingTakeBuyMessage = async ( - bot: MainContext, + bot: HasTelegram, seller: UserDocument, buyer: UserDocument, order: IOrder, @@ -458,7 +458,7 @@ const beginTakeSellMessage = async (ctx: MainContext, bot: HasTelegram, buyer: U }; const onGoingTakeSellMessage = async ( - bot: MainContext, + bot: HasTelegram, sellerUser: UserDocument, buyerUser: UserDocument, order: IOrder, @@ -513,7 +513,7 @@ const takeSellWaitingSellerToPayMessage = async ( }; const releasedSatsMessage = async ( - bot: MainContext, + bot: HasTelegram, sellerUser: UserDocument, buyerUser: UserDocument, i18nBuyer: I18nContext, @@ -533,7 +533,7 @@ const releasedSatsMessage = async ( } }; -const rateUserMessage = async (bot: Telegraf, caller: UserDocument, order: IOrder, i18n: I18nContext) => { +const rateUserMessage = async (bot: HasTelegram, caller: UserDocument, order: IOrder, i18n: I18nContext) => { try { const starButtons = []; for (let num = 5; num > 0; num--) { @@ -579,7 +579,7 @@ const notOrderMessage = async (ctx: MainContext) => { }; const publishBuyOrderMessage = async ( - bot: MainContext, + bot: HasTelegram, user: UserDocument, order: IOrder, i18n: I18nContext, @@ -617,7 +617,7 @@ const publishBuyOrderMessage = async ( }; const publishSellOrderMessage = async ( - ctx: MainContext, + ctx: HasTelegram, user: UserDocument, order: IOrder, i18n: I18nContext, @@ -907,7 +907,7 @@ const notOrdersMessage = async (ctx: MainContext) => { } }; -const notRateForCurrency = async (bot: MainContext, user: UserDocument, i18n: I18nContext) => { +const notRateForCurrency = async (bot: HasTelegram, user: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1099,7 +1099,7 @@ const counterPartyWantsCooperativeCancelMessage = async ( } }; -const invoicePaymentFailedMessage = async (bot: MainContext, user: UserDocument, i18n: I18nContext) => { +const invoicePaymentFailedMessage = async (bot: HasTelegram, user: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage( user.tg_id, @@ -1164,7 +1164,7 @@ const showInfoMessage = async (ctx: MainContext, user: UserDocument, config: ICo } }; -const buyerReceivedSatsMessage = async (bot: MainContext, buyerUser: UserDocument, sellerUser: UserDocument, i18n: I18nContext) => { +const buyerReceivedSatsMessage = async (bot: HasTelegram, buyerUser: UserDocument, sellerUser: UserDocument, i18n: I18nContext) => { try { await bot.telegram.sendMessage( buyerUser.tg_id, diff --git a/bot/ordersActions.ts b/bot/ordersActions.ts index 410d6f2b..b52b4454 100644 --- a/bot/ordersActions.ts +++ b/bot/ordersActions.ts @@ -5,7 +5,7 @@ import { getCurrency, numberFormat, getBtcExchangePrice, getFee, getUserAge, get import { logger } from '../logger'; import { I18nContext } from '@grammyjs/i18n'; import { UserDocument } from '../models/user'; -import { MainContext } from './start'; +import { HasTelegram, MainContext } from './start'; import { IOrder } from '../models/order'; import { IFiat } from '../util/fiatModel'; @@ -45,7 +45,7 @@ interface FiatAmountData { const createOrder = async ( i18n: I18nContext, - bot: MainContext, + bot: HasTelegram, user: UserDocument, { type, diff --git a/ln/connect.js b/ln/connect.ts similarity index 86% rename from ln/connect.js rename to ln/connect.ts index 473c814d..1cedb8ea 100644 --- a/ln/connect.js +++ b/ln/connect.ts @@ -1,9 +1,7 @@ -const fs = require('fs'); -const path = require('path'); -const lightning = require('lightning'); -const { logger } = require('../logger'); - -const { authenticatedLndGrpc } = lightning; +import fs from 'fs'; +import path from 'path'; +import { authenticatedLndGrpc } from 'lightning'; +import { logger } from '../logger'; // Trying to load TLS certificate and admin macaroon from environment variables first let cert = process.env.LND_CERT_BASE64; @@ -47,4 +45,4 @@ const { lnd } = authenticatedLndGrpc({ socket, }); -module.exports = lnd; +export default lnd; diff --git a/ln/hold_invoice.js b/ln/hold_invoice.ts similarity index 54% rename from ln/hold_invoice.js rename to ln/hold_invoice.ts index 493e3fef..1d870151 100644 --- a/ln/hold_invoice.js +++ b/ln/hold_invoice.ts @@ -1,26 +1,28 @@ -const { createHash, randomBytes } = require('crypto'); -const lightning = require('lightning'); -const lnd = require('./connect'); -const { logger } = require('../logger'); +import { BinaryLike, createHash, randomBytes } from 'crypto'; +import lightning from 'lightning'; +import lnd from './connect'; +import { logger } from '../logger'; -const createHoldInvoice = async ({ description, amount }) => { +const createHoldInvoice = async ({ description, amount }: { description: string, amount: number }) => { try { const randomSecret = () => randomBytes(32); - const sha256 = buffer => createHash('sha256').update(buffer).digest('hex'); + const sha256 = (buffer: Buffer) => createHash('sha256').update(buffer).digest('hex'); // We create a random secret const secret = randomSecret(); const expiresAt = new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + 3600); const hash = sha256(secret); - const cltv_delta = parseInt(process.env.HOLD_INVOICE_CLTV_DELTA); + const holdInvoiceCltvDelta = process.env.HOLD_INVOICE_CLTV_DELTA; + // sticking to semantics of JS code as requiring HOLD_INVOICE_CLTV_DELTA to be defined breaks tests + const cltv_delta = holdInvoiceCltvDelta === undefined ? NaN : parseInt(holdInvoiceCltvDelta); const { request, id } = await lightning.createHodlInvoice({ cltv_delta, lnd, description, id: hash, tokens: amount, - expires_at: expiresAt, + expires_at: expiresAt.toISOString(), }); // We sent back the response hash (id) to be used on testing @@ -30,7 +32,7 @@ const createHoldInvoice = async ({ description, amount }) => { } }; -const settleHoldInvoice = async ({ secret }) => { +const settleHoldInvoice = async ({ secret }: { secret: string }) => { try { await lightning.settleHodlInvoice({ lnd, secret }); } catch (error) { @@ -38,7 +40,7 @@ const settleHoldInvoice = async ({ secret }) => { } }; -const cancelHoldInvoice = async ({ hash }) => { +const cancelHoldInvoice = async ({ hash }: { hash: string }) => { try { await lightning.cancelHodlInvoice({ lnd, id: hash }); } catch (error) { @@ -46,7 +48,7 @@ const cancelHoldInvoice = async ({ hash }) => { } }; -const getInvoice = async ({ hash }) => { +const getInvoice = async ({ hash }: { hash: string }) => { try { return await lightning.getInvoice({ lnd, id: hash }); } catch (error) { @@ -54,7 +56,7 @@ const getInvoice = async ({ hash }) => { } }; -module.exports = { +export { createHoldInvoice, settleHoldInvoice, cancelHoldInvoice, diff --git a/ln/index.js b/ln/index.js deleted file mode 100644 index a50bcebd..00000000 --- a/ln/index.js +++ /dev/null @@ -1,26 +0,0 @@ -const { - createHoldInvoice, - settleHoldInvoice, - cancelHoldInvoice, - getInvoice, -} = require('./hold_invoice'); -const { subscribeInvoice, payHoldInvoice } = require('./subscribe_invoice'); -const subscribeProbe = require('./subscribe_probe'); -const resubscribeInvoices = require('./resubscribe_invoices'); -const { payRequest, payToBuyer, isPendingPayment } = require('./pay_request'); -const { getInfo } = require('./info'); - -module.exports = { - createHoldInvoice, - subscribeInvoice, - resubscribeInvoices, - settleHoldInvoice, - cancelHoldInvoice, - payRequest, - payToBuyer, - getInfo, - isPendingPayment, - subscribeProbe, - getInvoice, - payHoldInvoice, -}; diff --git a/ln/index.ts b/ln/index.ts new file mode 100644 index 00000000..92582fdf --- /dev/null +++ b/ln/index.ts @@ -0,0 +1,26 @@ +import { + createHoldInvoice, + settleHoldInvoice, + cancelHoldInvoice, + getInvoice, +} from './hold_invoice'; +import { subscribeInvoice, payHoldInvoice } from './subscribe_invoice'; +import { subscribeProbe } from './subscribe_probe'; +import { resubscribeInvoices } from './resubscribe_invoices'; +import { payRequest, payToBuyer, isPendingPayment } from './pay_request'; +import { getInfo } from './info'; + +export { + createHoldInvoice, + subscribeInvoice, + resubscribeInvoices, + settleHoldInvoice, + cancelHoldInvoice, + payRequest, + payToBuyer, + getInfo, + isPendingPayment, + subscribeProbe, + getInvoice, + payHoldInvoice, +}; diff --git a/ln/info.js b/ln/info.js deleted file mode 100644 index df2ebe5c..00000000 --- a/ln/info.js +++ /dev/null @@ -1,13 +0,0 @@ -const lightning = require('lightning'); -const lnd = require('./connect'); -const { logger } = require('../logger'); - -const getInfo = async () => { - try { - return await lightning.getWalletInfo({ lnd }); - } catch (error) { - logger.error(error); - } -}; - -module.exports = { getInfo }; diff --git a/ln/info.ts b/ln/info.ts new file mode 100644 index 00000000..db5d61fb --- /dev/null +++ b/ln/info.ts @@ -0,0 +1,13 @@ +import lightning from 'lightning'; +import lnd from './connect'; +import { logger } from '../logger'; + +const getInfo = async () => { + try { + return await lightning.getWalletInfo({ lnd }); + } catch (error) { + logger.error(error); + } +}; + +export { getInfo }; diff --git a/ln/pay_request.js b/ln/pay_request.ts similarity index 70% rename from ln/pay_request.js rename to ln/pay_request.ts index 3466c6b1..d070d517 100644 --- a/ln/pay_request.js +++ b/ln/pay_request.ts @@ -1,27 +1,41 @@ -const { +import { payViaPaymentRequest, getPayment, deleteForwardingReputations, -} = require('lightning'); + AuthenticatedLnd, +} from 'lightning'; const { parsePaymentRequest } = require('invoices'); -const { User, PendingPayment } = require('../models'); -const lnd = require('./connect'); -const { handleReputationItems, getUserI18nContext } = require('../util'); -const messages = require('../bot/messages'); -const { logger } = require('../logger'); -const OrderEvents = require('../bot/modules/events/orders'); +import { User, PendingPayment } from '../models'; +import lnd from './connect'; +import { handleReputationItems, getUserI18nContext } from '../util'; +import * as messages from '../bot/messages'; +import { logger } from '../logger'; +import * as OrderEvents from '../bot/modules/events/orders'; +import { IOrder } from '../models/order'; +import { HasTelegram } from '../bot/start'; -const payRequest = async ({ request, amount }) => { +interface PayViaPaymentRequestParams { + lnd: AuthenticatedLnd; + request: string; + pathfinding_timeout: number; + tokens?: number; + max_fee?: number; +} + +const payRequest = async ({ request, amount }: { request: string, amount: number }) => { try { const invoice = parsePaymentRequest({ request }); if (!invoice) return false; // If the invoice is expired we return is_expired = true if (invoice.is_expired) return invoice; + let maxRoutingFee = process.env.MAX_ROUTING_FEE; + if (maxRoutingFee === undefined) + throw new Error("Environment variable MAX_ROUTING_FEE is not defined"); // We need to set a max fee amount - const maxFee = amount * parseFloat(process.env.MAX_ROUTING_FEE); + const maxFee = amount * parseFloat(maxRoutingFee); - const params = { + const params : PayViaPaymentRequestParams = { lnd, request, pathfinding_timeout: 60000, @@ -45,7 +59,7 @@ const payRequest = async ({ request, amount }) => { } }; -const payToBuyer = async (bot, order) => { +const payToBuyer = async (bot: HasTelegram, order: IOrder) => { try { // We check if the payment is on flight we don't do anything const isPending = await isPendingPayment(order.buyer_invoice); @@ -57,6 +71,8 @@ const payToBuyer = async (bot, order) => { amount: order.amount, }); const buyerUser = await User.findOne({ _id: order.buyer_id }); + if (buyerUser === null) + throw new Error("buyerUser was not found"); // If the buyer's invoice is expired we let it know and don't try to pay again const i18nCtx = await getUserI18nContext(buyerUser); if (!!payment && payment.is_expired) { @@ -69,6 +85,8 @@ const payToBuyer = async (bot, order) => { return; } const sellerUser = await User.findOne({ _id: order.seller_id }); + if (sellerUser === null) + throw new Error("sellerUser was not found"); if (!!payment && !!payment.confirmed_at) { logger.info(`Order ${order._id} - Invoice with hash: ${payment.id} paid`); order.status = 'SUCCESS'; @@ -101,20 +119,20 @@ const payToBuyer = async (bot, order) => { } }; -const isPendingPayment = async request => { +const isPendingPayment = async (request: string) => { try { const { id } = parsePaymentRequest({ request }); const { is_pending } = await getPayment({ lnd, id }); return !!is_pending; - } catch (error) { + } catch (error: any) { const message = error.toString(); logger.error(`isPendingPayment catch error: ${message}`); return false; } }; -module.exports = { +export { payRequest, payToBuyer, isPendingPayment, diff --git a/ln/resubscribe_invoices.js b/ln/resubscribe_invoices.ts similarity index 59% rename from ln/resubscribe_invoices.js rename to ln/resubscribe_invoices.ts index f7b19e42..7829c855 100644 --- a/ln/resubscribe_invoices.js +++ b/ln/resubscribe_invoices.ts @@ -1,13 +1,15 @@ -const { getInvoices } = require('lightning'); -const lnd = require('./connect'); -const { subscribeInvoice } = require('./subscribe_invoice'); -const { Order } = require('../models'); -const { logger } = require('../logger'); +import { getInvoices, GetInvoicesResult } from 'lightning'; +import lnd from './connect'; +import { subscribeInvoice } from './subscribe_invoice'; +import { Order } from '../models'; +import { logger } from '../logger'; +import { CommunityContext } from '../bot/modules/community/communityContext'; +import { Telegraf } from 'telegraf'; -const resubscribeInvoices = async bot => { +const resubscribeInvoices = async (bot: Telegraf) => { try { let invoicesReSubscribed = 0; - const isHeld = invoice => !!invoice.is_held; + const unconfirmedInvoices = ( await getInvoices({ lnd, @@ -15,7 +17,7 @@ const resubscribeInvoices = async bot => { }) ).invoices; if (Array.isArray(unconfirmedInvoices) && unconfirmedInvoices.length > 0) { - const heldInvoices = unconfirmedInvoices.filter(isHeld); + const heldInvoices = unconfirmedInvoices.filter(invoice => !!invoice.is_held); for (const invoice of heldInvoices) { const orderInDB = await Order.findOne({ hash: invoice.id }); if (orderInDB) { @@ -28,10 +30,10 @@ const resubscribeInvoices = async bot => { } } logger.info(`Invoices resubscribed: ${invoicesReSubscribed}`); - } catch (error) { + } catch (error: any) { logger.error(`ResubscribeInvoice catch: ${error.toString()}`); return false; } }; -module.exports = resubscribeInvoices; +export { resubscribeInvoices }; diff --git a/ln/subscribe_invoice.js b/ln/subscribe_invoice.ts similarity index 76% rename from ln/subscribe_invoice.js rename to ln/subscribe_invoice.ts index 21302cec..932e65a9 100644 --- a/ln/subscribe_invoice.js +++ b/ln/subscribe_invoice.ts @@ -1,23 +1,31 @@ -const { subscribeToInvoice } = require('lightning'); -const { Order, User } = require('../models'); -const { payToBuyer } = require('./pay_request'); -const lnd = require('./connect'); -const messages = require('../bot/messages'); -const ordersActions = require('../bot/ordersActions'); -const { getUserI18nContext, getEmojiRate, decimalRound } = require('../util'); -const { logger } = require('../logger'); +import { subscribeToInvoice } from 'lightning'; +import { Order, User } from '../models'; +import { payToBuyer } from './pay_request'; +import lnd from './connect'; +import * as messages from '../bot/messages'; +import * as ordersActions from '../bot/ordersActions'; +import { getUserI18nContext, getEmojiRate, decimalRound } from '../util'; +import { logger } from '../logger'; +import { HasTelegram } from '../bot/start'; +import { IOrder } from '../models/order'; -const subscribeInvoice = async (bot, id, resub) => { +const subscribeInvoice = async (bot: HasTelegram, id: string, resub: boolean) => { try { const sub = subscribeToInvoice({ id, lnd }); sub.on('invoice_updated', async invoice => { if (invoice.is_held && !resub) { const order = await Order.findOne({ hash: invoice.id }); + if (order === null) + throw new Error("order was not found"); logger.info( `Order ${order._id} Invoice with hash: ${id} is being held!` ); const buyerUser = await User.findOne({ _id: order.buyer_id }); + if (buyerUser === null) + throw new Error("buyerUser was not found"); const sellerUser = await User.findOne({ _id: order.seller_id }); + if (sellerUser === null) + throw new Error("sellerUser was not found"); order.status = 'ACTIVE'; // This is the i18n context we need to pass to the message const i18nCtxBuyer = await getUserI18nContext(buyerUser); @@ -47,11 +55,13 @@ const subscribeInvoice = async (bot, id, resub) => { rate ); } - order.invoice_held_at = Date.now(); + order.invoice_held_at = new Date(); order.save(); } if (invoice.is_confirmed) { const order = await Order.findOne({ hash: id }); + if (order === null) + throw new Error("order was not found"); logger.info( `Order ${order._id} - Invoice with hash: ${id} was settled!` ); @@ -70,12 +80,16 @@ const subscribeInvoice = async (bot, id, resub) => { } }; -const payHoldInvoice = async (bot, order) => { +const payHoldInvoice = async (bot: HasTelegram, order: IOrder) => { try { order.status = 'PAID_HOLD_INVOICE'; await order.save(); const buyerUser = await User.findOne({ _id: order.buyer_id }); + if (buyerUser === null) + throw new Error("buyerUser was not found"); const sellerUser = await User.findOne({ _id: order.seller_id }); + if (sellerUser === null) + throw new Error("sellerUser was not found"); // We need two i18n contexts to send messages to each user const i18nCtxBuyer = await getUserI18nContext(buyerUser); const i18nCtxSeller = await getUserI18nContext(sellerUser); @@ -135,4 +149,4 @@ const payHoldInvoice = async (bot, order) => { } }; -module.exports = { subscribeInvoice, payHoldInvoice }; +export { subscribeInvoice, payHoldInvoice }; diff --git a/ln/subscribe_probe.js b/ln/subscribe_probe.ts similarity index 66% rename from ln/subscribe_probe.js rename to ln/subscribe_probe.ts index 4dc798bb..09ca4df0 100644 --- a/ln/subscribe_probe.js +++ b/ln/subscribe_probe.ts @@ -1,8 +1,8 @@ -const { subscribeToProbeForRoute } = require('lightning'); -const lnd = require('./connect'); -const { logger } = require('../logger'); +import { subscribeToProbeForRoute } from 'lightning'; +import lnd from './connect'; +import { logger } from '../logger'; -const subscribeProbe = async (destination, tokens) => { +const subscribeProbe = async (destination: string, tokens: number) => { try { const sub = subscribeToProbeForRoute({ destination, lnd, tokens }); sub.on('probe_success', async route => @@ -18,4 +18,4 @@ const subscribeProbe = async (destination, tokens) => { } }; -module.exports = subscribeProbe; +export { subscribeProbe }; From 7e63be40e5978ef2f768ce378bbaacd604c521c8 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 23 Oct 2024 11:37:24 +0200 Subject: [PATCH 14/22] package.json: install type packages Add type packages needed for Typescript code to package.json. --- .github/workflows/integrate.yaml | 4 ++-- package-lock.json | 9 ++++++++- package.json | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 6cd191db..2d0a58f6 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -55,8 +55,8 @@ jobs: sleep 10 mongosh --eval 'db.getSiblingDB("lnp2pbot").createCollection("mycollection")' - - - run: npm install @types/node @types/i18n @types/mocha + + - run: npm install @types/node - run: npx tsc - run: npm ci - name: Run tests diff --git a/package-lock.json b/package-lock.json index a43054ab..4f2bb43f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@grammyjs/i18n": "^0.5.1", "@grammyjs/ratelimiter": "^1.1.5", + "@types/i18n": "^0.13.12", + "@types/mocha": "^10.0.9", "axios": "^1.7.4", "crypto": "^1.0.1", "dotenv": "^10.0.0", @@ -27,7 +29,7 @@ }, "devDependencies": { "@types/mocha": "^10.0.9", - "@types/node": "^20.5.0", + "@types/node": "^20.16.15", "@types/node-schedule": "^2.1.0", "@types/qrcode": "^1.5.2", "chai": "^4.3.4", @@ -1757,6 +1759,11 @@ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, + "node_modules/@types/i18n": { + "version": "0.13.12", + "resolved": "https://registry.npmjs.org/@types/i18n/-/i18n-0.13.12.tgz", + "integrity": "sha512-iAd2QjKh+0ToBXocmCS3m38GskiaGzmSV1MTQz2GaOraqSqBiLf46J7u3EGINl+st+Uk4lO3OL7QyIjTJlrWIg==" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", diff --git a/package.json b/package.json index 89a4f469..eec5f9db 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "dependencies": { "@grammyjs/i18n": "^0.5.1", "@grammyjs/ratelimiter": "^1.1.5", + "@types/i18n": "^0.13.12", + "@types/mocha": "^10.0.9", "axios": "^1.7.4", "crypto": "^1.0.1", "dotenv": "^10.0.0", @@ -41,7 +43,7 @@ ], "devDependencies": { "@types/mocha": "^10.0.9", - "@types/node": "^20.5.0", + "@types/node": "^20.16.15", "@types/node-schedule": "^2.1.0", "@types/qrcode": "^1.5.2", "chai": "^4.3.4", From 28d388f33d45988ee09319fb04cd6bce3065cd02 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 23 Oct 2024 11:42:06 +0200 Subject: [PATCH 15/22] use eslint on TS files Setup eslint so that it checks .ts files instead of .js. --- .eslintrc.json | 1 + package-lock.json | 272 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 6 +- 3 files changed, 276 insertions(+), 3 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2b6d4ebd..1b0c6a8f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,6 +5,7 @@ "node": true, "jest": true }, + "parser": "@typescript-eslint/parser", "extends": ["standard", "eslint-config-prettier"], "parserOptions": { "ecmaVersion": "latest" diff --git a/package-lock.json b/package-lock.json index 4f2bb43f..236c57b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,9 +32,11 @@ "@types/node": "^20.16.15", "@types/node-schedule": "^2.1.0", "@types/qrcode": "^1.5.2", + "@typescript-eslint/eslint-plugin": "^8.11.0", + "@typescript-eslint/parser": "^8.11.0", "chai": "^4.3.4", "chokidar": "^3.5.3", - "eslint": "^8.15.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^8.5.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.26.0", @@ -1898,6 +1900,224 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", + "integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/type-utils": "8.11.0", + "@typescript-eslint/utils": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz", + "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/typescript-estree": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", + "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz", + "integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.11.0", + "@typescript-eslint/utils": "8.11.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", + "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", + "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz", + "integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/typescript-estree": "8.11.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", + "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.11.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -3791,6 +4011,22 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5144,6 +5380,15 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -5152,6 +5397,19 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -7365,6 +7623,18 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", diff --git a/package.json b/package.json index eec5f9db..b7718d14 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "node ./dist/app", "predev": "npx tsc", "dev": "nodemon ./dist/app", - "lint": "eslint .", + "lint": "eslint . --ext .ts", "format": "prettier --write '**/*.{js,ts}'", "pretest": "tsc -p tsconfig.test.json", "test": "export NODE_ENV=test && mocha --exit 'dist/tests/**/*.spec.js'" @@ -46,9 +46,11 @@ "@types/node": "^20.16.15", "@types/node-schedule": "^2.1.0", "@types/qrcode": "^1.5.2", + "@typescript-eslint/eslint-plugin": "^8.11.0", + "@typescript-eslint/parser": "^8.11.0", "chai": "^4.3.4", "chokidar": "^3.5.3", - "eslint": "^8.15.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^8.5.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.26.0", From 4431e147ca540f054924dab8aa868577f1a25c33 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 23 Oct 2024 12:19:09 +0200 Subject: [PATCH 16/22] fix eslint import ordering errors Fix "Import in body of module; reorder to top import/first" eslint errors. --- bot/commands.ts | 15 +++++++-------- bot/messages.ts | 3 +-- bot/modules/community/scenes.ts | 3 ++- bot/modules/nostr/events.ts | 4 ++-- bot/modules/nostr/index.ts | 3 ++- bot/ordersActions.ts | 4 ++-- bot/scenes.ts | 6 +++--- bot/start.ts | 15 ++++++++------- bot/validations.ts | 10 +++++----- jobs/node_info.ts | 4 ++-- jobs/pending_payments.ts | 4 ++-- ln/pay_request.ts | 3 ++- tests/bot/bot.spec.ts | 7 ++++--- tests/bot/validation.spec.ts | 14 +++++++------- tests/ln/lightning.spec.ts | 7 ++++--- 15 files changed, 53 insertions(+), 49 deletions(-) diff --git a/bot/commands.ts b/bot/commands.ts index cce4fd61..2473c1ed 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -1,10 +1,4 @@ import { validateFiatSentOrder, validateReleaseOrder } from './validations'; -const { - createHoldInvoice, - subscribeInvoice, - cancelHoldInvoice, - settleHoldInvoice, -} = require('../ln'); import { Order, User, Dispute } from '../models'; import * as messages from './messages'; import { getBtcFiatPrice, deleteOrderFromChannel, getUserI18nContext, getFee, removeLightningPrefix } from '../util'; @@ -13,12 +7,17 @@ import * as OrderEvents from './modules/events/orders'; import { resolvLightningAddress } from '../lnurl/lnurl-pay'; import { logger } from '../logger'; -import { Telegraf } from 'telegraf'; import { IOrder } from '../models/order'; import { UserDocument } from '../models/user'; import { HasTelegram, MainContext } from './start'; import { CommunityContext } from './modules/community/communityContext'; -import { Types } from 'mongoose'; + +const { + createHoldInvoice, + subscribeInvoice, + cancelHoldInvoice, + settleHoldInvoice, +} = require('../ln'); const waitPayment = async (ctx: MainContext, bot: HasTelegram, buyer: UserDocument, seller: UserDocument, order: IOrder, buyerInvoice: any) => { try { diff --git a/bot/messages.ts b/bot/messages.ts index 6c9d67fd..5c70c454 100644 --- a/bot/messages.ts +++ b/bot/messages.ts @@ -1,4 +1,4 @@ -import { TelegramError } from 'telegraf' +import { TelegramError, Telegraf } from 'telegraf' import QR from 'qrcode'; import { getCurrency, @@ -18,7 +18,6 @@ import { logger } from "../logger"; import { HasTelegram, MainContext } from './start'; import { UserDocument } from '../models/user' import { IOrder } from '../models/order' -import { Telegraf } from 'telegraf'; import { I18nContext } from '@grammyjs/i18n'; import { IConfig } from '../models/config'; import { IPendingPayment } from '../models/pending_payment'; diff --git a/bot/modules/community/scenes.ts b/bot/modules/community/scenes.ts index 6cbb8bb6..78bb2003 100644 --- a/bot/modules/community/scenes.ts +++ b/bot/modules/community/scenes.ts @@ -2,7 +2,6 @@ import { Scenes } from 'telegraf'; import { logger } from '../../../logger'; import { Community, User, PendingPayment } from '../../../models'; import { IOrderChannel, IUsernameId } from '../../../models/community'; -const { isPendingPayment } = require('../../../ln'); import { isGroupAdmin, itemsFromMessage, removeAtSymbol } from '../../../util'; import * as messages from '../../messages'; import { isValidInvoice } from '../../validations'; @@ -10,6 +9,8 @@ import { createCommunityWizardStatus, wizardCommunityWrongPermission } from './m import { CommunityContext } from './communityContext'; import * as commAdmin from './scenes.communityAdmin'; +const { isPendingPayment } = require('../../../ln'); + const CURRENCIES = parseInt(process.env.COMMUNITY_CURRENCIES || '10'); export const communityAdmin = commAdmin.communityAdmin(); diff --git a/bot/modules/nostr/events.ts b/bot/modules/nostr/events.ts index 98deee95..8ebb0c68 100644 --- a/bot/modules/nostr/events.ts +++ b/bot/modules/nostr/events.ts @@ -1,10 +1,10 @@ -const { finalizeEvent, verifyEvent } = require('nostr-tools/pure'); import * as Config from './config'; - import { Community } from '../../../models'; import { toKebabCase, removeAtSymbol } from '../../../util'; import { IOrder } from '../../../models/order'; +const { finalizeEvent, verifyEvent } = require('nostr-tools/pure'); + /// All events broadcasted are Parameterized Replaceable Events, /// the event kind must be between 30000 and 39999 const kind = 38383; diff --git a/bot/modules/nostr/index.ts b/bot/modules/nostr/index.ts index 30641f29..b5690d0c 100644 --- a/bot/modules/nostr/index.ts +++ b/bot/modules/nostr/index.ts @@ -1,4 +1,3 @@ -require('websocket-polyfill'); // is it needed? import { logger } from '../../../logger'; import * as Config from './config'; import { createOrderEvent } from './events'; @@ -7,6 +6,8 @@ import { Telegraf } from 'telegraf'; import { IOrder } from '../../../models/order'; import { CommunityContext } from '../community/communityContext'; +require('websocket-polyfill'); // is it needed? + export const configure = (bot: Telegraf) => { bot.command('/nostr', Commands.info); diff --git a/bot/ordersActions.ts b/bot/ordersActions.ts index b52b4454..1d8ffb9e 100644 --- a/bot/ordersActions.ts +++ b/bot/ordersActions.ts @@ -1,4 +1,3 @@ -const { ObjectId } = require('mongoose').Types; import { Order, Community } from '../models'; import * as messages from './messages'; import { getCurrency, numberFormat, getBtcExchangePrice, getFee, getUserAge, getStars } from '../util'; @@ -8,9 +7,10 @@ import { UserDocument } from '../models/user'; import { HasTelegram, MainContext } from './start'; import { IOrder } from '../models/order'; import { IFiat } from '../util/fiatModel'; - import * as OrderEvents from './modules/events/orders'; +const { ObjectId } = require('mongoose').Types; + interface CreateOrderArguments { type: string; amount: number; diff --git a/bot/scenes.ts b/bot/scenes.ts index 477e4fd4..c5e4c3f9 100644 --- a/bot/scenes.ts +++ b/bot/scenes.ts @@ -1,15 +1,15 @@ import { Scenes } from 'telegraf'; -const { parsePaymentRequest } = require('invoices'); import { isValidInvoice, validateLightningAddress } from './validations'; import { Order, PendingPayment } from '../models'; import { waitPayment, addInvoice, showHoldInvoice } from './commands'; import { getCurrency, getUserI18nContext } from '../util'; import * as messages from './messages'; -const { isPendingPayment } = require('../ln'); import { logger } from '../logger'; import { resolvLightningAddress } from '../lnurl/lnurl-pay'; import { CommunityContext } from './modules/community/communityContext'; -import * as OrderEvents from './modules/events/orders'; + +const { parsePaymentRequest } = require('invoices'); +const { isPendingPayment } = require('../ln'); interface InvoiceParseResult { invoice?: any; diff --git a/bot/start.ts b/bot/start.ts index 4ccf6466..0c3cbf7f 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -40,13 +40,6 @@ import { fiatSent, release, } from './commands'; -const { - settleHoldInvoice, - cancelHoldInvoice, - payToBuyer, - subscribeInvoice, - getInvoice, -} = require('../ln'); import { validateUser, validateParams, @@ -67,6 +60,14 @@ import { logger } from "../logger"; import { ICommunity, IUsernameId } from '../models/community'; import { CommunityContext } from './modules/community/communityContext'; +const { + settleHoldInvoice, + cancelHoldInvoice, + payToBuyer, + subscribeInvoice, + getInvoice, +} = require('../ln'); + export interface MainContext extends Context { match: Array | null; i18n: I18nContext; diff --git a/bot/validations.ts b/bot/validations.ts index ea6ee77c..c4f1f30f 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -1,18 +1,18 @@ import { HasTelegram, MainContext, OrderQuery, ctxUpdateAssertMsg } from "./start"; -import { ICommunity, IUsernameId } from "../models/community"; +import { IUsernameId } 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; import * as messages from './messages'; import { Order, User, Community } from '../models'; import { isIso4217, isDisputeSolver, removeLightningPrefix, isOrderCreator } from '../util'; -const { existLightningAddress } = require('../lnurl/lnurl-pay'); +import { existLightningAddress } from '../lnurl/lnurl-pay'; import { logger } from '../logger'; +const { parsePaymentRequest } = require('invoices'); +const { ObjectId } = require('mongoose').Types; + const ctxUpdateMessageFromAssertMsg = "ctx.update.message.from is not available"; // We look in database if the telegram user exists, diff --git a/jobs/node_info.ts b/jobs/node_info.ts index 2fc20b05..6e366abc 100644 --- a/jobs/node_info.ts +++ b/jobs/node_info.ts @@ -1,10 +1,10 @@ import { Telegraf } from "telegraf"; - import { Config } from '../models'; import { CommunityContext } from "../bot/modules/community/communityContext"; -const { getInfo } = require('../ln'); import { logger } from '../logger'; +const { getInfo } = require('../ln'); + const info = async (bot: Telegraf) => { try { let config = await Config.findOne({}); diff --git a/jobs/pending_payments.ts b/jobs/pending_payments.ts index 9f14cf35..ddc0106f 100644 --- a/jobs/pending_payments.ts +++ b/jobs/pending_payments.ts @@ -3,12 +3,12 @@ import * as messages from '../bot/messages'; 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'; import { CommunityContext } from '../bot/modules/community/communityContext'; import { orderUpdated } from '../bot/modules/events/orders'; +const { payRequest, isPendingPayment } = require('../ln'); + export const attemptPendingPayments = async (bot: Telegraf): Promise => { const pendingPayments = await PendingPayment.find({ paid: false, diff --git a/ln/pay_request.ts b/ln/pay_request.ts index d070d517..3ecccd15 100644 --- a/ln/pay_request.ts +++ b/ln/pay_request.ts @@ -4,7 +4,6 @@ import { deleteForwardingReputations, AuthenticatedLnd, } from 'lightning'; -const { parsePaymentRequest } = require('invoices'); import { User, PendingPayment } from '../models'; import lnd from './connect'; import { handleReputationItems, getUserI18nContext } from '../util'; @@ -14,6 +13,8 @@ import * as OrderEvents from '../bot/modules/events/orders'; import { IOrder } from '../models/order'; import { HasTelegram } from '../bot/start'; +const { parsePaymentRequest } = require('invoices'); + interface PayViaPaymentRequestParams { lnd: AuthenticatedLnd; request: string; diff --git a/tests/bot/bot.spec.ts b/tests/bot/bot.spec.ts index bebac146..c91475ff 100644 --- a/tests/bot/bot.spec.ts +++ b/tests/bot/bot.spec.ts @@ -1,8 +1,5 @@ import path from 'path'; import fs from 'fs'; -const sinon = require('sinon'); -const { expect } = require('chai'); -const proxyquire = require('proxyquire'); import { initialize } from '../../bot'; import { User, Order } from '../../models'; @@ -10,6 +7,10 @@ import { getCurrenciesWithPrice } from '../../util'; import { mockUpdatesResponseForCurrencies } from './mocks/currenciesResponse'; import { mockUpdatesResponseForLanguages } from './mocks/languagesResponse'; +const sinon = require('sinon'); +const { expect } = require('chai'); +const proxyquire = require('proxyquire'); + describe('Telegram bot', () => { const token = '123456'; let server: any; diff --git a/tests/bot/validation.spec.ts b/tests/bot/validation.spec.ts index e49c6d6d..c87ed175 100644 --- a/tests/bot/validation.spec.ts +++ b/tests/bot/validation.spec.ts @@ -1,9 +1,4 @@ -const { expect } = require('chai'); -const sinon = require('sinon'); -const { ObjectId } = require('mongoose').Types; -const proxyquire = require('proxyquire'); - -const { +import { validateSellOrder, validateBuyOrder, validateInvoice, @@ -16,11 +11,16 @@ const { validateTakeSellOrder, validateUserWaitingOrder, isBannedFromCommunity, -} = require('../../bot/validations'); +} from '../../bot/validations'; import * as messages from '../../bot/messages'; import { Order, User, Community } from '../../models'; import { IOrder } from '../../models/order'; +const { expect } = require('chai'); +const sinon = require('sinon'); +const { ObjectId } = require('mongoose').Types; +const proxyquire = require('proxyquire'); + describe('Validations', () => { let ctx: any; let replyStub: any; diff --git a/tests/ln/lightning.spec.ts b/tests/ln/lightning.spec.ts index 50618077..204ff698 100644 --- a/tests/ln/lightning.spec.ts +++ b/tests/ln/lightning.spec.ts @@ -1,10 +1,11 @@ -const sinon = require('sinon'); -const { expect } = require('chai'); import lightning from 'lightning'; -const { parsePaymentRequest } = require('invoices'); import { mockCreateHodlResponseForLightning } from './mocks/lightningResponse'; const { createHoldInvoice } = require('../../ln'); +const sinon = require('sinon'); +const { expect } = require('chai'); +const { parsePaymentRequest } = require('invoices'); + describe('Lighting network', () => { it('Should create hold invoice', async () => { // We spy on the lighting service call From ea2a3c6257b1008a026111a6be87e5d50c1851f1 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 5 Dec 2024 13:45:24 +0100 Subject: [PATCH 17/22] bot: change signature of notAuthorized function From Promise to Promise, to get rid of the following error: ``` tests/bot/validation.spec.ts(376,19): error TS2339: Property 'tg_id' does not exist on type 'void | (UserDocument & { _id: ObjectId; })'. Property 'tg_id' does not exist on type 'void'. ``` --- bot/messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/messages.ts b/bot/messages.ts index 5c70c454..0c3340a1 100644 --- a/bot/messages.ts +++ b/bot/messages.ts @@ -1567,7 +1567,7 @@ const currencyNotSupportedMessage = async (ctx: MainContext, currencies: Array { +const notAuthorized = async (ctx: MainContext, tgId?: string) : Promise => { try { if (tgId) { await ctx.telegram.sendMessage(tgId, ctx.i18n.t('not_authorized')); From 3f09acf40382870ede4387272cf88162ca3d3c3d Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 23 Oct 2024 12:30:49 +0200 Subject: [PATCH 18/22] fix more eslint errors --- bot/modules/community/communityContext.ts | 2 +- bot/modules/dispute/commands.ts | 1 - bot/modules/orders/index.ts | 1 - bot/modules/orders/takeOrder.ts | 1 - bot/start.ts | 2 +- jobs/cancel_orders.ts | 3 +-- jobs/pending_payments.ts | 2 +- ln/hold_invoice.ts | 2 +- ln/pay_request.ts | 2 +- ln/resubscribe_invoices.ts | 2 +- 10 files changed, 7 insertions(+), 11 deletions(-) diff --git a/bot/modules/community/communityContext.ts b/bot/modules/community/communityContext.ts index 9975e880..10d0b8ba 100644 --- a/bot/modules/community/communityContext.ts +++ b/bot/modules/community/communityContext.ts @@ -1,7 +1,7 @@ import { MainContext } from '../../start'; import { SceneContextScene, WizardContextWizard, WizardSessionData } from 'telegraf/typings/scenes'; import { Update, Message } from 'telegraf/typings/core/types/typegram'; -import { Scenes, Telegraf } from 'telegraf'; +import { Telegraf } from 'telegraf'; import { ICommunity, IOrderChannel, IUsernameId } from '../../../models/community'; import { IOrder } from '../../../models/order'; import { UserDocument } from '../../../models/user'; diff --git a/bot/modules/dispute/commands.ts b/bot/modules/dispute/commands.ts index dd7ce41e..2347ada2 100644 --- a/bot/modules/dispute/commands.ts +++ b/bot/modules/dispute/commands.ts @@ -5,7 +5,6 @@ import { validateParams, validateObjectId, validateDisputeOrder } from '../../va import * as messages from './messages'; import * as globalMessages from '../../messages'; import { logger } from '../../../logger'; -import * as OrderEvents from '../../modules/events/orders'; import { removeAtSymbol } from '../../../util'; const dispute = async (ctx: MainContext) => { diff --git a/bot/modules/orders/index.ts b/bot/modules/orders/index.ts index b825bd4c..65fd4c99 100644 --- a/bot/modules/orders/index.ts +++ b/bot/modules/orders/index.ts @@ -10,7 +10,6 @@ import { takeOrderActionValidation, takeOrderValidation, takesell, takebuyValida import { extractId } from '../../../util'; import { Telegraf } from 'telegraf'; import { CommunityContext } from '../community/communityContext'; -import { MainContext } from '../../start'; export * as Scenes from './scenes'; export const configure = (bot: Telegraf) => { diff --git a/bot/modules/orders/takeOrder.ts b/bot/modules/orders/takeOrder.ts index ba8b8cce..15f78ac3 100644 --- a/bot/modules/orders/takeOrder.ts +++ b/bot/modules/orders/takeOrder.ts @@ -1,5 +1,4 @@ // @ts-check -import { Telegraf } from 'telegraf'; import { logger } from '../../../logger'; import { Order } from '../../../models'; import { deleteOrderFromChannel } from '../../../util'; diff --git a/bot/start.ts b/bot/start.ts index 0c3cbf7f..d09ba9e2 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -57,7 +57,7 @@ import { nodeInfo, } from '../jobs'; import { logger } from "../logger"; -import { ICommunity, IUsernameId } from '../models/community'; +import { IUsernameId } from '../models/community'; import { CommunityContext } from './modules/community/communityContext'; const { diff --git a/jobs/cancel_orders.ts b/jobs/cancel_orders.ts index 05e1887f..2800e1ff 100644 --- a/jobs/cancel_orders.ts +++ b/jobs/cancel_orders.ts @@ -1,5 +1,4 @@ -import { Telegraf } from "telegraf"; -import { HasTelegram, MainContext } from "../bot/start"; +import { HasTelegram } from "../bot/start"; import { User, Order } from "../models"; import { cancelShowHoldInvoice, cancelAddInvoice } from '../bot/commands'; import * as messages from "../bot/messages"; diff --git a/jobs/pending_payments.ts b/jobs/pending_payments.ts index ddc0106f..58d08f38 100644 --- a/jobs/pending_payments.ts +++ b/jobs/pending_payments.ts @@ -36,7 +36,7 @@ export const attemptPendingPayments = async (bot: Telegraf): P // If one of the payments is on flight we don't do anything if (isPending || isPendingOldPayment) return; - let payment = await payRequest({ + const payment = await payRequest({ amount: pending.amount, request: pending.payment_request, }); diff --git a/ln/hold_invoice.ts b/ln/hold_invoice.ts index 1d870151..4eb4b004 100644 --- a/ln/hold_invoice.ts +++ b/ln/hold_invoice.ts @@ -1,4 +1,4 @@ -import { BinaryLike, createHash, randomBytes } from 'crypto'; +import { createHash, randomBytes } from 'crypto'; import lightning from 'lightning'; import lnd from './connect'; import { logger } from '../logger'; diff --git a/ln/pay_request.ts b/ln/pay_request.ts index 3ecccd15..198a9915 100644 --- a/ln/pay_request.ts +++ b/ln/pay_request.ts @@ -30,7 +30,7 @@ const payRequest = async ({ request, amount }: { request: string, amount: number // If the invoice is expired we return is_expired = true if (invoice.is_expired) return invoice; - let maxRoutingFee = process.env.MAX_ROUTING_FEE; + const maxRoutingFee = process.env.MAX_ROUTING_FEE; if (maxRoutingFee === undefined) throw new Error("Environment variable MAX_ROUTING_FEE is not defined"); // We need to set a max fee amount diff --git a/ln/resubscribe_invoices.ts b/ln/resubscribe_invoices.ts index 7829c855..0ac09d85 100644 --- a/ln/resubscribe_invoices.ts +++ b/ln/resubscribe_invoices.ts @@ -1,4 +1,4 @@ -import { getInvoices, GetInvoicesResult } from 'lightning'; +import { getInvoices } from 'lightning'; import lnd from './connect'; import { subscribeInvoice } from './subscribe_invoice'; import { Order } from '../models'; From c00f3ae89b61524c5958594342540236e6242576 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 23 Oct 2024 12:38:18 +0200 Subject: [PATCH 19/22] eslint: silenced remaining errors Silenced remaining eslint errors as what they refer to is intended behavior. --- bot/modules/community/communityContext.ts | 2 ++ bot/modules/community/scenes.ts | 3 ++- bot/modules/orders/scenes.ts | 1 + bot/start.ts | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bot/modules/community/communityContext.ts b/bot/modules/community/communityContext.ts index 10d0b8ba..c174e273 100644 --- a/bot/modules/community/communityContext.ts +++ b/bot/modules/community/communityContext.ts @@ -6,11 +6,13 @@ import { ICommunity, IOrderChannel, IUsernameId } from '../../../models/communit import { IOrder } from '../../../models/order'; import { UserDocument } from '../../../models/user'; +/* eslint-disable no-use-before-define */ export interface CommunityContext extends MainContext { scene: SceneContextScene; wizard: CommunityWizard; message: (Update.New & Update.NonChannel & Message.TextMessage) | undefined; } +/* eslint-enable no-use-before-define */ // This type is catch-all for any wizard state and probably should be split into several // more specialized types. diff --git a/bot/modules/community/scenes.ts b/bot/modules/community/scenes.ts index 78bb2003..a6d70d0c 100644 --- a/bot/modules/community/scenes.ts +++ b/bot/modules/community/scenes.ts @@ -106,6 +106,7 @@ export const communityWizard = new Scenes.WizardScene( } ctx.wizard.selectStep(0); // use ['steps'] syntax as steps is private property and TypeScript would complain + // eslint-disable-next-line dot-notation return ctx.wizard['steps'][ctx.wizard.cursor](ctx); } catch (err) { logger.error(err); @@ -231,7 +232,7 @@ const createCommunitySteps = { } const { bot, user } = ctx.wizard.state; const chan = itemsFromMessage(text); - //ctx.wizard.state.channels = chan; // ??? + // ctx.wizard.state.channels = chan; // ??? if (chan.length > 2) { await ctx.telegram.deleteMessage( ctx.message!.chat.id, diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 126ac58b..c6cb0aa1 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -94,6 +94,7 @@ export const createOrder = new Scenes.WizardScene( } await ctx.wizard.selectStep(0); // use ["steps"] syntax as steps is private property + // eslint-disable-next-line dot-notation return ctx.wizard["steps"][ctx.wizard.cursor](ctx); } catch (err) { logger.error(err); diff --git a/bot/start.ts b/bot/start.ts index d09ba9e2..6abd6455 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -278,6 +278,7 @@ const initialize = (botToken: string, options: Partial Date: Thu, 5 Dec 2024 14:11:06 +0100 Subject: [PATCH 20/22] tsconfig.json: set "strict" to true Like it was before 4b278ce. --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index ecc59192..f423479e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "strict": false, + "strict": true, "esModuleInterop": true, "resolveJsonModule": true, "downlevelIteration": true, From 4db11194783dfd56055fca1982a5d96e6cccb7c1 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 5 Dec 2024 14:18:01 +0100 Subject: [PATCH 21/22] bot: fix errors introduced by TS strict mode By adding guards for values that can be null/undefined. --- bot/commands.ts | 8 ++++++++ bot/validations.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bot/commands.ts b/bot/commands.ts index 2473c1ed..ce8d445a 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -246,6 +246,8 @@ const cancelAddInvoice = async (ctx: CommunityContext, order: IOrder | null = nu ctx.deleteMessage(); ctx.scene.leave(); userAction = true; + if (ctx.from === undefined) + throw new Error("ctx.from is undefined"); userTgId = String(ctx.from.id); if (order === null) { const orderId = !!ctx && (ctx.update as any).callback_query.message.text; @@ -271,6 +273,8 @@ const cancelAddInvoice = async (ctx: CommunityContext, order: IOrder | null = nu if(sellerUser === null) throw new Error("sellerUser was not found"); const buyerUser = await User.findOne({ _id: order.buyer_id }); + if(buyerUser === null) + throw new Error("buyerUser was not found"); const sellerTgId = sellerUser.tg_id; // If order creator cancels it, it will not be republished if (order.creator_id === order.buyer_id) { @@ -432,6 +436,8 @@ const cancelShowHoldInvoice = async (ctx: CommunityContext, order: IOrder | null ctx.deleteMessage(); ctx.scene.leave(); userAction = true; + if (ctx.from === undefined) + throw new Error("ctx.from is undefined"); userTgId = String(ctx.from.id); if (order === null) { const orderId = !!ctx && (ctx.update as any).callback_query.message.text; @@ -455,6 +461,8 @@ const cancelShowHoldInvoice = async (ctx: CommunityContext, order: IOrder | null if(buyerUser === null) throw new Error("buyerUser was not found"); const sellerUser = await User.findOne({ _id: order.seller_id }); + if(sellerUser === null) + throw new Error("sellerUser was not found"); const buyerTgId = buyerUser.tg_id; // If order creator cancels it, it will not be republished if (order.creator_id === order.seller_id) { diff --git a/bot/validations.ts b/bot/validations.ts index c4f1f30f..a46b1bea 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -434,7 +434,7 @@ const isValidInvoice = async (ctx: MainContext, lnInvoice: string) => { }; -const validateTakeSellOrder = async (ctx: MainContext, bot: HasTelegram, user: UserDocument, order: IOrder) => { +const validateTakeSellOrder = async (ctx: MainContext, bot: HasTelegram, user: UserDocument, order: IOrder | null) => { try { if (!order) { await messages.invalidOrderMessage(ctx, bot, user); From 79204c22a62bc05118516c91c680375878a1d95e Mon Sep 17 00:00:00 2001 From: webwarrior Date: Thu, 5 Dec 2024 14:37:14 +0100 Subject: [PATCH 22/22] tsconfig.json: remove "allowJs" property Because all code is now converted to TS. --- tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index f423479e..63ff67a6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,6 @@ "lib":["ES2021", "DOM"], "outDir": "./dist", "rootDir": ".", - "allowJs": true, "moduleResolution": "node" }, "include": [