diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 6b34bc0..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - }, - extends: 'airbnb-base', - overrides: [ - ], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - }, - rules: { - }, - ignorePatterns: ['**/*.test.js', 'node_modules/', 'coverage'], -}; diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..a78bd92 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,23 @@ +--- + +plugins: + - jest + +# https://eslint.org/docs/user-guide/configuring#specifying-environments +env: + node: true + +extends: + - 'airbnb-base' + - 'plugin:jest/recommended' + +parserOptions: + ecmaVersion: latest + +rules: + no-console: 0 + import/extensions: + - error + - ignorePackages + - js: always + no-underscore-dangle: [2, { "allow": ["__filename", "__dirname"] }] diff --git a/.npmrc b/.npmrc index d75b0f7..62b62c1 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -node-options=--experimental-vm-modules +node-options=--no-warnings --experimental-vm-modules diff --git a/bin/bot.js b/bin/bot.js index e01c2cc..820bfed 100644 --- a/bin/bot.js +++ b/bin/bot.js @@ -1,22 +1,20 @@ #!/usr/bin/env node --env-file=.env import { Bot } from 'grammy'; - -const getIntroMessage = (type) => { - const welcomeMessage = "Hi! Let me introduce you 'Bity Smarty' – a special bot that can provide a healthy diet and a grocery list for your next shopping.\n\n"; - const featureMessage = 'Here is 5 main features of this bot:\n' - + '1. Save your time: Only 1 hour for cooking per 3 day!\n' - + '2. No complex equipment. Just a multi cooker to start!\n' - + '3. Healthy diet with fancy recipes that looks great\n' - + '4. Most recipes can be easily stored in the fridge or in the freezer\n' - + '5. And to make it even tastier – It is completely free :)'; - return (type === 'welcome' ? welcomeMessage : featureMessage); -}; +import { getIntroMessage, provideMenuWithGroceryList } from '../src/index.js'; +import { basicCookBook } from '../src/basicCookBook.js'; const bot = new Bot(process.env.BOT_TOKEN); -bot.onText(/\/start/, (msg) => { - const chatId = msg.chat.id; - const userId = msg.from.id; +bot.command('start', async (ctx) => { + await ctx.reply(getIntroMessage('welcome')); + await ctx.reply(getIntroMessage('features')); +}); + +bot.command('menu', async (ctx) => { + const menuWithGroceryList = provideMenuWithGroceryList(basicCookBook); - bot.sendMessage(chatId, `ID: ${userId}. Chat ID: ${chatId}\n\n${getIntroMessage('welcome')}`).then(() => true); + await ctx.reply(menuWithGroceryList.menuText); + await ctx.reply(menuWithGroceryList.groceryListText); }); + +bot.start().then((r) => r).catch((e) => e); diff --git a/nodemon.json b/nodemon.json index 17e2e00..0b16719 100644 --- a/nodemon.json +++ b/nodemon.json @@ -4,6 +4,5 @@ "src/", ".env" ], - "ext": ".js", - "exec": "node --env-file=.env bin/index.js" + "ext": ".js" } diff --git a/package-lock.json b/package-lock.json index 7e37b92..cf6f71e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "eslint": "^8.55.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.29.0", + "eslint-plugin-jest": "^27.6.0", "husky": "^8.0.0", "jest": "^29.7.0", "nodemon": "^3.0.1", @@ -1341,6 +1342,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -1356,6 +1363,12 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1377,6 +1390,194 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -1539,6 +1740,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", @@ -2150,6 +2360,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2481,6 +2703,31 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-jest": { + "version": "27.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz", + "integrity": "sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -2643,6 +2890,34 @@ "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-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "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", @@ -2930,6 +3205,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -4352,6 +4647,15 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "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/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -4794,6 +5098,15 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -5599,6 +5912,27 @@ "node": ">=4" } }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5697,6 +6031,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 90bb530..ae23cee 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "eslint": "^8.55.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.29.0", + "eslint-plugin-jest": "^27.6.0", "husky": "^8.0.0", "jest": "^29.7.0", "nodemon": "^3.0.1", diff --git a/src/basicCookBook.js b/src/basicCookBook.js index 2ab5920..c8d458f 100644 --- a/src/basicCookBook.js +++ b/src/basicCookBook.js @@ -1,19 +1,21 @@ +// Basic meal list +const mealTypes = ['breakfast', 'snack', 'lunch', 'afternoonSnack', 'dinner']; // Recipe: Less than 7 ingredients, less than 20 minutes cooking, wide-spread products // Breakfasts const peanutOvernightOatsBreakfast = { name: 'Peanut Butter & Jelly Overnight Oats', meal: ['breakfast'], ingredients: [ - ['rolled oats', 50], - ['milk', 100], - ['greek yogurt', 50], - ['cha seeds', 5], - ['date syrup', 15], - ['vanilla extract', 3], // End of basic ingredients - ['strawberry jam', 15], - ['creamy peanut butter', 15], - ['strawberry', 50], - ['peanuts', 40], + { name: 'rolled oats', amount: 50, unit: 'g' }, + { name: 'milk', amount: 100, unit: 'ml' }, + { name: 'greek yogurt', amount: 50, unit: 'g' }, + { name: 'cha seeds', amount: 5, unit: 'g' }, + { name: 'date syrup', amount: 15, unit: 'ml' }, + { name: 'vanilla extract', amount: 3, unit: 'ml' }, // Base ingredients + { name: 'strawberry jam', amount: 15, unit: 'ml' }, + { name: 'creamy peanut butter', amount: 15, unit: 'g' }, + { name: 'strawberry', amount: 50, unit: 'g' }, + { name: 'peanuts', amount: 40, unit: 'g' }, ], recipe: [ 'Combine old fashioned oats, seeds, yoghurt and vanilla extract, sweetener and milk (it is convenient to make it in a glass) with all basic ingredients', @@ -35,16 +37,16 @@ const appleOvernightOatsBreakfast = { name: 'Apple pie Overnight Oats', meal: ['breakfast'], ingredients: [ - ['rolled oats', 50], - ['milk', 100], - ['greek yogurt', 50], - ['cha seeds', 5], - ['date syrup', 15], - ['vanilla extract', 3], // End of basic ingredients - ['apple', 100], - ['pecan', 20], - ['maple syrup', 40], - ['cinnamon', 5], + { name: 'rolled oats', amount: 50, unit: 'g' }, + { name: 'milk', amount: 100, unit: 'ml' }, + { name: 'greek yogurt', amount: 50, unit: 'g' }, + { name: 'cha seeds', amount: 5, unit: 'g' }, + { name: 'date syrup', amount: 15, unit: 'ml' }, + { name: 'vanilla extract', amount: 3, unit: 'ml' }, + { name: 'apple', amount: 100, unit: 'g' }, + { name: 'pecan', amount: 20, unit: 'g' }, + { name: 'maple syrup', amount: 40, unit: 'ml' }, + { name: 'cinnamon', amount: 5, unit: 'g' }, ], recipe: [ 'Combine old fashioned oats, seeds, yoghurt and vanilla extract, sweetener and milk (it is convenient to make it in a glass) with all basic ingredients', @@ -66,16 +68,16 @@ const bananaOvernightOatsBreakfast = { name: 'Banana & Nutella Overnight Oats', meal: ['breakfast'], ingredients: [ - ['rolled oats', 50], - ['milk', 100], - ['greek yogurt', 50], - ['cha seeds', 5], - ['date syrup', 15], - ['vanilla extract', 3], // End of basic ingredients - ['banana', 60], - ['nutella', 20], - ['hazelnuts', 20], - ['chocolate', 20], + { name: 'rolled oats', amount: 50, unit: 'g' }, + { name: 'milk', amount: 100, unit: 'ml' }, + { name: 'greek yogurt', amount: 50, unit: 'g' }, + { name: 'cha seeds', amount: 5, unit: 'g' }, + { name: 'date syrup', amount: 15, unit: 'ml' }, + { name: 'vanilla extract', amount: 3, unit: 'ml' }, + { name: 'banana', amount: 60, unit: 'g' }, + { name: 'nutella', amount: 20, unit: 'g' }, + { name: 'hazelnuts', amount: 20, unit: 'g' }, + { name: 'chocolate', amount: 20, unit: 'g' }, ], cookingTimeInMinutes: 10, nutrients: { @@ -97,7 +99,9 @@ const bananaOvernightOatsBreakfast = { const bambaSnack = { name: 'Bamba', meal: ['snack'], - ingredients: [['bamba', 80]], + ingredients: [ + { name: 'bamba', amount: 80, unit: 'g' }, + ], nutrients: { calories: 427, carbohydrates: 39.2, @@ -113,7 +117,9 @@ const bambaSnack = { const peanutsSnack = { name: 'Fried Peanuts', meal: ['snack'], - ingredients: [['peanuts', 44]], + ingredients: [ + { name: 'peanuts', amount: 44, unit: 'g' }, + ], nutrients: { calories: 237, carbohydrates: 7.1, @@ -131,14 +137,14 @@ const chickenSaladLunch = { name: 'Light Chicken Salad', meal: ['lunch'], ingredients: [ - ['cooked chicken breast', 200], - ['mixed greens (spinach, lettuce, arugula)', 150], - ['cherry tomatoes, halved', 100], - ['cucumber, sliced', 80], - ['red onion, thinly sliced', 50], - ['olive oil', 15], - ['lemon juice', 10], - ['salt and pepper, to taste'], + { name: 'cooked chicken breast', amount: 200, unit: 'g' }, + { name: 'mixed greens (spinach, lettuce, arugula)', amount: 150, unit: 'g' }, + { name: 'cherry tomatoes, halved', amount: 100, unit: 'g' }, + { name: 'cucumber, sliced', amount: 80, unit: 'g' }, + { name: 'red onion, thinly sliced', amount: 50, unit: 'g' }, + { name: 'olive oil', amount: 15, unit: 'ml' }, + { name: 'lemon juice', amount: 10, unit: 'ml' }, + { name: 'salt and pepper, to taste', amount: undefined }, // Поскольку не указано количество, оно будет undefined ], cookingTimeInMinutes: 15, nutrients: { @@ -160,13 +166,13 @@ const chickenAvocadoToastLunch = { name: 'Chicken Avocado Toast', meal: ['lunch'], ingredients: [ - ['cooked chicken breast, shredded', 150], - ['avocado, mashed', 1], - ['whole grain bread slices', 2], - ['cherry tomatoes, sliced', 50], - ['fresh basil leaves', 10], - ['olive oil', 10], - ['salt and pepper, to taste'], + { name: 'cooked chicken breast, shredded', amount: 150, unit: 'g' }, + { name: 'avocado, mashed', amount: 1, unit: 'unit' }, + { name: 'whole grain bread slices', amount: 2, unit: 'unit' }, + { name: 'cherry tomatoes, sliced', amount: 50, unit: 'g' }, + { name: 'fresh basil leaves', amount: 10, unit: 'g' }, + { name: 'olive oil', amount: 10, unit: 'ml' }, + { name: 'salt and pepper, to taste', amount: undefined }, // Поскольку не указано количество, оно будет undefined ], cookingTimeInMinutes: 15, nutrients: { @@ -191,9 +197,9 @@ const lemonPossetAfternoonSnack = { name: 'Lemon Posset', meal: ['afternoonSnack'], ingredients: [ - ['lemon', 1], - ['sugar', 50], - ['heavy cream (33%)', 200], + { name: 'lemon', amount: 1, unit: 'unit' }, + { name: 'sugar', amount: 50, unit: 'g' }, + { name: 'heavy cream (33%)', amount: 200, unit: 'ml' }, ], nutrients: { calories: 215.8, @@ -217,15 +223,15 @@ const grilledSalmonDinner = { name: 'Grilled Salmon with Vegetables', meal: ['dinner'], ingredients: [ - ['salmon fillet', 150], - ['asparagus spears', 100], - ['bell peppers (assorted colors), sliced', 150], - ['zucchini, sliced', 100], - ['olive oil', 15], - ['garlic powder', 5], - ['lemon zest', 5], - ['salt', 2], - ['pepper', 2], + { name: 'salmon fillet', amount: 150, unit: 'g' }, + { name: 'asparagus spears', amount: 100, unit: 'g' }, + { name: 'bell peppers (assorted colors), sliced', amount: 150, unit: 'g' }, + { name: 'zucchini, sliced', amount: 100, unit: 'g' }, + { name: 'olive oil', amount: 15, unit: 'ml' }, + { name: 'garlic powder', amount: 5, unit: 'g' }, + { name: 'lemon zest', amount: 5, unit: 'g' }, + { name: 'salt', amount: 2, unit: 'g' }, + { name: 'pepper', amount: 2, unit: 'g' }, ], cookingTimeInMinutes: 20, nutrients: { @@ -244,52 +250,74 @@ const grilledSalmonDinner = { storageTimeInHours: 48, }; -const panFriedCodDinner = { - name: 'Pan-Fried Cod with Lemon Sauce', +const onePotChickenAndRice = { + name: 'One Pot Chicken and Rice', meal: ['dinner'], ingredients: [ - ['cod fillet', 200], - ['all-purpose flour', 50], - ['olive oil', 15], - ['garlic cloves, minced', 2], - ['chicken or vegetable broth', 150], - ['lemon juice', 30], - ['fresh parsley, chopped', 10], - ['salt and pepper, to taste'], + { name: 'paprika', amount: 0.5, unit: 'tsp' }, + { name: 'dried oregano', amount: 0.25, unit: 'tsp' }, + { name: 'dried thyme', amount: 0.25, unit: 'tsp' }, + { name: 'garlic powder', amount: 0.13, unit: 'tsp' }, + { name: 'onion powder', amount: 0.13, unit: 'tsp' }, + { name: 'salt', amount: 0.06, unit: 'tsp' }, + { name: 'pepper', amount: 0.06, unit: 'tsp' }, + { name: 'boneless, skinless chicken thighs', amount: 0.31, unit: 'lbs' }, + { + name: 'cooking oil', amount: 0.5, unit: 'Tbsp', divided: true, + }, + { + name: 'yellow onion', amount: 0.25, unit: 'unit', diced: true, + }, + { + name: 'long-grain white rice', amount: 0.25, unit: 'cup', uncooked: true, + }, + { name: 'vegetable broth', amount: 0.44, unit: 'cups' }, + { + name: 'chopped parsley', amount: 0.25, unit: 'Tbsp', optional: true, garnish: true, + }, ], - cookingTimeInMinutes: 20, + instructions: [ + 'Combine the paprika, oregano, thyme, garlic powder, onion powder, salt, and pepper in a small bowl.', + 'Coat both sides of the chicken thighs in the seasoning mix.', + 'Add 0.5 Tbsp cooking oil to a deep skillet and heat over medium. Once hot, swirl to coat the surface of the skillet, then add the chicken thighs.', + 'Cook the thighs for a few minutes on each side, or until well browned. The chicken does not need to be cooked through at this point.', + 'Remove the browned chicken to a clean plate.', + 'Reduce the heat to medium-low, add an additional 0.5 Tbsp cooking oil to the skillet, then add the diced onion. Sauté the onion for about 5 minutes, or until softened. Allow the moisture from the onion to dissolve the browned bits from the skillet as you stir.', + 'Add the uncooked rice to the skillet and continue to sauté for 1-2 minutes more to toast the rice.', + 'Add the vegetable broth to the skillet and briefly stir to dissolve any remaining brown bits from the bottom of the skillet.', + 'Return the chicken to the skillet, setting it on top of the rice. Place a lid on the skillet, turn the heat up to medium-high, and allow the broth to come up to a full boil.', + 'Once boiling, turn the heat down to low and let the chicken and rice continue to simmer over low, without lifting the lid or stirring, for 20 minutes.', + 'After 20 minutes, turn off the heat and let it rest, without lifting the lid, for an additional 5 minutes.', + 'Finally, remove the lid and fluff the rice around the chicken. Garnish with chopped parsley, if desired, then serve and enjoy!', + ], + cookingTimeInMinutes: 40, nutrients: { - calories: 320, - carbohydrates: 10, - protein: 35, - fat: 15, - sodium: 0.5, + servingSize: '1 serving', + calories: 421, + carbohydrates: 42, + protein: 31, + fat: 13, + sodium: 688, + fiber: 2, }, - recipe: [ - 'Season the cod fillet with salt and pepper. Dredge it in flour, shaking off excess.', - 'Heat olive oil in a skillet over medium-high heat. Add the cod fillet and cook for 3-4 minutes per side, or until golden brown and cooked through. Remove from the skillet and set aside.', - 'In the same skillet, add minced garlic and cook until fragrant. Pour in the broth and lemon juice. Bring to a simmer and let it cook for 2-3 minutes, until the sauce thickens slightly.', - 'Return the cod fillet to the skillet. Spoon the lemon sauce over the fish. Garnish with chopped parsley.', - 'Serve hot with your favorite side dishes, or refrigerate for later consumption.', - ], - storageTimeInHours: 48, + storageTimeInHours: 72, }; const tunaPastaDinner = { name: 'Tuna Pasta with Tomato Sauce', meal: ['dinner'], ingredients: [ - ['canned tuna in water, drained', 200], - ['spaghetti or your favorite pasta', 150], - ['olive oil', 15], - ['garlic clove, minced', 1], - ['canned crushed tomatoes', 200], - ['dried basil', 3], - ['dried oregano', 3], - ['red pepper flakes, optional', 2], - ['salt and black pepper', 2], - ['fresh parsley, chopped (for garnish)', 30], - ['grated Parmesan cheese (optional, for serving)', 30], + { name: 'canned tuna in water, drained', amount: 200, unit: 'g' }, + { name: 'spaghetti or your favorite pasta', amount: 150, unit: 'g' }, + { name: 'olive oil', amount: 15, unit: 'ml' }, + { name: 'garlic clove, minced', amount: 1, unit: 'unit' }, + { name: 'canned crushed tomatoes', amount: 200, unit: 'g' }, + { name: 'dried basil', amount: 3, unit: 'g' }, + { name: 'dried oregano', amount: 3, unit: 'g' }, + { name: 'red pepper flakes, optional', amount: 2, unit: 'g' }, + { name: 'salt and black pepper', amount: 2, unit: 'g' }, + { name: 'fresh parsley, chopped (for garnish)', amount: 30, unit: 'g' }, + { name: 'grated Parmesan cheese (optional, for serving)', amount: 30, unit: 'g' }, ], cookingTimeInMinutes: 20, nutrients: { @@ -320,9 +348,12 @@ const basicCookBook = [ lemonPossetAfternoonSnack, chickenSaladLunch, grilledSalmonDinner, + onePotChickenAndRice, chickenAvocadoToastLunch, - panFriedCodDinner, tunaPastaDinner, ]; -export default basicCookBook; +export { + mealTypes, + basicCookBook, +}; diff --git a/src/index.js b/src/index.js index 5dd92e8..7ba314c 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1,80 @@ +import { mealTypes } from './basicCookBook.js'; +import capitalize from './utils.js'; // Get welcome messages +const getIntroMessage = (type) => { + const welcomeMessage = "Hi! Let me introduce you 'Bity Smarty' – a special bot that can provide a healthy diet and a grocery list for your next shopping.\n\n"; + const featureMessage = 'Here is 5 main features of this bot:\n' + + '1. Save your time: Only 1 hour for cooking per 3 day!\n' + + '2. No complex equipment. Just a multi cooker to start!\n' + + '3. Healthy diet with fancy recipes that looks great\n' + + '4. Most recipes can be easily stored in the fridge or in the freezer\n' + + '5. And to make it even tastier – It is completely free :)'; + return (type === 'welcome' ? welcomeMessage : featureMessage); +}; + +// Menu +const generateMenu = (recipesList) => { + const getRecipesByMealType = (recipes, mealType) => recipes + .filter((recipe) => recipe.meal.includes(mealType)); + const getRandomArrayIndex = (array) => Math.floor(Math.random() * array.length); + const getRandomRecipe = ( + recipesByMealType, + ) => recipesByMealType[getRandomArrayIndex(recipesByMealType)]; + + const recipesByMealType = mealTypes.map((meal) => getRecipesByMealType(recipesList, meal)); + const randomMenu = recipesByMealType.flatMap((meal) => getRandomRecipe(meal)); + + return mealTypes.reduce((acc, mealType, currentIndex) => ( + { ...acc, [mealType]: randomMenu[currentIndex] } + ), {}); +}; + +const formatMenu = (menu) => { + const getMenuLine = (meal, menuVariant) => `${capitalize(meal)}: ${menuVariant[meal].name}`; + const menuHeader = 'Menu\n\n'; + const menuText = mealTypes + .map((meal) => getMenuLine(meal, menu)) + .join('\n'); + return `${menuHeader}${menuText}`; +}; + +// Grocery List +const generateGroceryList = (currentMenu) => { + const ingredientDuplicated = Object.entries(currentMenu).flatMap(([, meal]) => meal.ingredients); + return ingredientDuplicated.reduce((acc, currentValue) => { + acc[currentValue.name] = { + amount: (acc[currentValue.name] ?? 0) + currentValue.amount, + unit: currentValue.unit, + }; + return acc; + }, {}); +}; + +const formatGroceryList = (currentGroceryList) => { + const getGroceryListLine = (name, amount, unit) => `– ${capitalize(name)}: ${amount}${unit}`; + const ingredientsList = Object.entries(currentGroceryList); + const groceryListHeader = 'Grocery List\n\n'; + const groceryListText = ingredientsList + .map( + ( + [name, { amount, unit }], + ) => getGroceryListLine(name, amount, unit), + ) + .join('\n'); + return `${groceryListHeader}${groceryListText}`; +}; + +// Result +const provideMenuWithGroceryList = (recipesList) => { + const newMenu = generateMenu(recipesList); + const groceryList = generateGroceryList(newMenu); + return { + menuText: formatMenu(newMenu), + groceryListText: formatGroceryList(groceryList), + }; +}; + +export { + getIntroMessage, + provideMenuWithGroceryList, +}; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..0acd54d --- /dev/null +++ b/src/utils.js @@ -0,0 +1,3 @@ +const capitalize = (s) => s && s[0].toUpperCase() + s.slice(1); + +export default capitalize;