From d6b2e358cf03f0f88da0b6fbdc13b276de064808 Mon Sep 17 00:00:00 2001 From: Alex Sukhov Date: Mon, 30 Jan 2023 03:07:11 +0300 Subject: [PATCH 1/5] WIP - Configure persistence for settings and add-transactions --- package-lock.json | 15 ++++- package.json | 1 + src/composers/helpers.ts | 17 +++--- src/composers/settings.ts | 40 +++++-------- src/composers/transactions/add-transaction.ts | 60 ++++++++----------- src/index.ts | 40 +++++++++---- src/lib/firefly/index.ts | 8 +-- src/lib/middlewares.ts | 4 +- src/types/SessionData.ts | 27 ++++++++- 9 files changed, 118 insertions(+), 94 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0cafc9..552f188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "firefly-iii-telegram-bot", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firefly-iii-telegram-bot", - "version": "1.0.1", + "version": "1.0.2", "license": "GPL-3.0-or-later", "dependencies": { "@grammyjs/i18n": "0.5.1", "@grammyjs/menu": "1.1.2", "@grammyjs/router": "2.0.0", + "@grammyjs/storage-file": "2.1.0", "axios": "1.2.6", "dayjs": "1.11.7", "debug": "4.3.4", @@ -127,6 +128,11 @@ "grammy": "^1.0.0" } }, + "node_modules/@grammyjs/storage-file": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@grammyjs/storage-file/-/storage-file-2.1.0.tgz", + "integrity": "sha512-XfSVE+vgkHxUWUpHVprarLR5m/5HXT+aHTk4U+hXILKiD0wG70zYvS8BrA5V6W40Ygovo9a4Y+MLvRtfECs5JA==" + }, "node_modules/@grammyjs/types": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-2.11.2.tgz", @@ -6415,6 +6421,11 @@ "integrity": "sha512-xJ3HRNj3mVojLtkFmV+GvKpOWOnS0CGdAIdvpaq+j+8dic1wM1OhOukaotkKzAWZ6bb9QyzUUgVFN6xfZDO5VA==", "requires": {} }, + "@grammyjs/storage-file": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@grammyjs/storage-file/-/storage-file-2.1.0.tgz", + "integrity": "sha512-XfSVE+vgkHxUWUpHVprarLR5m/5HXT+aHTk4U+hXILKiD0wG70zYvS8BrA5V6W40Ygovo9a4Y+MLvRtfECs5JA==" + }, "@grammyjs/types": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-2.11.2.tgz", diff --git a/package.json b/package.json index d81b59e..d829b69 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@grammyjs/i18n": "0.5.1", "@grammyjs/menu": "1.1.2", "@grammyjs/router": "2.0.0", + "@grammyjs/storage-file": "2.1.0", "axios": "1.2.6", "dayjs": "1.11.7", "debug": "4.3.4", diff --git a/src/composers/helpers.ts b/src/composers/helpers.ts index 32c7891..d3a2a8a 100644 --- a/src/composers/helpers.ts +++ b/src/composers/helpers.ts @@ -8,7 +8,6 @@ import { Keyboard, InlineKeyboard } from 'grammy' import firefly from '../lib/firefly' import Mapper from '../lib/Mapper' import type { MyContext } from '../types/MyContext' -import { getUserStorage } from '../lib/storage' import { TransactionRead } from '../lib/firefly/model/transaction-read' import { TransactionTypeProperty } from '../lib/firefly/model/transaction-type-property' import { TransactionSplit } from '../lib/firefly/model/transaction-split' @@ -145,10 +144,10 @@ function formatTransaction(ctx: MyContext, tr: Partial){ return ctx.i18n.t(translationString, { ...baseProps }) } -async function createCategoriesKeyboard(userId: number, mapper: Mapper) { +async function createCategoriesKeyboard(ctx: MyContext, mapper: Mapper) { const log = debug.extend('createCategoriesKeyboard') try { - const categories = (await firefly(userId).Categories.listCategory()).data.data + const categories = (await firefly(ctx.session.userSettings).Categories.listCategory()).data.data log('categories: %O', categories) const keyboard = new InlineKeyboard() @@ -173,7 +172,7 @@ async function createCategoriesKeyboard(userId: number, mapper: Mapper) { } async function createAccountsKeyboard( - userId: number, + ctx: MyContext, accountType: AccountTypeFilter | AccountTypeFilter[], mapper: Mapper, opts?: { skipAccountId: string } @@ -185,7 +184,7 @@ async function createAccountsKeyboard( if (Array.isArray(accountType)) { const promises: any = [] - accountType.forEach(accType => promises.push(firefly(userId).Accounts.listAccount(1, now, accType))) + accountType.forEach(accType => promises.push(firefly(ctx.session.userSettings).Accounts.listAccount(1, now, accType))) const responses = await Promise.all(promises) log('Responses length: %s', responses.length) @@ -194,7 +193,7 @@ async function createAccountsKeyboard( return r.data.data })) } else { - accounts = (await firefly(userId).Accounts.listAccount(1, now, accountType)).data.data + accounts = (await firefly(ctx.session.userSettings).Accounts.listAccount(1, now, accountType)).data.data } log('accounts: %O', accounts) @@ -274,8 +273,7 @@ function formatTransactionUpdate( function createEditMenuKeyboard(ctx: MyContext, tr: TransactionRead) { const keyboard = new InlineKeyboard() const trId = tr.id - const userId = ctx.from!.id - const { fireflyUrl } = getUserStorage(userId) + const { fireflyUrl } = ctx.session.userSettings // Only withdrawal transactions may have category assigned if (tr.attributes.transactions[0].type === 'withdrawal') { @@ -357,8 +355,7 @@ function generateWelcomeMessage(ctx: MyContext) { const log = debug.extend('generateWelcomeMessage') log('start: %O', ctx.message) - const userId = ctx.from!.id - const { fireflyUrl, fireflyAccessToken } = getUserStorage(userId) + const { fireflyUrl, fireflyAccessToken } = ctx.session.userSettings let welcomeMessage: string = ctx.i18n.t('welcome') const isConfigured = !!(fireflyUrl && fireflyAccessToken) diff --git a/src/composers/settings.ts b/src/composers/settings.ts index d1eac2a..874eaad 100644 --- a/src/composers/settings.ts +++ b/src/composers/settings.ts @@ -8,7 +8,6 @@ import type { MyContext } from '../types/MyContext' import i18n, { getLanguageIcon } from '../lib/i18n'; import { command } from '../lib/constants' import { createMainKeyboard, generateWelcomeMessage } from './helpers' -import { getUserStorage } from '../lib/storage' import firefly from '../lib/firefly' import { AccountTypeFilter } from '../lib/firefly/model/account-type-filter' import { AccountRead } from '../lib/firefly/model/account-read' @@ -55,13 +54,12 @@ bot.use(router) export default bot function settingsText(ctx: MyContext) { - const userId = ctx.from!.id const { fireflyUrl, fireflyAccessToken, defaultSourceAccount, language - } = getUserStorage(userId) + } = ctx.session.userSettings // Grab only first 4 and last 4 chars of the token const accessToken = fireflyAccessToken?.replace(/(.{4})(.*?)(.{4})$/, '$1...$3') @@ -108,8 +106,6 @@ async function fireflyAccessTokenRouteHandler(ctx: MyContext) { const log = rootLog.extend('fireflyAccessTokenRouteHandler') log('Entered fireflyAccessTokenRouteHandler...') try { - const userId = ctx.from!.id - const storage = getUserStorage(userId) log('ctx.msg: %O', ctx.msg) const text = ctx.msg!.text as string log('User entered text: %s', text) @@ -122,7 +118,7 @@ async function fireflyAccessTokenRouteHandler(ctx: MyContext) { }) } - storage.fireflyAccessToken = text + ctx.session.userSettings.fireflyAccessToken = text ctx.session.step = 'IDLE' return ctx.reply( @@ -137,8 +133,6 @@ async function fireflyUrlRouteHandler(ctx: MyContext) { const log = rootLog.extend('fireflyUrlRouteHandler') log('Entered fireflyUrlRouteHandler...') try { - const userId = ctx.from!.id - const storage = getUserStorage(userId) log('ctx.msg: %O', ctx.msg) const text = ctx.msg!.text as string log('User entered text: %s', text) @@ -153,7 +147,8 @@ async function fireflyUrlRouteHandler(ctx: MyContext) { }) } - storage.fireflyUrl = text + ctx.session.userSettings.fireflyUrl = text + ctx.session.userSettings.fireflyApiUrl = text ctx.session.step = 'IDLE' return ctx.reply( @@ -200,9 +195,8 @@ async function selectDefaultAssetAccountCbQH(ctx: MyContext) { const log = rootLog.extend('selectDefaultAssetAccountCbQH') log(`Entered the ${SELECT_DEFAULT_ASSET_ACCOUNT} callback query handler`) try { - const userId = ctx.from!.id - const { fireflyUrl, fireflyAccessToken } = getUserStorage(userId) - log('userId: %s', userId) + const userSettings = ctx.session.userSettings + const { fireflyUrl, fireflyAccessToken } = userSettings if (!fireflyUrl) { return ctx.answerCallbackQuery({ @@ -222,7 +216,7 @@ async function selectDefaultAssetAccountCbQH(ctx: MyContext) { }) } - const accounts: AccountRead[] = (await firefly(userId).Accounts.listAccount( + const accounts: AccountRead[] = (await firefly(userSettings).Accounts.listAccount( 1, dayjs().format('YYYY-MM-DD'), AccountTypeFilter.Asset)).data.data log('accounts: %O', accounts) @@ -260,15 +254,15 @@ async function defaultAccountCbQH(ctx: MyContext) { log(`Entered the ${SELECT_DEFAULT_ASSET_ACCOUNT} query handler`) try { log('ctx: %O', ctx) - const userId = ctx.from!.id - const storage = getUserStorage(userId) const accountId = ctx.match![1] log('accountId: %s', accountId) - const account = (await firefly(userId).Accounts.getAccount(accountId)).data.data + const userSettings = ctx.session.userSettings + + const account = (await firefly(userSettings).Accounts.getAccount(accountId)).data.data log('account: %O', account) - storage.defaultSourceAccount = { + userSettings.defaultSourceAccount = { id: accountId.toString(), name: account.attributes.name, type: account.attributes.type @@ -289,8 +283,6 @@ async function cancelCbQH(ctx: MyContext) { const log = rootLog.extend('cancelCbQH') try { log('Cancelling...: ') - const userId = ctx.from!.id - log('userId: %O', userId) ctx.session.step = 'IDLE' @@ -309,8 +301,8 @@ async function testConnectionCbQH(ctx: MyContext) { log('Entered testConnectionCbQH action handler') log('ctx: %O', ctx) try { - const userId = ctx.from!.id - const { fireflyUrl, fireflyAccessToken } = getUserStorage(userId) + const userSettings = ctx.session.userSettings + const { fireflyUrl, fireflyAccessToken } = userSettings if (!fireflyUrl) { return ctx.answerCallbackQuery({ @@ -330,7 +322,7 @@ async function testConnectionCbQH(ctx: MyContext) { }) } - const userInfo = (await firefly(userId).About.getCurrentUser()).data.data + const userInfo = (await firefly(userSettings).About.getCurrentUser()).data.data log('Firefly user info: %O', userInfo) if (!userInfo) return ctx.answerCallbackQuery({ @@ -353,14 +345,12 @@ async function switchLanguageCbQH(ctx: MyContext) { log(`Entered the switch language query handler`) try { log('ctx: %O', ctx) - const userId = ctx.from!.id - const storage = getUserStorage(userId) const language = ctx.match![1] log('language: %O', language) ctx.i18n.locale(language) dayjs.locale(language) - storage.language = language + ctx.session.userSettings.language = language const welcomeMessage = generateWelcomeMessage(ctx) diff --git a/src/composers/transactions/add-transaction.ts b/src/composers/transactions/add-transaction.ts index fe0c513..fbff921 100644 --- a/src/composers/transactions/add-transaction.ts +++ b/src/composers/transactions/add-transaction.ts @@ -4,7 +4,6 @@ import { Composer, InlineKeyboard } from 'grammy' import flatten from 'lodash.flatten' import type { MyContext } from '../../types/MyContext' -import { getUserStorage } from '../../lib/storage' import { addTransactionsMapper as mapper, parseAmountInput, @@ -46,9 +45,8 @@ export async function addTransaction(ctx: MyContext) { const log = rootLog.extend('addTransaction') log('Entered text handler') try { - const userId = ctx.from!.id const text = ctx.message!.text as string - const { fireflyUrl } = getUserStorage(userId) + const { fireflyUrl } = ctx.session.userSettings log('ctx.message.text: %O', text) const validInput = /^(?\d{1,}(?:[.,]\d+)?([-+/*^]\d{1,}(?:[.,]\d+)?)*)*$|(?.+)\s(?\d{1,}(?:[.,]\d+)?([-+/*^]\d{1,}(?:[.,]\d+)?)*)$/ @@ -67,7 +65,7 @@ export async function addTransaction(ctx: MyContext) { parse_mode: 'Markdown' })) - const defaultSourceAccount = await getDefaultSourceAccount(userId) + const defaultSourceAccount = await getDefaultSourceAccount(ctx) log('defaultSourceAccount: %O', defaultSourceAccount) if (!defaultSourceAccount) { @@ -79,7 +77,7 @@ export async function addTransaction(ctx: MyContext) { }) } - const defaultDestinationAccount = await getDefaultDestinationAccount(userId) + const defaultDestinationAccount = await getDefaultDestinationAccount(ctx) log('defaultDestinationAccount: %O', defaultDestinationAccount) // If description is not null, than we'll add transaction in a fast mode @@ -90,7 +88,7 @@ export async function addTransaction(ctx: MyContext) { if (description) { log('Creating quick transaction...') const tr = await createQuickTransaction({ - userId, + ctx, // Telegram message date is a Unix timestamp (10 digits, seconds since the Unix Epoch) date: (ctx.message?.date ? dayjs.unix(ctx.message.date) : dayjs()).toISOString(), amount, @@ -116,7 +114,7 @@ export async function addTransaction(ctx: MyContext) { } const keyboard = await createCategoriesKeyboard( - userId, + ctx, mapper.selectCategory ) log('Got a partial categories keyboard: %O', keyboard.inline_keyboard) @@ -153,12 +151,11 @@ async function newTransactionCategoryCbQH(ctx: MyContext) { log('Entered the newTransactionCategory callback handler...') try { - const userId = ctx.from!.id const categoryId = ctx.match![1] log('categoryId: %s', categoryId) - const defaultSourceAccount = await getDefaultSourceAccount(userId) + const defaultSourceAccount = await getDefaultSourceAccount(ctx) log('defaultSourceAccount: %O', defaultSourceAccount) - const defaultDestinationAccount = await getDefaultDestinationAccount(userId) + const defaultDestinationAccount = await getDefaultDestinationAccount(ctx) log('defaultDestinationAccount: %O', defaultDestinationAccount) const payload = { @@ -175,7 +172,7 @@ async function newTransactionCategoryCbQH(ctx: MyContext) { log('Transaction payload: %O', payload) - const tr = (await firefly(userId).Transactions.storeTransaction(payload)).data.data + const tr = (await firefly(ctx.session.userSettings).Transactions.storeTransaction(payload)).data.data log('Created transaction: %O', tr) ctx.session.newTransaction = {} @@ -210,10 +207,9 @@ async function deleteTransactionActionHandler(ctx: MyContext) { const log = rootLog.extend('deleteTransactionActionHandler') log('Entered deleteTransaction action handler') try { - const userId = ctx.from!.id const trId = ctx.match![1] - if (trId) await firefly(userId).Transactions.deleteTransaction(trId) + if (trId) await firefly(ctx.session.userSettings).Transactions.deleteTransaction(trId) else return ctx.reply( ctx.i18n.t('transactions.add.couldNotDelete', { id: trId }) ) @@ -226,7 +222,7 @@ async function deleteTransactionActionHandler(ctx: MyContext) { } interface ICreateFastTransactionPayload { - userId: number + ctx: MyContext amount: number description: string sourceAccountId: string @@ -234,7 +230,7 @@ interface ICreateFastTransactionPayload { date: string | undefined } -async function createQuickTransaction({ userId, amount, description, sourceAccountId, destinationAccountId, date }: ICreateFastTransactionPayload): Promise { +async function createQuickTransaction({ ctx, amount, description, sourceAccountId, destinationAccountId, date }: ICreateFastTransactionPayload): Promise { const log = rootLog.extend('createFastTransaction') try { const transactionStore = { @@ -247,7 +243,7 @@ async function createQuickTransaction({ userId, amount, description, sourceAccou destination_id: destinationAccountId, }] } - const res = (await firefly(userId).Transactions.storeTransaction(transactionStore)).data.data + const res = (await firefly(ctx.session.userSettings).Transactions.storeTransaction(transactionStore)).data.data log('Created transaction: %O', res) log('Created transaction splits: %O', res.attributes.transactions) @@ -259,13 +255,13 @@ async function createQuickTransaction({ userId, amount, description, sourceAccou } } -async function getDefaultSourceAccount(userId: number): Promise { +async function getDefaultSourceAccount(ctx: MyContext): Promise { const log = rootLog.extend('getDefaultSourceAccount') try { - let { defaultSourceAccount } = getUserStorage(userId) + let { defaultSourceAccount } = ctx.session.userSettings if (!defaultSourceAccount.name) { - const firstAccount = (await firefly(userId).Accounts.listAccount( + const firstAccount = (await firefly(ctx.session.userSettings).Accounts.listAccount( 1, dayjs().format('YYYY-MM-DD'), AccountTypeFilter.Asset)).data.data[0] log('firstAccount: %O', firstAccount) @@ -286,13 +282,13 @@ async function getDefaultSourceAccount(userId: number): Promise(config.botToken) // Attach a session middleware and specify the initial data bot.use( session({ - initial: (): SessionData => ({ - step: 'IDLE', - newTransaction: {}, - editTransaction: {}, - category: {}, - newCategories: [], + getSessionKey, + initial: (): SessionData => ({ ...initialSessionData }), + storage: new FileAdapter({ + dirName: 'sessions', }), }) ) @@ -46,16 +47,15 @@ bot.use(i18n.middleware()); bot.use(requireSettings()) bot.use(cleanup()) bot.use(addTransaction) -bot.use(editTransaction) -bot.use(listTransactions) -bot.use(accounts) +// bot.use(editTransaction) +// bot.use(listTransactions) +// bot.use(accounts) bot.use(settings) -bot.use(categories) +// bot.use(categories) +// bot.use(reports) bot.command(command.START, startHandler) bot.command(command.HELP, helpHandler) -bot.hears(i18n.t('en', 'labels.REPORTS'), ctx => ctx.reply('Coming soon...')) -bot.hears(i18n.t('ru', 'labels.REPORTS'), ctx => ctx.reply('Coming soon...')) bot.on('message:text', textHandler) bot.start() @@ -120,3 +120,19 @@ function errorHandler(err: any) { console.error('Unknown error:', e) } } + +// // Stores data per user. +// function getSessionKey(ctx: any): string | undefined { +// // Give every user their personal session storage +// // (will be shared across groups and in their private chat) +// return ctx.from?.id.toString(); +// } + +// Stores data per user-chat combination. +function getSessionKey(ctx: any): string | undefined { + // Give every user their one personal session storage per chat with the bot + // (an independent session for each group and their private chat) + return ctx.from === undefined || ctx.chat === undefined + ? undefined + : `${ctx.from.id}_${ctx.chat.id}`; +} diff --git a/src/lib/firefly/index.ts b/src/lib/firefly/index.ts index d483037..835341f 100644 --- a/src/lib/firefly/index.ts +++ b/src/lib/firefly/index.ts @@ -4,7 +4,6 @@ import globalAxios from 'axios'; import Debug from 'debug' import { AxiosError, AxiosResponse } from 'axios'; -import { getUserStorage } from '../storage' import { AuthenticationError, HostNotFoundError, @@ -14,12 +13,11 @@ import { const debug = Debug('firefly') -export default function firefly(userId: number) { +export default function firefly(userSettings : { fireflyApiUrl: string, fireflyAccessToken: string }) { const log = debug.extend('index') - const { fireflyApiUrl, fireflyAccessToken } = getUserStorage(userId) const configuration = new Configuration({ - accessToken: fireflyAccessToken, - basePath: fireflyApiUrl.replace(/\/+$/, ""), + accessToken: userSettings.fireflyAccessToken, + basePath: userSettings.fireflyApiUrl.replace(/\/+$/, ""), }) log('configuration: %O', configuration) diff --git a/src/lib/middlewares.ts b/src/lib/middlewares.ts index 4965388..aa69466 100644 --- a/src/lib/middlewares.ts +++ b/src/lib/middlewares.ts @@ -2,7 +2,6 @@ import debug from 'debug' import i18n from './i18n' import { command } from './constants' -import { getUserStorage } from './storage' import type { MyContext } from '../types/MyContext' const rootLog = debug(`bot:mdlwr`) @@ -79,8 +78,7 @@ export function requireSettings() { return next() } - const userId = ctx.from!.id - const { fireflyAccessToken, fireflyUrl } = getUserStorage(userId) + const { fireflyAccessToken, fireflyUrl } = ctx.session.userSettings log('fireflyAccessToken: %O', fireflyAccessToken) log('fireflyUrl: %O', fireflyUrl) diff --git a/src/types/SessionData.ts b/src/types/SessionData.ts index d41263d..b06756a 100644 --- a/src/types/SessionData.ts +++ b/src/types/SessionData.ts @@ -7,8 +7,18 @@ import { Route as EditTransactionRoute } from '../composers/transactions/edit-tr import { TransactionRead } from '../lib/firefly/model/transaction-read' import { TransactionTypeProperty } from '../lib/firefly/model/transaction-type-property' +type Step = 'IDLE' | CategoriesRoute | SettingsRoute | EditTransactionRoute + export interface SessionData { - step: 'IDLE' | CategoriesRoute | SettingsRoute | EditTransactionRoute + step: Step + userSettings: { + fireflyUrl: string + fireflyApiUrl: string + fireflyAccessToken: string + defaultSourceAccount: { id: string, type: string, name: string } + defaultDestinationAccount: { id: string, type: string, name: string } + language: string + }, // transaction: Partial & Partial newTransaction: { type?: TransactionTypeProperty @@ -32,3 +42,18 @@ export interface AccountAttributes { id: string } +export const initialSessionData = { + userSettings: { + fireflyUrl: '', + fireflyApiUrl: '', + fireflyAccessToken: '', + defaultSourceAccount: { id: '', type: '', name: '' }, + defaultDestinationAccount: { id: '', type: '', name: '' }, + language: 'en' + }, + step: 'IDLE' as Step, + newTransaction: {}, + editTransaction: {}, + category: {}, + newCategories: [], +} From 7bc972bdffcb4b72901f2ec625eb2fda986c5f04 Mon Sep 17 00:00:00 2001 From: Alex Sukhov Date: Sat, 11 Feb 2023 14:39:47 +0300 Subject: [PATCH 2/5] Configure persistence for the rest of composers --- src/composers/accounts.ts | 4 +- src/composers/categories.ts | 30 ++++++------- .../transactions/edit-transaction.ts | 45 +++++++++---------- .../transactions/list-transactions.ts | 4 +- src/index.ts | 10 ++--- 5 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/composers/accounts.ts b/src/composers/accounts.ts index 0a6e6ed..1abd2d1 100644 --- a/src/composers/accounts.ts +++ b/src/composers/accounts.ts @@ -32,7 +32,7 @@ async function showAccounts(ctx: MyContext) { log(`Entered showAccounts callback handler...`) try { log('ctx: %O', ctx) - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const isRegularMessage = !!ctx.update.message log('isRegularMessage: %O', isRegularMessage) log('ctx.match: %O', ctx.match) @@ -50,7 +50,7 @@ async function showAccounts(ctx: MyContext) { } log('accType: %O', accType) - const accounts = (await firefly(userId).Accounts.listAccount( + const accounts = (await firefly(userSettings).Accounts.listAccount( page, balanceToDate, accType as AccountTypeFilter)).data.data log('accounts: %O', accounts) diff --git a/src/composers/categories.ts b/src/composers/categories.ts index 80ecd72..cbafcfb 100644 --- a/src/composers/categories.ts +++ b/src/composers/categories.ts @@ -134,12 +134,12 @@ async function doDeleteCategoryCbQH(ctx: MyContext) { const log = rootLog.extend('doDeleteCategoryCbQH') log('Entered the doDeleteCategoryCbQH...') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings log('ctx.match: %O', ctx.match) const categoryId = ctx.match![1] log('categoryId: %O', categoryId) - await firefly(userId).Categories.deleteCategory(categoryId) + await firefly(userSettings).Categories.deleteCategory(categoryId) await ctx.answerCallbackQuery({ text: ctx.i18n.t('categories.deleted') }) return replyWithListOfCategories(ctx) @@ -179,13 +179,13 @@ async function newCategoryNameRouteHandler(ctx: MyContext) { const log = rootLog.extend('newCategoryNameRouteHandler') log('Entered newCategoryNameRouteHandler...') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings log('ctx.session: %O', ctx.session) const text = ctx.msg?.text || '' const categoryId = ctx.session.category.id - await firefly(userId).Categories.updateCategory(categoryId, { name: text }) + await firefly(userSettings).Categories.updateCategory(categoryId, { name: text }) return replyWithListOfCategories(ctx) } catch (err) { console.error(err) @@ -223,11 +223,11 @@ function parseCategoriesInput(input: string) { async function confirmCategoriesCbQH(ctx: MyContext) { const log = rootLog.extend('confirmCategoriesCbQH') try { - log('Creating categories in firefly(userId): %O', ctx.session.newCategories) - const userId = ctx.from!.id + log('Creating categories in firefly: %O', ctx.session.newCategories) + const userSettings = ctx.session.userSettings for (const category of ctx.session.newCategories) { - await firefly(userId).Categories.storeCategory({ name: category }) + await firefly(userSettings).Categories.storeCategory({ name: category }) } await ctx.answerCallbackQuery({ text: 'Категории созданы!' }) @@ -241,8 +241,8 @@ async function replyWithListOfCategories(ctx: MyContext) { const log = rootLog.extend('replyWithListOfCategories') log('ctx: %O', ctx) try { - const userId = ctx.from!.id - const categories = (await firefly(userId).Categories.listCategory()).data.data + const userSettings = ctx.session.userSettings + const categories = (await firefly(userSettings).Categories.listCategory()).data.data const categoriesNames = categories.map((c: any) => c.attributes.name) // log('categories: %O', categories) @@ -273,8 +273,8 @@ async function replyWithListOfCategories(ctx: MyContext) { export async function createCategoriesInlineKeyboard(ctx: MyContext): Promise { const log = rootLog.extend('createCategoriesInlineKeyboard') try { - const userId = ctx.from!.id - const categories = (await firefly(userId).Categories.listCategory()).data.data + const userSettings = ctx.session.userSettings + const categories = (await firefly(userSettings).Categories.listCategory()).data.data const keyboard = new InlineKeyboard() const nowDate = dayjs().format('YYYY-MM-DD') @@ -304,7 +304,7 @@ async function showCategoryDetails(ctx: MyContext) { const log = rootLog.extend('showCategoryDetails') try { await ctx.answerCallbackQuery() - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const categoryId = ctx.match![1] const startDate = ctx.match![2] log('ctx.match: %O', ctx.match) @@ -316,10 +316,10 @@ async function showCategoryDetails(ctx: MyContext) { log('start: %O', start) log('end: %O', end) - const categoryPromise = firefly(userId).Categories.getCategory(categoryId) - const categoryTransactionsPromise = firefly(userId).Categories + const categoryPromise = firefly(userSettings).Categories.getCategory(categoryId) + const categoryTransactionsPromise = firefly(userSettings).Categories .listTransactionByCategory(categoryId, 1, start, end) - const expenseCategoriesPromise = firefly(userId).Insight + const expenseCategoriesPromise = firefly(userSettings).Insight .insightExpenseCategory(start, end, [parseInt(categoryId, 10)]) // Resolve all the promises diff --git a/src/composers/transactions/edit-transaction.ts b/src/composers/transactions/edit-transaction.ts index 840e71c..a3d48f7 100644 --- a/src/composers/transactions/edit-transaction.ts +++ b/src/composers/transactions/edit-transaction.ts @@ -58,14 +58,14 @@ async function showEditTransactionMenu(ctx: MyContext) { // Prevent all router handlers from happening ctx.session.step = 'IDLE' - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const { editTransaction } = ctx.session log('transaction: %O', editTransaction) const trId = ctx.match![1] log('trId: %O', trId) - const tr = (await firefly(userId).Transactions.getTransaction(trId)).data.data + const tr = (await firefly(userSettings).Transactions.getTransaction(trId)).data.data ctx.session.editTransaction = tr @@ -109,13 +109,13 @@ async function showEditTransactionMenu(ctx: MyContext) { async function doneEditTransactionCbQH(ctx: MyContext) { const log = rootLog.extend('doneEditTransactionCbQH') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const trId = ctx.match![1] log('transaction id: %O', trId) await ctx.answerCallbackQuery() - const tr = (await firefly(userId).Transactions.getTransaction(trId)).data.data + const tr = (await firefly(userSettings).Transactions.getTransaction(trId)).data.data return ctx.editMessageText( formatTransaction(ctx, tr), @@ -170,7 +170,7 @@ async function changeAmountRouteHandler(ctx: MyContext) { const log = rootLog.extend('changeAmountRouteHandler') log('Entered change amount route handler') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings log('ctx.session: %O', ctx.session) const text = ctx.msg?.text || '' @@ -202,7 +202,7 @@ async function changeAmountRouteHandler(ctx: MyContext) { await ctx.session.deleteBotsMessage() } - const updatedTr = (await firefly(userId).Transactions.updateTransaction( + const updatedTr = (await firefly(userSettings).Transactions.updateTransaction( tr.id || '', update )).data.data @@ -220,7 +220,7 @@ async function changeDescriptionRouteHandler(ctx: MyContext) { const log = rootLog.extend('changeDescriptionRouteHandler') log('Entered change description route handler') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings log('ctx.session: %O', ctx.session) const description = ctx.msg?.text || '' log('description: %O', description) @@ -249,7 +249,7 @@ async function changeDescriptionRouteHandler(ctx: MyContext) { await ctx.session.deleteBotsMessage() } - const updatedTr = (await firefly(userId).Transactions.updateTransaction( + const updatedTr = (await firefly(userSettings).Transactions.updateTransaction( tr.id || '', update )).data.data @@ -267,18 +267,18 @@ async function assignCategory(ctx: MyContext) { const log = rootLog.extend('assignCategory') log('Entered assignCategory action handler') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const trId = ctx.match![1] log('trId: %O', trId) await ctx.answerCallbackQuery() - const tr = (await firefly(userId).Transactions.getTransaction(trId)).data.data + const tr = (await firefly(userSettings).Transactions.getTransaction(trId)).data.data ctx.session.editTransaction = tr const categoriesKeyboard = await createCategoriesKeyboard( - userId, + ctx, mapper.setCategory ) @@ -308,13 +308,12 @@ async function selectNewCategory(ctx: MyContext) { const log = rootLog.extend('selectNewCategory') log('Entered selectNewCategory action handler') try { - const userId = ctx.from!.id const trId = ctx.match![1] await ctx.answerCallbackQuery() const categoriesKeyboard = await createCategoriesKeyboard( - userId, + ctx, mapper.setCategory ) // If inline_keyboard array does not contain anything, than user has no categories yet @@ -343,13 +342,12 @@ async function selectNewSourceAccount(ctx: MyContext) { const log = rootLog.extend('selectNewSourceAccount') log('Entered selectNewSourceAccount action handler') try { - const userId = ctx.from!.id const trId = ctx.match![1] await ctx.answerCallbackQuery() const accountsKeyboard = await createAccountsKeyboard( - userId, + ctx, [AccountTypeFilter.Asset, AccountTypeFilter.Liabilities], mapper.setSourceAccount ) @@ -372,7 +370,6 @@ async function selectNewDestinationAccount(ctx: MyContext) { const log = rootLog.extend('selectNewDestinationAccount') log('Entered selectNewDestinationAccount action handler') try { - const userId = ctx.from!.id const trId = ctx.match![1] await ctx.answerCallbackQuery() @@ -383,7 +380,7 @@ async function selectNewDestinationAccount(ctx: MyContext) { ] const accountsKeyboard = (await createAccountsKeyboard( - userId, + ctx, accTypeFilters, mapper.setDestinationAccount )) @@ -404,7 +401,7 @@ async function setNewCategory(ctx: MyContext) { const log = rootLog.extend('setNewCategory') log('Entered setNewCategory action handler') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const categoryId = ctx.match![1] log('categoryId: %O', categoryId) @@ -422,7 +419,7 @@ async function setNewCategory(ctx: MyContext) { } log('Transaction update: %O', update) - const updatedTr = (await firefly(userId).Transactions.updateTransaction( + const updatedTr = (await firefly(userSettings).Transactions.updateTransaction( tr.id || '', update )).data.data @@ -441,7 +438,7 @@ async function setNewSourceAccount(ctx: MyContext) { const log = rootLog.extend('setNewSourceAccount') log('Entered setNewSourceAccount action handler') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const sourceAccountId = ctx.match![1] log('sourceAccountId: %O', sourceAccountId) @@ -462,7 +459,7 @@ async function setNewSourceAccount(ctx: MyContext) { // When we change the source account of the transaction, we also want to change the // currency of the transaction to match the newly set source account. Otherwise // the transaction would have the original account's currency. - const sourceAccountData = (await firefly(userId).Accounts.getAccount(sourceAccountId)).data.data + const sourceAccountData = (await firefly(userSettings).Accounts.getAccount(sourceAccountId)).data.data log('sourceAccountData: %O', sourceAccountData) const update = { @@ -473,7 +470,7 @@ async function setNewSourceAccount(ctx: MyContext) { } // Proceed with updating the transaction - const tr = (await firefly(userId).Transactions.updateTransaction(trId, update)).data.data + const tr = (await firefly(userSettings).Transactions.updateTransaction(trId, update)).data.data return ctx.editMessageText( formatTransaction(ctx, tr), @@ -489,7 +486,7 @@ async function setNewDestinationAccount(ctx: MyContext) { const log = rootLog.extend('setNewDestinationAccount') log('Entered setNewDestinationAccount action handler') try { - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const destId = ctx.match![1] log('destId: %O', destId) @@ -497,7 +494,7 @@ async function setNewDestinationAccount(ctx: MyContext) { const trId = ctx.session.editTransaction.id || '' log('trId: %O', trId) - const tr = (await firefly(userId).Transactions.updateTransaction( + const tr = (await firefly(userSettings).Transactions.updateTransaction( trId, { transactions: [{ destination_id: destId }]} )).data.data diff --git a/src/composers/transactions/list-transactions.ts b/src/composers/transactions/list-transactions.ts index e40c680..0328209 100644 --- a/src/composers/transactions/list-transactions.ts +++ b/src/composers/transactions/list-transactions.ts @@ -31,7 +31,7 @@ async function showTransactions(ctx: MyContext) { log(`Entered showTransactions callback handler...`) try { // await ctx.answerCallbackQuery() - const userId = ctx.from!.id + const userSettings = ctx.session.userSettings const isRegularMessage = !!ctx.update.message log('isRegularMessage: %O', isRegularMessage) log('ctx.match: %O', ctx.match) @@ -56,7 +56,7 @@ async function showTransactions(ctx: MyContext) { log('start: %O', start) log('end: %O', end) - const transactions = (await firefly(userId).Transactions.listTransaction( + const transactions = (await firefly(userSettings).Transactions.listTransaction( page, start, end, diff --git a/src/index.ts b/src/index.ts index 7fabf64..cedce09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,6 @@ import editTransaction from './composers/transactions/edit-transaction' import listTransactions from './composers/transactions/list-transactions' import accounts from './composers/accounts' import categories from './composers/categories' -import reports from './composers/reports' import type { MyContext } from './types/MyContext' import type { SessionData } from './types/SessionData' @@ -47,12 +46,11 @@ bot.use(i18n.middleware()); bot.use(requireSettings()) bot.use(cleanup()) bot.use(addTransaction) -// bot.use(editTransaction) -// bot.use(listTransactions) -// bot.use(accounts) +bot.use(editTransaction) +bot.use(listTransactions) +bot.use(accounts) bot.use(settings) -// bot.use(categories) -// bot.use(reports) +bot.use(categories) bot.command(command.START, startHandler) bot.command(command.HELP, helpHandler) From 22370e7e1f786c34fe3188532a18d53983597b3f Mon Sep 17 00:00:00 2001 From: Alex Sukhov Date: Sat, 11 Feb 2023 15:01:53 +0300 Subject: [PATCH 3/5] Delete storage.ts file --- src/lib/storage.ts | 77 ---------------------------------------------- 1 file changed, 77 deletions(-) delete mode 100644 src/lib/storage.ts diff --git a/src/lib/storage.ts b/src/lib/storage.ts deleted file mode 100644 index dc26dca..0000000 --- a/src/lib/storage.ts +++ /dev/null @@ -1,77 +0,0 @@ -import config from '../config' -import debug from 'debug' - -import { AccountAttributes } from '../types/SessionData' - -const rootLog = debug(`bot:storage`) -const allowedLanguages = ['ru', 'en'] - -class UserSettings { - _fireflyUrl = '' - _fireflyApiUrl = '' - _fireflyAccessToken = '' - _defaultSourceAccount = { id: '', type: '', name: '' } - _defaultDestinationAccount = { id: '', type: '', name: '' } - _language = 'en' - - constructor({ - fireflyUrl = config.fireflyUrl, - fireflyApiUrl = config.fireflyApiUrl, - fireflyAccessToken = '' - }) { - this._fireflyUrl = fireflyUrl - this._fireflyApiUrl = fireflyApiUrl || fireflyUrl - this._fireflyAccessToken = fireflyAccessToken - } - - get fireflyUrl() { return this._fireflyUrl } - set fireflyUrl(val: string) { this._fireflyUrl = val } - - get fireflyApiUrl() { return this._fireflyApiUrl } - set fireflyApiUrl(val: string) { this._fireflyApiUrl = val } - - get fireflyAccessToken() { return this._fireflyAccessToken } - set fireflyAccessToken(val: string) { this._fireflyAccessToken = val } - - get defaultSourceAccount() { return this._defaultSourceAccount } - set defaultSourceAccount(val: AccountAttributes) { this._defaultSourceAccount = val } - - get defaultDestinationAccount() { return this._defaultDestinationAccount } - set defaultDestinationAccount(val: AccountAttributes) { this._defaultDestinationAccount = val } - - get language() { return this._language } - set language(val: string) { - if (allowedLanguages.includes(val as 'en' | 'ru')) this._language = val - } -} - -type UserStorage = { - [key: number]: UserSettings -} - -const userStorage: UserStorage = { } as UserStorage - -export function getUserStorage(userId: number): UserSettings { - return userStorage[userId] || bootstrapUserStorage(userId) -} - -function bootstrapUserStorage(userId: number): UserSettings { - const log = rootLog.extend('bootstrapUserStorage') - log('userId: %O', userId) - let userSettings: UserSettings - - if (config.userId && config.userId === userId) { - userSettings = new UserSettings({ - fireflyAccessToken: config.fireflyAccessToken - }) - } else if (config.userId && config.userId !== userId) { - log('⚠️ WARNING! You provided `TG_USER_ID (%s)` via .env file which does not match the current Telegram userId (%s), therefore the user config will be reset.', config.userId, userId) - userSettings = new UserSettings({}) - } else { - userSettings = new UserSettings({}) - } - - userStorage[userId] = userSettings - log('userStorage[userId]: %O', userStorage[userId]) - return userStorage[userId] -} From aafc2c197038c12bbbc6da273efd48c3b01f393f Mon Sep 17 00:00:00 2001 From: Alex Sukhov Date: Sat, 11 Feb 2023 15:08:26 +0300 Subject: [PATCH 4/5] Bump version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d829b69..8b7af1b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "firefly-iii-telegram-bot", "description": "A Telegram bot for working with Firefly III with a supersonic speed", - "version": "1.0.2", + "version": "1.1.0", "homepage": "https://github.com/cyxou/firefly-iii-telegram-bot#readme", "license": "GPL-3.0-or-later", "repository": { From 731c428932b71a305fb82d5e2f01fda933d741f0 Mon Sep 17 00:00:00 2001 From: Alex Sukhov Date: Sat, 11 Feb 2023 15:10:38 +0300 Subject: [PATCH 5/5] Cleanup a bit --- src/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index cedce09..a183bc1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -119,13 +119,6 @@ function errorHandler(err: any) { } } -// // Stores data per user. -// function getSessionKey(ctx: any): string | undefined { -// // Give every user their personal session storage -// // (will be shared across groups and in their private chat) -// return ctx.from?.id.toString(); -// } - // Stores data per user-chat combination. function getSessionKey(ctx: any): string | undefined { // Give every user their one personal session storage per chat with the bot