{"id":18062114,"url":"https://github.com/feliksnovoselskyi/quizizzbot","last_synced_at":"2025-07-24T03:05:46.212Z","repository":{"id":256479937,"uuid":"855434379","full_name":"FeliksNovoselskyi/QuizizzBOT","owner":"FeliksNovoselskyi","description":"Quizizz infrastructure, but on Telegram. Create your own quiz on the platform, and upload it to the bot, running a quiz for your students through it","archived":false,"fork":false,"pushed_at":"2025-01-23T23:22:51.000Z","size":13004,"stargazers_count":11,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-11T15:10:34.114Z","etag":null,"topics":["bot","ejs","express","javascript","javascript-framework","nodejs","open-source","quiz","quiz-app","sequelize-orm","telegram","telegram-bot"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FeliksNovoselskyi.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-10T21:27:20.000Z","updated_at":"2025-03-05T21:51:41.000Z","dependencies_parsed_at":"2024-09-17T23:46:06.555Z","dependency_job_id":"8b630f62-29e5-417b-8b1c-6d088eed9074","html_url":"https://github.com/FeliksNovoselskyi/QuizizzBOT","commit_stats":null,"previous_names":["feliksnovoselskyi/quizizzbot"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/FeliksNovoselskyi/QuizizzBOT","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FeliksNovoselskyi%2FQuizizzBOT","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FeliksNovoselskyi%2FQuizizzBOT/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FeliksNovoselskyi%2FQuizizzBOT/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FeliksNovoselskyi%2FQuizizzBOT/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FeliksNovoselskyi","download_url":"https://codeload.github.com/FeliksNovoselskyi/QuizizzBOT/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FeliksNovoselskyi%2FQuizizzBOT/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266786798,"owners_count":23983871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-24T02:00:09.469Z","response_time":99,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bot","ejs","express","javascript","javascript-framework","nodejs","open-source","quiz","quiz-app","sequelize-orm","telegram","telegram-bot"],"created_at":"2024-10-31T05:05:42.214Z","updated_at":"2025-07-24T03:05:46.197Z","avatar_url":"https://github.com/FeliksNovoselskyi.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Quizizz Telegram Bot\n\u003cimg width=\"100%\" height=\"auto\" src=\"readme_files/README HEADER.png\"\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Node.js-v16.20.0-lightgrey?style=flat\u0026logo=Node.js\" alt=\"Node.js\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/stars/FeliksNovoselskyi/QuizizzBOT?style=social\" alt=\"GitHub Repo stars\"\u003e\n\u003c/p\u003e\n\n\n---\n![Node.js](https://img.shields.io/badge/Node.js-339933?style=for-the-badge\u0026logo=nodedotjs\u0026logoColor=white)\n![node-telegram-bot-api](https://img.shields.io/badge/Node--Telegram--Bot--API-2CA5E0?style=for-the-badge\u0026logo=telegram\u0026logoColor=white)\n![Express](https://img.shields.io/badge/Express.js-000000?style=for-the-badge\u0026logo=express\u0026logoColor=white)\n![EJS](https://img.shields.io/badge/EJS-87C918?style=for-the-badge\u0026logo=ejs\u0026logoColor=white)\n![Sequelize](https://img.shields.io/badge/Sequelize-52B0E7?style=for-the-badge\u0026logo=sequelize\u0026logoColor=white)\n![SQLite](https://img.shields.io/badge/SQLite-003B57?style=for-the-badge\u0026logo=sqlite\u0026logoColor=white)\n![JavaScript ES6](https://img.shields.io/badge/JavaScript-ES6-F7DF1E?style=for-the-badge\u0026logo=javascript\u0026logoColor=black)\n![Bootstrap 5](https://img.shields.io/badge/Bootstrap%205-563D7C?style=for-the-badge\u0026logo=bootstrap\u0026logoColor=white)\n![jQuery](https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge\u0026logo=jquery\u0026logoColor=white)\n![AJAX](https://img.shields.io/badge/AJAX-00A7E1?style=for-the-badge\u0026logo=ajax\u0026logoColor=white)\n![Sortable.js](https://img.shields.io/badge/Sortable.js-7E7E7E?style=for-the-badge\u0026logo=javascript\u0026logoColor=white)\n![Async](https://img.shields.io/badge/Async-Enabled-007acc?style=for-the-badge\u0026logo=javascript)\n![Contributions Welcome](https://img.shields.io/badge/Contributions-Welcome-brightgreen?style=for-the-badge\u0026logo=github\u0026logoColor=white)\n![Open Source](https://img.shields.io/badge/Open%20Source-%E2%9D%A4-red?style=for-the-badge)\n\n## About\nA ***two-part*** project\n\n**Platform** - provides the ability to build a quiz on the site and get an output `.json` file with quiz info that the ***bot*** needs to start the quiz\n\n**Bot** - provides access levels for *teacher* and *student*, *teacher* is able to upload `.json` files and start the quiz. At the time the *student* is able to take it\n\n---\n## Navigation\n- [Installation](#installation)\n- [Technologies used](#technologies-used)\n- [Project structure](#project-structure)\n- [Figma and FigJam](#figma-and-figjam)\n- [Contribute](#contribute)\n- [Testing](#testing)\n- [Documentation](#documentation)\n- [Contacts](#contacts)\n- [Project future](#project-future)\n\n---\n## Installation\n*Of course install [NodeJS](https://nodejs.org/en) previously\n\n\n1. Clone the repository\n```\ngit clone https://github.com/FeliksNovoselskyi/QuizizzBOT.git\n```\n2. Choose main directory of the project\n3. Install packages\n```\nnpm install\n```\n4. Create your own `.env` file in main directory\n\n5. Write the information required for the **bot** and **platform** here as in the `.env-sample` file in main directory\n\n6. Then choose what project do you wanna to start\n### BOT\n7. Choose `/bot`\n```\ncd bot\n```\n\n8. Create the `/uploaded_files` directory\n(copies of the quiz files you upload as a *teacher* will be saved there)\n```\nmkdir uploaded_files\n```\n\n9. Run the project (run `index.js`)\n```\nnode index.js\n```\n\n### PLATFORM\n7. Choose `/platform`\n```\ncd platform\n```\n\n8. Run the project (run `server.js`)\n```\nnode server.js\n```\n\n### Importantly\nThere is an `questions.json` file in the project directory\n\nYou can use it to create a quiz when the **bot** asks you to upload a `.json` file with the questions for the quiz\n\n---\n## Technologies Used\n\u003e[Back to top](#quizizz-telegram-bot)\n\n| **Technology**  | **Description** |\n| ------------- | -------------      |\n| [NodeJS](https://nodejs.org/uk)       | The *main framework* on which the **bot** and the **platform** are built  |\n| [Express](https://expressjs.com/)  | *Web framework* used to build the **platform**  |\n| [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)  | The primary programming language  |\n| [Sequelize ORM](https://sequelize.org/)  | **ORM** (Object-Relational Mapping) used for interacting with the database  |\n| [node-telegram-bot-api](https://www.npmjs.com/package/node-telegram-bot-api)  | **API** used to develop a **Telegram bot**  |\n| [EJS](https://ejs.co/)      | A simple templates language used for developing *website templates* |\n| [jQuery](https://jquery.com/)                                                  | A **JavaScript** library that simplifies development and interaction within the project.                              |\n| [Sortable](https://jqueryui.com/sortable/)                                     | A **jQuery** plugin that allows smooth and quick drag-and-drop *functionality* for reordering items.                    |\n| [AJAX](https://api.jquery.com/category/ajax/)                                  | A technology for fast and convenient data handling without page refreshes.                                        |\n| [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)        | Used for asynchronous operations on the site without constant page refreshes.                                     |\n| [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML)/[CSS](https://developer.mozilla.org/en-US/docs/Learn/CSS) | Languages used for *website* layout, *structure*, and *styling*.                   |\n| [Bootstrap 5](https://getbootstrap.com/)                                       | A ***frontend framework*** used to create various elements on the pages.                                                |\n| [Figma](https://help.figma.com/hc/en-us)                                       | An online service used for designing the site's layout.                                                           |\n| [SQLite3](https://www.sqlite.org/docs.html)                                    | A database used for site development.                                                                             |\n\n---\n## Project structure\n\u003e[Back to top](#quizizz-telegram-bot)\n```mermaid\ngraph TD;\n    A[**QuizizzBOT**] --\u003e B[**bot** \n    **Telegram bot** acting as a quiz organizer, *ensuring* their *uploading* and *launching*] --\u003e b1[*modules*];\n    B --\u003e b2[*db*];\n    B --\u003e b3[***questions.json***];\n    B --\u003e b4[***index.js***];\n\n    A --\u003e D[**platform**\n    A web application to create a quiz to be sent to a **bot**] --\u003e d1[*templates*];\n    D --\u003e d2[*static*];\n    D --\u003e d3[*db*];\n    D --\u003e d4[***server.js***];\n```\n\n---\n## Figma and FigJam\n\u003e[Back to top](#quizizz-telegram-bot)\n\nHere are links to Figma and FigJam\n\nIn FigJam - there is a complete project plan and its functioning, in the form of a scheme\nIn Figma you can find the design project of the platform\n\n* [Figma](https://www.figma.com/design/jMjdkaAEDIh5ONLtelxzi7/QuizizzBOT-Platform?node-id=0-1\u0026t=Y2RKr0VkKNAYPncM-1)\n* [FigJam](https://www.figma.com/board/AZD4TR2pNnDRlWKSJgVxKp/QuizizzBOT-Structure?node-id=0-1\u0026t=FxYQlPNAcjUQaIe8-1)\n\n---\n## Contribute\n\u003e[Back to top](#quizizz-telegram-bot)\n\nFor a project developer, it's always nice when his project someone wants to improve, but to make it as easy as possible for another developer to contribute\n\nYou have full **[documentation](#documentation)**, with visual demonstrations that explain how the main parts of the project work, with detailed commentary\n\nEverything you need for a quick entry and minimum entry threshold, as well as the fastest possible start of development\n\nAll that's left is to figure out how to contribute!\n\n### Here are the full-size instructions:\n1. Make a **Fork** of this repository, there is a button for that, in the top right corner of the repository page:\n\n![](readme_files/fork_image.png)\n\nAfter you do **Fork** you will have a copy of the project\n\n2. **Clone a copy of the project** to your local computer:\n```\ngit clone https://github.com/FeliksNovoselskyi/QuizizzBOT.git\n```\n\n3. **Select the project directory**, for this purpose you can use the command:\n```\ncd QuizizzBOT\n```\n\n4. **Create a new branch** for your changes, and navigate to it with the command:\n```\ngit checkout -b your-branch-name\n```\nThis is so that I can then conveniently review your changes, and add them if I find them useful rather than malicious\n\n5. Now make the changes you would like to add, at your discretion:\n- Add a new feature in a **bot** or **platform**\n- Fix bugs that I haven't noticed or haven't solved yet, but you've encountered them\n- Improve the documentation, in general my `readme.md`\n- Improve the design part of the project (this is more for the **platform**)\n- Additional optimization, speeding up both **bot** and **platform** performance\n- Improved file structure, code, and improved comments\nAs it is, any changes that pump this project up are welcome\n\n#### Important!\nAll your changes will be scrutinized and verified by me, after which I will merge your branch with the main branch and you will become a contributor to my repository!\n\n#### Important!\nA request, not an obligation, but I'd love it - put your code changes in the documentation, if possible, it will be easier for me to spell it out. But this is purely at your request\n\n#### Important!\nPlease test your own, improved version of the project thoroughly before creating a **Pull Request**\n\nYou can read more about testing in the corresponding section of my `readme.md` - **[Testing](#testing)**\n\n6. **Commit** your changes:\n\n**Add changes** to the tracking:\n```\ngit add .\n```\n\nMake a **commit**:\n```\ngit commit -m \"Description of changes\"\n```\n\n7. Push changes to your copy of the repository:\n```\ngit push origin your-branch-name\n```\n\n8. Create a **Pull Request**\n\nTo do this, go to **[original repository](https://github.com/FeliksNovoselskyi/QuizizzBOT)** and click on the tab labeled **Pull requests**:\n\n![](readme_files/pull_request_image.png)\n\nNext, click on the button labeled **New Pull Request**:\n\n![](readme_files/new_pull_request_button_image.png)\n\nDescribe your changes and submit the **merge** request\n\nI realize that many people have never done **Contribute** to **Open Source**, for that you have the **[instructions](#here-are-the-full-size-instructions)** above\n\nBut if there are additional questions about **contributing**, you always have a link to my **[community](https://t.me/feliks_novo)**\n\nIn it, we will be able to discuss your concerns in a separate thread for that called **[Contribute](https://t.me/feliks_novo)**\n\n**Thank you for contributing to the project. It will help to make this project better, and most importantly, in a shorter time!**\n\n---\n## Testing\n\u003e[Back to top](#quizizz-telegram-bot)\n\nComing soon...\n\n---\n## Documentation\n\u003e[Back to top](#quizizz-telegram-bot)\n\n- [For platform](#for-platform)\n- [For bot](#for-bot)\n- [End of documentation](#end-of-documentation)\n\nFull-fledged documentation for the project, contains introductory information for further acquaintance and work on the project for other developers\n\n---\n### For platform\n- [DB functionality](#platform-db-functionality)\n- [Server part](#server-part)\n- [`/templates` directory](#directories-templates)\n- [`/static` directory](#static)\n- [`/fetch_api` directory](#the-last-thing-left-in-the-static-directory-is-the-fetch_api)\n\n\nFirst of all, we familiarize ourselves with the structure of the database, **platform models**\n\nIn the **platform's** file system, there is a `/db` directory (as well as in the **bot's** file system)\n\nA database will be created in the `/db` directory, with the name you specify in the `.env` file (fill it according to the example in `.env-sample`)\n\nIn the database, you will be able to track:\n- Adding test questions\n- Deleting questions\n- Changing their order\n\nAnd so, let's move on to the files\n\n---\n#### Platform DB functionality\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\nLet's get started\n##### File `dbSetup.js`:\n```javascript\nimport {Sequelize} from 'sequelize'\nimport dotenv from 'dotenv'\n\nimport {fileURLToPath} from 'url'\nimport {dirname, join} from 'path'\n\n\ndotenv.config({path: '../.env'})\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\n// Create an instance of the Sequelize class, which will be an ORM, to work with the db\nexport const sequelize = new Sequelize(process.env.PLATFORM_DB_NAME, process.env.PLATFORM_DB_ADMIN_NAME, process.env.PLATFORM_DB_PASSWORD, {\n    host: 'localhost',\n    dialect: 'sqlite',\n    storage: join(__dirname, `${process.env.PLATFORM_DB_NAME}.db`)\n})\n\n// Sync DB\nsequelize.sync()\n    .then(() =\u003e {\n        console.log('Database created successfully')\n    })\n    .catch((error) =\u003e {\n        console.log('Error during creating database:', error)\n    })\n```\n\nThis file *prepares the database* for further use and *synchronizes* it\n\n**[Sequelize ORM](https://sequelize.org/)** is used\n\nThe data required to create the database is uploaded from the `.env` file, such as:\n- *Database Name*\n- *Database administrator name*\n- *Database password*\n\nTraditionally, as in all *project files*, there are comments, sometimes short and clear, sometimes clearly described in detail\nAll for the sake of convenience of further use and *development*, as well as simple and qualitative familiarization with the project code\n\nLet's move on\n\n##### File `models.js`:\n```javascript\nimport {DataTypes} from 'sequelize'\n\n// My scripts\nimport * as dataBase from './dbSetup.js'\n\n// Questions model\nexport const Questions = dataBase.sequelize.define('Questions', {\n    id: {\n        type: DataTypes.INTEGER,\n        autoIncrement: true,\n        primaryKey: true\n    },\n    questionText: {type: DataTypes.STRING},\n    answer1: {type: DataTypes.STRING},\n    answer2: {type: DataTypes.STRING},\n    answer3: {type: DataTypes.STRING},\n    answer4: {type: DataTypes.STRING},\n    correctAnswer: {type: DataTypes.INTEGER},\n    order: {type: DataTypes.INTEGER, defaultValue: 0}\n})\n```\n\nA file containing absolutely all the *models* for the **platform** database\n\nLet's move on to the rest of the **platform** structure:\n- `server.js`\n- `/templates`\n- `/static`\n\n---\n#### Server part\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\n##### File `server.js`:\n```javascript\nimport express from 'express'\nimport dotenv from 'dotenv'\nimport csrf from \"csurf\"\nimport cookieParser from 'cookie-parser'\n\nimport {fileURLToPath} from 'url'\nimport {dirname, join} from 'path'\n\n// My scripts\nimport * as models from './db/models.js'\n\ndotenv.config({path: '../.env'})\n\nconst app = express()\n\n// PORT and HOST (from .env file or basic values)\nconst PORT = process.env.PORT || 3000\nconst HOST = process.env.HOST || 'localhost'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\napp.set('view engine', 'ejs')\napp.set('views', './templates')\n\n// Setup static routes\napp.use('/static/', express.static(join(__dirname, 'static')))\napp.use('/css', express.static(join(__dirname, 'node_modules/bootstrap/dist/css')))\napp.use('/js', express.static(join(__dirname, 'node_modules/bootstrap/dist/js')))\napp.use('/js', express.static(join(__dirname, 'node_modules/jquery/dist')))\n\napp.use(express.urlencoded({extended: true}))\napp.use(express.json()) // for json parsing after ajax requests\n\n// CSRF protection\napp.use(cookieParser())\n\nconst csrfProtection = csrf({cookie: true})\napp.use(csrfProtection)\n\napp.use((req, res, next) =\u003e {\n    res.locals.csrfToken = req.csrfToken()\n    next()\n})\n\nlet context = {}\n\n// Render of the main page\napp.get('/', async (req, res) =\u003e {\n    context.error = null\n\n    // Preparing questions in their order (in ascending order)\n    const allQuestions = await models.Questions.findAll({\n        order: [['order', 'ASC']]\n    })\n    const questionData = allQuestions.map(question =\u003e question.dataValues)\n    context.questionData = questionData\n\n    res.render('main', context)\n})\n\n// Post requests on main page\napp.post('/', csrfProtection, async (req, res) =\u003e {\n    context = {}\n\n    const {\n        questionTextInput, \n        answer1Input, \n        answer2Input, \n        answer3Input, \n        answer4Input, \n        correctAnswerIndex, \n        action, \n        cell_order\n    } = req.body\n\n    if (action === \"createQuest\") {\n        if (!questionTextInput || !answer1Input || !answer2Input || !answer3Input || !answer4Input) {\n            // Unfilled inputs during creation of question\n            // response to ajax\n            return res.status(400).json({error: 'Fill all inputs to create a question'})\n        } else if (correctAnswerIndex == null) {\n            return res.status(400).json({ error: 'Choose only one correct answer within the valid range (1-4)' })\n        } else {\n            const newQuestion = await models.Questions.create({\n                questionText: questionTextInput,\n                answer1: answer1Input,\n                answer2: answer2Input,\n                answer3: answer3Input,\n                answer4: answer4Input,\n                correctAnswer: correctAnswerIndex\n            })\n\n            // Successfully created question\n            // response to ajax\n            return res.status(200).json({\n                id: newQuestion.id,\n                questionText: questionTextInput,\n                answer1: answer1Input,\n                answer2: answer2Input,\n                answer3: answer3Input,\n                answer4: answer4Input,\n                correctAnswer: correctAnswerIndex,\n                message: 'Question added successfully!'\n            })\n        }\n    }\n\n    if (action === \"deleteQuest\") {\n        // get questionId from the submitted form (from hidden input)\n        const questionId = req.body.questionId\n\n        // if the questionId was missed at the time of receipt\n        if (!questionId) {\n            return res.status(400).json({error: 'Question ID is missing'})\n        }\n\n        // Trying to delete question from db\n        try {\n            await models.Questions.destroy({\n                where: {id: questionId}\n            })\n\n            // Successfully deleted question\n            return res.status(200).json({deleteQuestion: true})\n        } catch (error) {\n            console.error(error)\n\n            // Probably errors during question deleting\n            return res.status(400).json({error: 'Failed to delete question'})\n        }\n    }\n\n    // If the item order (in this case, QUESTIONS) has been updated\n    if (action === \"cellOrderUpgrade\") {\n        for (const item_order of cell_order) {\n            await models.Questions.update(\n                {order: item_order.order},\n                {\n                    where: {id: item_order.id}\n                }\n            )\n        }\n    }\n\n    // If a teacher wants a .json file of questions\n    if (action === \"downloadFile\") {\n        try {\n            // Receiving questions in the order they appear in the db\n            const questions = await models.Questions.findAll({\n                order: [['order', 'ASC']]\n            })\n\n            // Creates an object with an array of questions, converting data from the database into a format suitable for JSON\n            const questionsData = {\n                questions: questions.map((q) =\u003e ({\n                    question: q.questionText,\n                    options: [q.answer1, q.answer2, q.answer3, q.answer4],\n                    correct: q.correctAnswer\n                }))\n            }\n\n            // Sets the content type as JSON so that the browser knows it is JSON data\n            const jsonContent = JSON.stringify(questionsData, null, 2)\n\n            // Sets the header to prompt the browser to download a file named “questions.json”\n            // Sets the content type as JSON so that the browser knows it is JSON data\n            res.setHeader('Content-Disposition', 'attachment; filename=\"questions.json\"')\n            res.setHeader('Content-Type', 'application/json')\n\n            // Sends JSON data to the client (for downloading)\n            res.send(jsonContent)\n        } catch (error) {\n            console.log(\"Error when creating JSON file\", error)\n            res.status(500).json({error: \"Failed to create JSON file\"})\n        }\n    }\n})\n\n// Url output for the site (for convenience during development)\napp.listen(PORT, HOST, () =\u003e {\n    console.log(`Server started on http://${HOST}:${PORT}`)\n})\n```\n\n**Platform** core file, server side using server-side **[JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)**\n\n**[Express](https://expressjs.com/)** is used, basic protection against **CSRF-attacks** is connected\n\nThis file accepts requests from the **frontend** part and responds to them, which makes the **platform** fast and easy to use\n\nHere such events as:\n- *Question creation*\n- *Question deletions*\n- *Error handling*\n- *Changing the order* of questions\n- *Uploading a `.json`* file with questions for the quiz\n\nThere are extensive comments that will make it easier and faster to understand the code of the `server.js` file\n\n---\n#### Directories (`/templates`)\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\nThe `/templates` directory contains these project templates:\n- `main.ejs`\n\nAs you can see, a simple templates language **[EJS](https://ejs.co/)** is used for templates.\n\n`main.ejs` - file in which the main project template is stored, with the necessary blocks for it\n\n#### `/static`\nThe `/static` directory contains directories such as:\n- `/css`\n- `/js`\n\n`/css` - directory with style files\n`/js` - directory with `.js` files that provide comfortable use of the client part for the user\n\nLet's break down `/js` in more detail:\n- `/fetch_api` - (directory) option to use **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** instead of **[AJAX](https://api.jquery.com/category/ajax/)** and **[jQuery](https://jquery.com/)**\n- `createQuest.js` - creating a question\n- `deleteQuest.js` - deleting a question\n- `fileDownloading.js` - unload `.json` file\n- `preloadScreen.js` - loading screen\n- `sortableElements.js` - sorting questions on the page\n\n---\n![](readme_files/create_question.gif)\n\n##### File `createQuest.js`:\n```javascript\n$(document).ready(function() {\n    $('.correct-answer-checkbox').change(function() {\n        // If the current checkbox is selected, deselect all other checkboxes\n        if ($(this).prop('checked')) {\n            $('.correct-answer-checkbox').not(this).prop('checked', false)\n        }\n    })\n\n    // Create question ajax request\n    $(\".question-form\").submit(function(event) {\n        event.preventDefault()\n\n        const questionTextInput = $('#questionTextInput').val()\n        const answer1Input = $('#answer1Input').val()\n        const answer2Input = $('#answer2Input').val()\n        const answer3Input = $('#answer3Input').val()\n        const answer4Input = $('#answer4Input').val()\n\n        const checkboxAnswers = [\n            $('#correctAnswer1Input'),\n            $('#correctAnswer2Input'),\n            $('#correctAnswer3Input'),\n            $('#correctAnswer4Input')\n        ]\n\n        function chooseCorrectAnswer() {\n            const correctAnswers = checkboxAnswers.filter(checkbox =\u003e checkbox.prop('checked'))\n\n            if (correctAnswers.length === 1) {\n                const correctAnswerIndex = checkboxAnswers.indexOf(correctAnswers[0])\n                return correctAnswerIndex\n            }\n            return null\n        }\n\n        const correctAnswerIndex = chooseCorrectAnswer()\n        const csrfToken = $('meta[name=\"csrf-token\"]').attr('content')\n\n        $.ajax({\n            url: '/',\n            type: 'POST',\n            headers: {\n                'X-CSRF-Token': csrfToken,\n            },\n            contentType: 'application/json',\n            data: JSON.stringify({\n                questionTextInput,\n                answer1Input,\n                answer2Input,\n                answer3Input,\n                answer4Input,\n                correctAnswerIndex,\n                action: 'createQuest'\n            }),\n            success: function(response) {\n                function getCorrectAnswer(index) {\n                    if (response.correctAnswer === index) {\n                        return 'corr-answer'\n                    } else {\n                        return 'incorr-answer'\n                    }\n                }\n\n                const newQuestionHtml = `\n                    \u003cdiv class=\"question\" data-question-id=\"${response.id}\"\u003e\n                        \u003cdiv class=\"question-header\"\u003e\n                            \u003ch3 class=\"question-text\"\u003e${response.questionText}\u003c/h3\u003e\n                            \u003cform action=\"/\" method=\"post\" class=\"delete-quest-form\"\u003e\n                                \u003cinput type=\"hidden\" name=\"questionId\" value=\"${response.id}\"\u003e\n                                \u003cbutton type=\"submit\" class=\"delete-quest-button\" name=\"action\" value=\"deleteQuest\" data-question-id=\"${response.id}\"\u003eDelete question\u003c/button\u003e\n                            \u003c/form\u003e\n                        \u003c/div\u003e\n                        \u003cdiv class=\"answers\"\u003e\n                            \n\n                            \u003cp class=\"answers-text ${getCorrectAnswer(0)}\"\u003e${response.answer1}\u003c/p\u003e\n                            \u003cp class=\"answers-text ${getCorrectAnswer(1)}\"\u003e${response.answer2}\u003c/p\u003e\n                            \u003cp class=\"answers-text ${getCorrectAnswer(2)}\"\u003e${response.answer3}\u003c/p\u003e\n                            \u003cp class=\"answers-text ${getCorrectAnswer(3)}\"\u003e${response.answer4}\u003c/p\u003e\n                        \u003c/div\u003e\n                    \u003c/div\u003e\n                `\n                // Add new question on page\n                $('#created-questions').append(newQuestionHtml)\n\n                // Clear error message and reset form inputs\n                $('.error-message').text(\"\")\n                $('.question-form')[0].reset()\n            },\n\n            error: function(response) {\n                if (response.status === 400) {\n                    $('.error-message').text(response.responseJSON.error)\n                }\n            }\n        })\n    })\n})\n```\n\nIn this file, an asynchronous request is sent to the server when adding a question, in order to speed up the site, and also to avoid regular reloading of the site at each action\nThe above mentioned technologies are used here:\n- **[AJAX](https://api.jquery.com/category/ajax/)**\n- **[jQuery](https://jquery.com/)**\n\n---\n![](readme_files/delete_question.gif)\n\n##### File `deleteQuest.js`:\n```javascript\n$(document).ready(function() {\n    // Delegation of events\n    // to be able to handle events of dynamically added elements\n    $(document).on('submit', '.delete-quest-form', function(event) {\n        event.preventDefault()\n\n        // Get form using jQuery for correctly questions deleting\n        let $form = $(this)\n        let questionId = $form.find(\"input[name=questionId]\").val()\n\n        const csrfToken = $('meta[name=\"csrf-token\"]').attr('content')\n\n        $.ajax({\n            url: '/',\n            type: 'POST',\n            headers: {\n                'X-CSRF-Token': csrfToken,\n            },\n            contentType: 'application/json',\n            data: JSON.stringify({\n                questionId,\n                action: 'deleteQuest'\n            }),\n            success: function(response) {\n                if (response.deleteQuestion) {\n                    // Deleting .question div where the delete button\n                    $form.closest('.question').remove()\n                }\n            },\n            error: function(response) {\n                console.log(response.responseJSON.error)\n            }\n        })\n    })\n})\n```\n\nSimilarly, allows you to delete questions asynchronously and without reloading the page, both on the page and in the database, which is logical\nThe technologies used are similar\nAlso connected protection from **CSRF-attacks** as in other static files from the `/static` directory\n\n---\n![](readme_files/file_downloading.gif)\n\n##### File `fileDownloading.js`:\n```javascript\n$(document).ready(function() {\n    // Process a button click to load a .json file with test questions\n    // in order to upload this file to chat with the bot in the future\n    $(document).on('submit', '#download-form', function(event) {\n        event.preventDefault()\n\n        const csrfToken = $('meta[name=\"csrf-token\"]').attr('content')\n\n        $.ajax({\n            url: '/',\n            type: 'POST',\n            headers: {\n                'X-CSRF-Token': csrfToken,\n            },\n            contentType: 'application/json',\n            data: JSON.stringify({\n                action: 'downloadFile'\n            }),\n            success: function(response) {\n                // Converts a response object to formatted JSON with indentation\n                const jsonString = JSON.stringify(response, null, 2)\n\n                // Creating a page link element\n                // Putting a link in it that will prompt you to select a location\n                // in the computer's file system\n                const link = document.createElement('a')\n                link.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(jsonString)\n                link.download = 'questions.json'\n\n                // Simulate clicking on a newly created link to instantly download a file\n                document.body.appendChild(link)\n                link.click()\n                document.body.removeChild(link)\n            }\n        })\n    })\n})\n```\n\nThe file provides convenient and fast `.json` uploading at the moment when the teacher needs it\n\nAfter clicking on the button to upload `.json` file, on the server the necessary information is collected from the database and formed into a `.json` file, this file will be sent by the **teacher** to the **bot** personally to process it and the teacher can run the quiz for his students\n\nOn the client side, a temporary link is formed, when you click on it, it opens a window of your computer's file system:\n- **macOS** - Finder\n- **Windows** - Explorer\n\nThen you choose a convenient place on your computer to save the new file\n\nThe technologies used are similar\n\n---\n![](readme_files/preload_screen.gif)\n\n##### File `preloadScreen.js`:\n```javascript\nwindow.addEventListener(\"load\", () =\u003e {\n    const loadingScreen = document.querySelector(\"#loading-screen\")\n    const content = document.querySelector(\"main\")\n    \n    loadingScreen.style.display = \"none\"\n    content.style.display = \"block\"\n})\n```\n\nAs you can see, a compact file\nParticipates in the work of a beautiful and minimalistic loading screen for the **platform**, to brighten up your wait for loading the site if you have a weak computer)))))\nWell, or, it is heavily loaded with other processes\n\nThe basic **DOM-tree**, its *methods*, and `css` *styles* are used\n\n---\n![](readme_files/sortable_elements.gif)\n\n##### File `sortableElements.js`:\nThe last file in this directory\n```javascript\n$(document).ready(function () {\n    const $questions = $(\"#created-questions\")\n\n    function sortingElements($container, dataAttr) {\n        // Check if there are elements that can be sorted\n        if ($container.length) {\n            new Sortable($container[0], {\n                animation: 150,\n                onEnd: function () {\n                    const order = []\n                    const containerCells = $container.children()\n                    \n                    // Prepare order before being sent to the server\n                    containerCells.each(function (index) {\n                        const cellId = $(this).data(dataAttr)\n                        order.push({\n                            id: cellId,\n                            order: index + 1\n                        })\n                    })\n    \n                    const csrfToken = $('meta[name=\"csrf-token\"]').attr('content')\n                    \n                    $.ajax({\n                        url: \"/\",\n                        type: \"POST\",\n                        headers: {\n                            'X-CSRF-Token': csrfToken,\n                        },\n                        contentType: 'application/json',\n                        data: JSON.stringify({\n                            cell_order: order,\n                            action: \"cellOrderUpgrade\",\n                        })\n                    })\n                }\n            })\n        }\n    }\n\n    sortingElements(\n        $container=$questions,\n        dataAttr=\"question-id\"\n    )\n})\n```\n\nUsed for smooth, and most importantly beautiful change of the order of questions in the interface of the site\nThere is a function that allows you to compactly sort and other groups of elements on the site\n\nUsed such technologies as:\n- **[jQuery](https://jquery.com/)**\n- **[Sortable](https://jqueryui.com/sortable/)**\n- **[AJAX](https://api.jquery.com/category/ajax/)**\n\nThe new order of questions, which are displayed on the page after dragging and dropping them, will be processed and saved in the database\nThat will allow to save the order of questions after page refresh, as well as to place them in the necessary order in the generated `.json` file when uploading it\n\n---\n#### The last thing left in the `/static` directory is the `/fetch_api`\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\nWhat it consists of:\n- `createQuestFetch.js`\n- `deleteQuestFetch.js`\n\nThese files perform similar functionality to the `createQuest.js` and `deleteQuest.js` files\nHowever, the **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** is used here\n\n---\n![](readme_files/create_question.gif)\n\n##### File `createQuestFetch.js`:\n```javascript\n$(document).ready(function() {\n    $('.correct-answer-checkbox').change(function() {\n        // If the current checkbox is selected, deselect all other checkboxes\n        if ($(this).prop('checked')) {\n            $('.correct-answer-checkbox').not(this).prop('checked', false)\n        }\n    })\n\n    // Create question ajax request\n    $(\".question-form\").submit(function(event) {\n        event.preventDefault()\n\n        // Get form input values\n        // using jQuery\n        const questionTextInput = $('#questionTextInput').val()\n        const answer1Input = $('#answer1Input').val()\n        const answer2Input = $('#answer2Input').val()\n        const answer3Input = $('#answer3Input').val()\n        const answer4Input = $('#answer4Input').val()\n\n        const checkboxAnswers = [\n            $('#correctAnswer1Input'),\n            $('#correctAnswer2Input'),\n            $('#correctAnswer3Input'),\n            $('#correctAnswer4Input')\n        ]\n\n        function chooseCorrectAnswer() {\n            const correctAnswers = checkboxAnswers.filter(checkbox =\u003e checkbox.prop('checked'))\n\n            if (correctAnswers.length === 1) {\n                const correctAnswerIndex = checkboxAnswers.indexOf(correctAnswers[0])\n                return correctAnswerIndex\n            }\n            return null\n        }\n\n        const correctAnswerIndex = chooseCorrectAnswer()\n\n        fetch('/', {\n            method: 'POST',\n            headers: {\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify({\n                questionTextInput,\n                answer1Input,\n                answer2Input,\n                answer3Input,\n                answer4Input,\n                correctAnswerIndex,\n                action: 'createQuest'\n            })\n        })\n        .then(response =\u003e {\n            if (!response.ok) {\n                throw response\n            }\n            return response.json()\n        })\n        // Successfully created question\n        .then(data =\u003e {\n            const newQuestionHtml = `\n                \u003cdiv class=\"question\"\u003e\n                    \u003cdiv class=\"question-header\"\u003e\n                        \u003ch3 class=\"question-text\"\u003e${data.questionText}\u003c/h3\u003e\n                        \u003cform action=\"/\" method=\"post\" class=\"delete-quest-form\"\u003e\n                            \u003cinput type=\"hidden\" name=\"questionId\" value=\"${data.id}\"\u003e\n                            \u003cbutton type=\"submit\" class=\"delete-quest-button\" name=\"action\" value=\"deleteQuest\" data-question-id=\"${data.id}\"\u003eDelete question\u003c/button\u003e\n                        \u003c/form\u003e\n                    \u003c/div\u003e\n                    \u003cdiv class=\"answers\"\u003e\n                        \u003cp class=\"answers-text\"\u003e${data.answer1}\u003c/p\u003e\n                        \u003cp class=\"answers-text\"\u003e${data.answer2}\u003c/p\u003e\n                        \u003cp class=\"answers-text\"\u003e${data.answer3}\u003c/p\u003e\n                        \u003cp class=\"answers-text\"\u003e${data.answer4}\u003c/p\u003e\n                    \u003c/div\u003e\n                \u003c/div\u003e\n            `\n\n            // Add new question on page\n            // insertAdjacentHTML method is used to correctly add new code\n            // using dom-tree\n            document.getElementById('created-questions').insertAdjacentHTML('beforeend', newQuestionHtml)\n            \n            // Clear error message and reset form inputs\n            document.querySelector('.error-message').textContent = \"\"\n            document.querySelector('.question-form').reset()\n        })\n        // Errors during form validation\n        .catch(error =\u003e {\n            // Check if error is an instance of Response class\n            // It is checked to ensure that the request has been sent, even if it is not correct\n            if (error instanceof Response) {\n                error.json().then(err =\u003e {\n                    document.querySelector('.error-message').textContent = err.error\n                })\n            } else {\n                console.error(error)\n            }\n        })\n    })\n})\n\n// Response.json() takes the Response stream and reads it to the end\n// It returns a promise that resolves to the result of parsing the response body as a string\n```\n\nIt is used for what its counterpart - *asynchronous* question creation, is also used for\nCheck out the comments, and if you have ideas on how to improve this part of the project, I'd love to see your suggestions in the form of a **Pull Request**!\n\n---\n![](readme_files/delete_question.gif)\n\n##### File `deleteQuestFetch.js`:\n```javascript\n$(document).ready(function() {\n    // Delegation of events\n    // to be able to handle events of dynamically added elements\n    $(document).on('submit', '.delete-quest-form', function(event) {\n        event.preventDefault()\n\n        // Get form using jQuery for correctly questions deleting\n        let $form = $(this)\n        let questionId = $form.find(\"input[name=questionId]\").val()\n\n        fetch('/', {\n            method: 'POST',\n            headers: {\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify({\n                questionId,\n                action: 'deleteQuest'\n            })\n        })\n        .then(response =\u003e {\n            if (!response.ok) {\n                throw response\n            }\n            return response.json()\n        })\n        // Successfully deleted question\n        .then(data =\u003e {\n            if (data.deleteQuestion) {\n                $form.closest('.question').remove()\n            }\n        })\n\n        // Possible errors\n        .catch(error =\u003e {\n            // Check if error is an instance of Response class\n            // It is checked to ensure that the request has been sent, even if it is not correct\n            if (error instanceof Response) {\n                error.json().then(err =\u003e {\n                    console.error(err.error)\n                })\n            }\n        })\n    })\n})\n\n// Response.json() takes the Response stream and reads it to the end\n// It returns a promise that resolves to the result of parsing the response body as a string\n```\n\nFile deletion, using the same technology\nRead the comments to familiarize yourself, if you have additional questions, contact me at my **[GitHub Profile](https://github.com/FeliksNovoselskyi)**\n\nThese files provide an alternative for those developers who are either not familiar with **[jQuery](https://jquery.com/)** and **[AJAX](https://api.jquery.com/category/ajax/)** at all, or you're just more used to **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)**\n\nIf I have time, in the future I will try to make an alternative on **[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)** and for the rest of the *client part*, and also, to finalize these files\n\nAnd you will have an opportunity to work on this project with more familiar and familiar technology, if you are interested in it, of course\n\n---\n### For bot\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\n- [BOT DB functionality](#bot-db-functionality)\n- [Main bot files](#main-bot-files)\n- [Modules directory](#modules-directory)\n\nDocumentation of the **bot**, I think we should start with its models\nIn the **bot's** directory there is a `/db` directory\nLet's have a look at its contents:\n- `dbFunctions.js`\n- `dbSetup.js`\n- `models.js`.\n\nThe `dbFunctions.js` file is present here, unlike in the **platform**, because in the **Telegram bot** some operations with the database were repeated, because of which, it was decided to put some functions in a separate file for convenience\n\nAs you can see, the structure that ensures the operation of the database is very similar to the same structure for the **platform**, this is done on purpose, so that the structure of projects was maximally similar, simple, and most importantly understandable\n\nLet's analyze each file in this directory separately\n\n#### BOT DB functionality\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\nFile `dbSetup.js`:\n```javascript\nimport {Sequelize} from 'sequelize'\nimport dotenv from 'dotenv'\n\nimport {fileURLToPath} from 'url'\nimport {dirname, join} from 'path'\n\ndotenv.config({path: '../.env'})\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\n// Create an instance of the Sequelize class, which will be an ORM, to work with the db\nexport const sequelize = new Sequelize(process.env.BOT_DB_NAME, process.env.BOT_DB_ADMIN_NAME, process.env.BOT_DB_PASSWORD, {\n    host: 'localhost',\n    dialect: 'sqlite',\n    storage: join(__dirname, `${process.env.BOT_DB_NAME}.db`)\n})\n\n// Sync BOT DB\nsequelize.sync()\n    .then(() =\u003e {\n        console.log('Database created successfully')\n    })\n    .catch((error) =\u003e {\n        console.log('Error during database creation:', error)\n    })\n```\n\nThis file configures, creates and synchronizes the database, using the familiar **[Sequelize ORM](https://sequelize.org/)**\n\nThe values needed for the database, such as:\n- *Database Administrator Name*\n- *Database Password*\n- *Database Name*\n\nSimilarly with the **platform**, taken from the `.env` file\n\nFile `models.js`:\n```javascript\nimport {DataTypes} from 'sequelize'\n\n// My scripts\nimport * as dataBase from './dbSetup.js'\n\n// Users model\nexport const Users = dataBase.sequelize.define('Users', {\n    userId: {type: DataTypes.INTEGER},\n    username: {type: DataTypes.TEXT},\n    firstName: {type: DataTypes.TEXT},\n    lastName: {type: DataTypes.TEXT},\n    role: {type: DataTypes.TEXT},\n    progress: {type: DataTypes.TEXT}\n})\n```\n\nThis file contains models for the **bot**, for example, the `Users` model, which stores such information about users:\n- `userId` - primary key of the user from *Telegram* itself\n- `username` - full user name\n- `firstName` - user's first name\n- `lastName` - user's last name\n- `role` - user's role for the **bot** (*teacher* or *student*)\n- `progress` - user's progress after passing the quiz\n\nFinally, the fun part, let's go to the `dbFunctions.js` file\n\nFile `dbFunctions.js`:\n```javascript\n// My scripts\nimport * as models from './models.js'\n\n// Function in which user information is added to the database\nexport async function addUser(userId, username, firstName, lastName, role) {\n    try {\n        models.Users.create({\n            userId: userId,\n            username: username,\n            firstName: firstName,\n            lastName: lastName,\n            role: role,\n        })\n    } catch (error) {\n        console.error('Error adding a user:', error.message)\n    }\n}\n\n// Function to get information about the user when he/she wants it\nexport async function getUserById(userId) {\n    try {\n        const user = await models.Users.findOne({where: {userId}})\n        return user\n    } catch (error) {\n        console.error('Error when receiving a user:', error.message)\n        return null\n    }\n}\n\n// Function updating the user role in the database\n// depending on the one he's got now\nexport async function updateUserRole(userId, newRole, callback) {\n    try {\n        const [updated] = await models.Users.update({role: newRole}, {where: {userId}})\n        \n        if (updated) {\n            callback()\n        } else {\n            console.error(\"Error: user with this id was not found\")\n        }\n    } catch (error) {\n        console.error(\"Error updating a role:\", error.message)\n    }\n}\n\n// Function that updates the user's progress\nexport async function updateProgress(newList, userId) {\n    try {\n        await models.Users.update({progress: newList}, {where: {userId}})\n    } catch (error) {\n        console.error(\"Error when updating user progress:\", error.message)\n    }\n}\n\n\n// Function to clearing user progress from the database\nexport async function clearProgress(userId) {\n    try {\n        await models.Users.update({progress: null}, {where: {userId}})\n    } catch (error) {\n        console.error(\"Error during clearing progress\", error.message)\n    }\n}\n```\n\nAs you can see, there are quite a lot of functions in this file, which are used in almost all pieces of code for the **bot**\nIt was possible to use already existing methods, but creating your own functions allowed you to add ***logging***, for additional checks of the code and with the database specifically\nAt the same time getting a more detailed description of errors, which **[NodeJS](https://nodejs.org/uk)** has quite big problems with\n\nIn short, the point of the file is to improve the existing methods in **[Sequelize ORM](https://sequelize.org/)**, while gathering them in one place and giving a more detailed description of each of them\n\nAs always, there are comments in the code, which will help you to understand the project, and if you want to make your own changes, which I will be extremely glad and grateful to you\n\n#### Main bot files\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\nNow, let's move on to the main **bot** files:\n- `index.js` - the main **bot** file, where all its processes are managed\n- `config.js` - **bot** configuration, its main variables and constants\n- `questions.json` - an example `.json` file to run a quiz\n\nThese files are located in the main **bot** directory\n\nLet's start with the main file, `index.js`\nFile `index.js`:\n```javascript\n// My scripts\nimport {\n    bot, \n    completedQuizzes, \n    canStart, \n    addedFile, \n    isTeacherLogin, \n    helpMessage,\n    allQuestions,\n    __dirname\n} from \"./config.js\"\nimport handleCallbackQuery from './modules/callbackHandlers.js'\nimport handleFileUpload from './modules/filesHandlers.js'\n\nimport * as dbFunctions from './db/dbFunctions.js'\n\nimport * as quizFuncs from './modules/quizFunctional.js'\nimport * as botFuncs from './modules/botFunctions.js'\n\n// Create a menu of commands for the bot\nbot.setMyCommands([\n    {command: '/start', description: '❕ Start communicating with the bot'},\n    {command: '/help', description: '❕ Get help from the bot'},\n    {command: '/info', description: '❕ Get information about you'},\n    {command: '/change_role', description: '❕ Change your role'},\n    {command: '/can_start_quiz', description: '❕ Provide an opportunity to take the quiz (teacher)'},\n    {command: '/quiz', description: '❕ Start taking the quiz if given the opportunity to do so (student)'},\n])\n\n// User messages\nbot.on('message', async function(message) {\n    const chatId = message.chat.id\n    const userId = message.from.id\n    const messageId = message.message_id\n\n    // If a user has written /start we get data about him/her\n    // whether it is stored in the database or not\n    if (message.text === '/start') {\n        dbFunctions.getUserById(userId).then(async (user) =\u003e {\n            if (user) {\n                await bot.sendMessage(chatId, `Hi! 👋\\nYou are already registered with this bot as ${user.role}`)\n            } else {\n                const startOptions = {\n                    reply_markup: {\n                        inline_keyboard: [\n                            [{text: '🧑‍🎓 Register as a student', callback_data: 'register_student'}],\n                            [{text: '👨‍🏫 Log in as a teacher', callback_data: 'login_teacher'}],\n                        ],\n                    },\n                }\n        \n                await bot.sendMessage(chatId, 'Hello! 👋\\nYou can sign up for this bot as a student 🧑‍🎓 or log in as a teacher 👨‍🏫 to create quizzes! \\n❕ Find out your status: /info', startOptions)\n            }\n        })\n    }\n\n    // If the user wants to retrieve data about him/herself\n    if (message.text === '/info')  {\n        dbFunctions.getUserById(userId).then(async (user) =\u003e {\n            if (user) {\n                await bot.sendMessage(chatId, `👉 Your first and last names: ${user.firstName} ${user.lastName} \\n👉 Your status: ${user.role}`)\n            } else {\n                await bot.sendMessage(chatId, '😓 You are not logged into this bot')\n            }\n        })\n    }\n\n    // If the user wants to take help with bot commands\n    if (message.text === '/help')  {\n        await bot.sendMessage(chatId, helpMessage)\n    }\n\n    // Command to change role\n    if (message.text === '/change_role')  {\n        dbFunctions.getUserById(userId).then(async (user) =\u003e {\n\n            if (user) {\n                botFuncs.checkUserRole(userId, chatId)\n            } else {\n                await bot.sendMessage(chatId, '❗️ You are not logged into this bot! ❗️')\n            }\n        })\n    }\n\n    // If the user wants to start taking the quiz\n    if (message.text === '/quiz') {\n        dbFunctions.getUserById(userId).then(async (user) =\u003e {\n            if (user.role === 'student' \u0026\u0026 canStart.canStartQuiz \u0026\u0026 !completedQuizzes[chatId]) {\n                await dbFunctions.clearProgress(userId)\n\n                // Resetting the question index for a user\n                quizFuncs.userQuestions[chatId] = 0\n                await quizFuncs.sendQuestion(chatId, messageId)\n            } else {\n                await bot.sendMessage(chatId, '❗️ Passing the quiz is not allowed!\\n❗️ Or, if you are a teacher, you cannot take the quiz')\n            }\n        })\n    }\n\n    // Command authorizing to start the quiz (available only to the teacher)\n    if (message.text === '/can_start_quiz') {\n        dbFunctions.getUserById(userId).then(async (user) =\u003e {\n            if (user.role === 'teacher') {\n                if (addedFile.addedJsonFile) {\n                    canStart.canStartQuiz = true\n                    completedQuizzes[chatId] = false\n                    bot.sendMessage(chatId, '🔥👍 You have successfully created a quiz!\\n🤔 Now your students can start quiz with command /quiz')\n                } else {\n                    bot.sendMessage(chatId, '❗️ You did not upload a .json file with questions ❗️')\n                }\n            } else {\n                await bot.sendMessage(chatId, '❗️ Only the teacher has the right to start the quiz ❗️')\n            }\n        })\n    }\n\n    // Handling the condition when absolutely any message has been written\n    // in this case, help for the user will be displayed\n    if (\n    !message.document \u0026\u0026\n    !isTeacherLogin.isLogin \u0026\u0026\n    message.text !== '/start' \u0026\u0026\n    message.text !== '/info' \u0026\u0026\n    message.text !== '/help' \u0026\u0026\n    message.text !== '/change_role' \u0026\u0026\n    message.text !== '/quiz' \u0026\u0026\n    message.text !== '/can_start_quiz'\n        ) {\n        await bot.sendMessage(chatId, helpMessage)\n    }\n})\n\n// Callbacks handling\nbot.on('callback_query', async function(query) {\n    await handleCallbackQuery(\n        query, \n        quizFuncs, \n        dbFunctions, \n        botFuncs\n    )\n})\n\n// Files handling\nbot.on('document', async function(message) {\n    await handleFileUpload(\n        dbFunctions,\n        message\n    )\n})\n```\n\nThe main file in the **bot's** structure, at the same time the largest one\nThis file manages all the processes of the **bot**, which also take place in other files\n\nThe main variables and constants from `config.js` are imported here, as well as functions from other modules\n\nThis is where it is processed:\n- Commands\n- Callback requests\n- Document upload (basically, `.json` file with quiz)\n- A universal menu for **bot** commands is set up\n\nAbout the menu of commands, yes, it can be done through **BotFather**, but, in this case, the menu will be unique for a particular **bot**, and when cloning the project, this menu in your **bot** with your *token* will not be displayed\nThe menu of course takes extra space in the code, but makes the transition to work on the project more convenient, fast and comfortable\n\nIf you have additional questions about this and other files:\nFirst of all, familiarize yourself with the comments\nSecond, contact me at my **[GitHub Profile](https://github.com/FeliksNovoselskyi)**\n\nLet's move on\n\nFile `config.js`:\n```javascript\nimport {dirname} from 'path'\nimport {fileURLToPath} from 'url'\n\nimport path from 'path'\nimport telegramApi from 'node-telegram-bot-api'\nimport dotenv from 'dotenv'\n\ndotenv.config({path: '../.env'})\n\nconst __filename = fileURLToPath(import.meta.url)\nexport const __dirname = dirname(__filename)\n\nconst botToken = process.env.token\nexport const bot = new telegramApi(botToken, {polling: true})\n\n// Get the directory where we will save json files sent by the user\nexport const uploadFilesDir = path.join(__dirname, 'uploaded_files')\n\n// Variables for quiz functionality (quiz in one message)\nexport const answerMsgIdState = {\n    answerMessageId: null\n}\nexport const completedQuizzes = {}\nexport const jsonFileName = {}\n\n// Flags\nexport const canStart = {\n    canStartQuiz: false\n}\nexport const addedFile = {\n    addedJsonFile: false\n}\nexport const isTeacherLogin = {\n    isLogin: false\n}\n\n// Constant to which the questions will be written after parsing the file.json\nexport const allQuestions = {\n    questions: {}\n}\n\n// Constant storing the text of the message with the result of the answer to the question\n// is used when checking for a match between a new text and an existing text.\nexport const currentMessageText = {\n    messageText: \"\"\n}\n\n\n// Bot message (only huge messages)\nexport let helpMessage = `\nHi! 👋🤘\nDo you need some help? 🤔\nHere's a list of my commands that can help you:\n\n/start - starts your communication with me\n/help - will give you a list of commands that can help you\n/info - will give you information about yourself, your status, whether you are registered or not\n/change_role - allows you to change your role, for example from student to teacher\n/can_start_quiz - command that allows you to pass quiz. Available only to teacher 👨‍🏫\n/quiz - command to start a quiz. Available only to the student 🧑‍🎓\n\nAlso remember that you always have a menu of my commands that can help you 🤗\n`\n```\n\nA configuration file for the bot, which contains the main constants and variables that are exported and used in other bot modules in the future\n\nThe reason for the creation of the file was the overloading of `index.js`, because of which it was split into many files, which remain to this day\n\nLikewise, extensive comments are present, giving you a chance to familiarize yourself with every point that is difficult to understand at a glance\n\nGood luck!\n\nNext, the last file in the main directory\n\nFile `questions.json`:\n```json\n{\n    \"questions\": [\n        {\n            \"question\": \"What language is used to develop with Node.js?\",\n            \"options\": [\"Python\", \"C#\", \"JavaScript\", \"Ruby\"],\n            \"correct\": 2\n        },\n        {\n            \"question\": \"What is the name of the package manager for Node.js?\",\n            \"options\": [\"npm\", \"pip\", \"composer\", \"gem\"],\n            \"correct\": 0\n        },\n        {\n            \"question\": \"What is an API?\",\n            \"options\": [\"Data transfer protocol\", \"Versioning system\", \"Interface for application interaction\", \"Python module\"],\n            \"correct\": 2\n        },\n        {\n            \"question\": \"What method is used to output information to the console in JavaScript?\",\n            \"options\": [\"print()\", \"echo()\", \"console.log()\", \"alert()\"],\n            \"correct\": 2\n        },\n        {\n            \"question\": \"What is a variable in programming?\",\n            \"options\": [\"Fixed value\", \"Named container for saving data\", \"Data type\", \"System team\"],\n            \"correct\": 1\n        },\n        {\n            \"question\": \"What character is used to assign a value in JavaScript?\",\n            \"options\": [\"==\", \"=\", \"=\u003e\", \"===\"],\n            \"correct\": 1\n        },\n        {\n            \"question\": \"Which data type is primitive in JavaScript?\",\n            \"options\": [\"Object\", \"Array\", \"Function\", \"String\"],\n            \"correct\": 3\n        },\n        {\n            \"question\": \"What does the ‘===’ operator do in JavaScript?\",\n            \"options\": [\"Compares only the value of\", \"Compares types and values\", \"Compares object references\", \"Converts data types before comparing\"],\n            \"correct\": 1\n        },\n        {\n            \"question\": \"What function is used to convert a string to a number in JavaScript?\",\n            \"options\": [\"Number()\", \"String()\", \"parseInt()\", \"float()\"],\n            \"correct\": 2\n        },\n        {\n            \"question\": \"What is DOM in web development?\",\n            \"options\": [\"Markup language\", \"Software library\", \"Interface for working with HTML and XML documents\", \"A framework for JS\"],\n            \"correct\": 2\n        }\n    ]\n}\n```\n\nThis file is not used in other modules, or the functioning of the project as a whole\nThis is an example file that shows a sample `.json` file that will be sent to the **bot** to allow it to start your quiz\n\nAdded so that you can quickly grab an already built file that will serve as an example for the **bot** to work from\n\nIf you are interested in this, you can already create your own `.json`, but with the help of the **platform**, in a convenient and simple interface, instead of manually writing the code\n\nI think it will be very convenient for you, as well as those with whom you can work on this project\n\n#### Modules directory\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\nSo, we have familiarized ourselves with the main part of the **bot's** file system\nNow we can move on to the `/modules` directory\n\nWhat `/modules` consists of:\n- `botFunctions.js`\n- `callbackHandlers.js`\n- `filesHandlers.js`\n- `quizFunctional.js`\n\nThese are the **bot's** service files, which provide the lion's share of its functionality, and are also designed to unload the main file, which is obviously more convenient when working on the project\n\n---\n![](readme_files/botFunctions.gif)\n\nFile `botFunctions.js`:\n```javascript\n// My scripts\nimport * as dbFunctions from '../db/dbFunctions.js'\n\nimport {\n    bot,\n    isTeacherLogin\n} from \"../config.js\"\n\n// Function that handles login as a teacher, with password verification\nexport async function teacherLogin(chatId, messageId, userId, username, firstName, lastName, changeToTeacherRole) {\n    await bot.sendMessage(chatId, '❕ Enter your teacher account password (password only, no extra characters)')\n\n    const teacherPassword = process.env.teacherPassword\n    bot.once('message', async (message) =\u003e {\n        const userInputPassword = message.text\n\n        // Checking the validity of the password entered by the user\n        if (userInputPassword === teacherPassword) {\n            await bot.editMessageReplyMarkup({inline_keyboard: []}, {chat_id: chatId, message_id: messageId})\n            if (changeToTeacherRole) {\n                // Role change from student to teacher\n                dbFunctions.updateUserRole(userId, 'teacher', () =\u003e {\n                    isTeacherLogin.isLogin = false\n                    bot.sendMessage(chatId, \"👨‍🏫 Your role has been changed to teacher! \\n\\n❕ Now you can upload a JSON file with your test questions, and give your students the opportunity to start the test with command /can_start_quiz!\")\n                })\n                return\n            }\n            \n            dbFunctions.addUser(userId, username, firstName, lastName, 'teacher')\n            await bot.sendMessage(chatId, '👨‍🏫 You have successfully logged in as a teacher! \\n❕ Now you can upload a JSON file with your test questions, and give your students the opportunity to start the test with command /can_start_quiz!')\n        } else {\n            await bot.sendMessage(chatId, '❗️ Incorrect password! \\nTry again \\nTo do this, press the login button again ❗️')\n        }\n    })\n}\n\n// Check the current user role\nexport function checkUserRole(userId, chatId) {\n    dbFunctions.getUserById(userId).then(async (user) =\u003e {\n        if (user) {\n            if (user.role === 'student') {\n                // If the current role is student, offer a change to teacher\n                bot.sendMessage(chatId, \"🧑‍🎓 You are logged in as a student. Would you like to log in as a teacher 👨‍🏫?\", {\n                    reply_markup: {\n                        inline_keyboard: [\n                            [{text: \"Yes\", callback_data: \"switch_to_teacher\"}],\n                            [{text: \"No\", callback_data: \"cancel_change_role\"}]\n                        ]\n                    }\n                })\n            } else if (user.role === 'teacher') {\n                // If the current role is teacher, offer a change to student\n                bot.sendMessage(chatId, \"👨‍🏫 You are logged in as a teacher. Would you like to log in as a student 🧑‍🎓?\", {\n                    reply_markup: {\n                        inline_keyboard: [\n                            [{text: \"Yes\", callback_data: \"switch_to_student\"}],\n                            [{text: \"No\", callback_data: \"cancel_change_role\"}]\n                        ]\n                    }\n                })\n            }\n        }\n    })\n}\n```\n\nThis file contains some of the main functions of the bot, which are used in many cases, which simplifies the code and writing of the project\n- `teacherLogin()`\n- `checkUserRole()`\n\n`teacherLogin()` - Logs the user in as a *teacher*, and checks if the user is already logged in (and accordingly tries to change his *role* to *teacher*), or creates an account immediately as a *teacher*\n\n`checkUserRole()` - checks the user's *role* before offering to change it, so that if you are a *student*, you can successfully change your *role* to *teacher* (if you know the password, of course)\nAnd being a *teacher*, you can become a *student*\n\nThe \"`teacher` / `student`\" functionality was created for this purpose:\n- First of all for convenient testing of the project\n- To get rid of the need to maintain 2 accounts in **Telegram**\n- Allow users to change their *role*\n\nAs everywhere else, there are comments that will help you to understand the file code in detail in practice\n\n---\n![](readme_files/callbackHandlers.gif)\n\nFile `callbackHandlers.js`:\n```javascript\n// My scripts\nimport {\n    bot,\n    allQuestions,\n    answerMsgIdState,\n    isTeacherLogin,\n    currentMessageText,\n    __dirname\n} from \"../config.js\"\n\n// Processing callback functions\n// bot.on('callback_query', async function(query) {\nexport default async function handleCallbackQuery(query, quizFuncs, dbFunctions, botFuncs) {\n    const chatId = query.message.chat.id\n    const userId = query.from.id\n    const messageId = query.message.message_id\n\n    const username = query.from.username || ''\n    const firstName = query.from.first_name || ''\n    const lastName = query.from.last_name || ''\n\n    const userIndex = quizFuncs.userQuestions[chatId] || 0\n    const currentQuestion = allQuestions.questions[userIndex]\n\n    // Sign up as a student\n    if (query.data === 'register_student') {\n        dbFunctions.addUser(userId, username, firstName, lastName, 'student')\n        await bot.editMessageReplyMarkup({inline_keyboard: []}, {chat_id: chatId, message_id: messageId})\n        await bot.sendMessage(chatId, '🧑‍🎓 You are registered in this bot as a student')\n        return\n    }\n\n    // Sign in as a teacher\n    if (query.data === 'login_teacher') {\n        isTeacherLogin.isLogin = true\n        botFuncs.teacherLogin(chatId, messageId, userId, username, firstName, lastName, false)\n        return\n    }\n\n    // Role change from student to teacher\n    if (query.data === 'switch_to_teacher') {\n        isTeacherLogin.isLogin = true\n        botFuncs.teacherLogin(chatId, messageId, userId, username, firstName, lastName, true)\n        return\n    } \n    \n    if (query.data === 'switch_to_student') {\n        // Role change from teacher to student\n        delete quizFuncs.userQuestions[chatId]\n        dbFunctions.updateUserRole(userId, 'student', () =\u003e {\n            bot.sendMessage(chatId, \"🧑‍🎓 Your role has been changed to student\")\n        })\n        await bot.editMessageReplyMarkup({inline_keyboard: []}, {chat_id: chatId, message_id: messageId})\n        return\n    }\n    \n    if (query.data === 'cancel_change_role') {\n        // Cancelling a role change\n        await bot.sendMessage(chatId, \"🤨 Role change cancelled\")\n        await bot.editMessageReplyMarkup({inline_keyboard: []}, {chat_id: chatId, message_id: messageId})\n        return\n    }\n\n    // Check if there is no current issue, avoid executing the code\n    if (!currentQuestion) {\n        return // If there is no question, we don't do anything further\n    }\n\n    // Processing of responses to questions\n    const answerIndex = parseInt(query.data)\n    const isCorrect = answerIndex === currentQuestion.correct\n\n    const numberQuestion = quizFuncs.userQuestions[userId]\n\n    let questionResult = {}\n    let userProgressJSON\n\n    if (isCorrect) {\n        const correctAnswerText = \"✅ That's the right answer! ✅\"\n        \n        questionResult[numberQuestion] = 1\n        quizFuncs.userProgress.push(questionResult)\n\n        // Determining whether a reply message has been sent or not\n        // it's done by its message_id\n        if (answerMsgIdState.answerMessageId) {\n            // Check if the existing text matches the text that will be added\n            // If they match, an error will occur\n            if (currentMessageText.messageText !== correctAnswerText) {\n                await bot.editMessageText(correctAnswerText, {\n                    chat_id: chatId,\n                    message_id: answerMsgIdState.answerMessageId\n                })\n                currentMessageText.messageText = correctAnswerText\n            }\n        } else {\n            const sentMessage = await bot.sendMessage(chatId, correctAnswerText)\n            answerMsgIdState.answerMessageId = sentMessage.message_id\n            currentMessageText.messageText = correctAnswerText\n        }\n        \n        userProgressJSON = JSON.stringify(quizFuncs.userProgress)\n        dbFunctions.updateProgress(userProgressJSON, userId)\n    } else {\n        const wrongAnswerText = `❌ Wrong answer! The correct answer: ${currentQuestion.options[currentQuestion.correct]} ❌`\n        \n        questionResult[numberQuestion] = 0\n        quizFuncs.userProgress.push(questionResult)\n\n        // Determining whether a reply message has been sent or not\n        // it's done by its message_id\n        if (answerMsgIdState.answerMessageId) {\n            // Check if the existing text matches the text that will be added\n            // If they match, an error will occur\n            if (currentMessageText.messageText !== wrongAnswerText) {\n                await bot.editMessageText(wrongAnswerText, {\n                    chat_id: chatId,\n                    message_id: answerMsgIdState.answerMessageId\n                })\n                currentMessageText.messageText = wrongAnswerText\n            }\n        } else {\n            const sentMessage = await bot.sendMessage(chatId, wrongAnswerText)\n            answerMsgIdState.answerMessageId = sentMessage.message_id\n            currentMessageText.messageText = wrongAnswerText\n        }\n        \n        userProgressJSON = JSON.stringify(quizFuncs.userProgress)\n        dbFunctions.updateProgress(userProgressJSON, userId)\n    } \n    \n\n    await bot.editMessageReplyMarkup({inline_keyboard: []}, {chat_id: chatId, message_id: messageId})\n\n    // Moving on to the next question\n    quizFuncs.userQuestions[chatId] = userIndex + 1\n    await quizFuncs.sendQuestion(chatId, messageId)\n}\n```\n\nThis file was created as a result of unloading the main file (`index.js`)\nIt contains one function - `handleCallbackQuery()`\n\nIn this function absolutely all ***callback requests*** from users are processed, this function is exported, and used in the main project file (`index.js`)\n\nThe comments will help to understand its conditions and cases of its use in more detail, and they will speak for themselves\n\nThe processing of requests in each case tends to keep the same style and format, which will maximally preserve the integrity and identity of the code, which will make it easier to study and work on it\n\nLet's move on\n\n---\n![](readme_files/filesHandlers.gif)\n\nFile `filesHandlers.js`:\n```javascript\nimport fs from 'fs'\nimport path from 'path'\n\n// My scripts\nimport {\n    bot,\n    allQuestions,\n    jsonFileName,\n    uploadFilesDir,\n    addedFile,\n    __dirname\n} from \"../config.js\"\n\n// Teacher uploading .json file with quiz questions\nexport default async function handleFileUpload(dbFunctions, message) {\n    const chatId = message.chat.id\n    const userId = message.from.id\n\n    const fileId = message.document.file_id\n    const fileName = message.document.file_name\n\n    // Checking that the file is .json and that it is a teacher account\n    if (path.extname(fileName) === '.json') {\n        dbFunctions.getUserById(userId).then(async function (user) {\n            if (!user || user.role !== 'teacher') {\n                return bot.sendMessage(chatId, '❗️ Only teachers are allowed to upload files! ❗️')\n            }\n\n            try {\n                // Download the file that the user uploaded to us\n                // then save this file in the /uploaded_files/\n                const downloadedPath = await bot.downloadFile(fileId, uploadFilesDir)\n                const renamedPath = path.join(uploadFilesDir, fileName)\n                fs.renameSync(downloadedPath, renamedPath)\n                \n                // Search for the file by the new name (the new name is the same as the one with which the user uploaded it)\n                // and parsing it for further use of data from it\n                fs.readFile(renamedPath, 'utf8', (error, data) =\u003e {\n                    if (error) {\n                        return bot.sendMessage(chatId, '❗️ Error during reading a file ❗️')\n                    }\n\n                    try {\n                        const json_file = JSON.parse(data)\n\n                        allQuestions.questions = json_file.questions\n\n                        jsonFileName[chatId] = fileName\n\n                        bot.sendMessage(chatId, '🔥👍 The file with questions has been uploaded successfully! To start the quiz, please write /can_start_quiz')\n                        addedFile.addedJsonFile = true\n                    } catch (parseError) {\n                        console.log('Error parsing JSON:', parseError.message)\n                        bot.sendMessage(chatId, '❗️ Error parsing JSON file. Check the file format is correct ❗️')\n                    }\n                })\n            } catch (downloadError) {\n                console.log('Error downloading file:', downloadError.message)\n                bot.sendMessage(chatId, '❗️ Error downloading the file ❗️')\n            }\n        })\n    } else {\n        bot.sendMessage(chatId, '❗️ You can only upload a .json file for the quiz! ❗️')\n    }\n}\n```\n\nThis file handles the file loading, to be more specific, it handles the loading of the `.json` file, parses it, and executes the necessary functionality so that the test can start\n\nSimilarly with the `handleCallbackQuery()` function.\nThe `handleFileUpload()` function is exported, and called in the main project file (`index.js`)\n\nHere, as well as in the rest of the files, logging is maximally specified, which will make it easier to find errors\nAs you can see, the construct:\n```javascript\ntry {\n    console.log(“PERFECT”)\n} catch (error) {\n    console.log(“ERROR”)\n}\n```\n\nMakes life much easier during development\n\nFinally, the last file in this directory\n\n---\n![](readme_files/quizFunctional.gif)\n\nFile `quizFunctional.js`:\n```javascript\nimport fs from 'fs'\nimport path from 'path'\n\n// My scripts\nimport {\n    bot,\n    answerMsgIdState, \n    completedQuizzes, \n    jsonFileName, \n    addedFile,\n    allQuestions, \n    __dirname\n} from \"../config.js\"\n\n// Indexes of current issues for each user\nexport const userQuestions = {}\nexport let userProgress = []\n\n// Function for sending a question with inline buttons\nexport async function sendQuestion(chatId, messageId) {\n    try {\n        const userIndex = userQuestions[chatId] || 0\n    \n        if (userIndex \u003c allQuestions.questions.length) {\n            const currentQuestion = allQuestions.questions[userIndex]\n    \n            // Generate options for the inline keyboard under the message\n            const options = currentQuestion.options.map((option, index) =\u003e {\n                return [{text: option, callback_data: String(index)}]\n            })\n\n            if (userIndex === 0) {\n                // Sending a message with an inline keyboard\n                await bot.sendMessage(chatId, currentQuestion.question, {\n                    reply_markup: {\n                        inline_keyboard: options\n                    }\n                })\n            } else {\n                await bot.editMessageText(currentQuestion.question, {\n                    chat_id: chatId,\n                    message_id: messageId,\n                    reply_markup: {\n                        inline_keyboard: options\n                    }\n                })\n            }\n\n        } else {\n            let allQuestions = userProgress.length\n            let allCorrectAnswers = 0\n\n            userProgress.forEach((question) =\u003e {\n                let objectValues = Object.values(question)\n\n                allCorrectAnswers = allCorrectAnswers + parseInt(objectValues)\n            })\n\n            await bot.sendMessage(chatId, `✋🛑 The test is over! \\n\\n👉 Number of questions: ${allQuestions} \\n\\n👉 The number of correct answers: ${allCorrectAnswers} \\n\\nThank you for all your answers! 🤗`)\n            \n            // Resetting the status for the user\n            delete userQuestions[chatId]\n            userProgress = []\n            answerMsgIdState.answerMessageId = null\n\n            completedQuizzes[chatId] = true\n            addedFile.addedJsonFile = false\n            \n            const copiedFileName = jsonFileName[chatId]\n\n            // Deleting a copy of the .json file with questions from the teacher\n            const filePath = path.join(__dirname, 'uploaded_files', copiedFileName)\n\n            fs.unlink(filePath, (error) =\u003e {\n                if (error) {\n                    console.error(error)\n                }\n            })\n        }\n    } catch (error) {\n        console.log(error)\n    }\n}\n```\n\nAt the moment, there is only one function defined in this file, and that is `sendQuestion()`\nThis function is responsible for sending each new question to the student, \nThis function, traditionally for this project, has logging\n\nHere it handles whether it is the first question or not, and whether it is the first answer to the question or not\n\n- ***If it is the first question*** - a new message is sent.\n- ***If it is the second question*** - it modifies the existing one (including the inline keyboard)\n\n- ***If it was the first answer to a question*** - a new message is sent to indicate whether the answer is correct or not\n- ***If it is the second, third, fourth, etc. answer to the question*** - the existing message is modified\n\nThis functionality is designed to make the quiz compact, in just two messages, \nand not in dozens of posts as it was before, because it is logically not convenient\n\nAlso, when the quiz is completed, the results of the quiz are calculated here, and a corresponding message about the results is displayed\n\nAlso, when the quiz is finished, the `.json` file that contained the data for the past quiz is deleted\n\n\n---\n### End of documentation\n\u003e[Back to top](#quizizz-telegram-bot)\n\n\u003e[Back to top of the documentation](#documentation)\n\nAt the moment, this is all the documentation for this project, as I develop this project, and as your improvements and contributions, the documentation will expand\n\nIt is quite possible that even on the **platform** itself there will be documentation in a more convenient form, but this is in the not too distant or maybe even distant future, depending on my busyness\n\nAlso, describing changes to `readme.md` after your changes, in the **[Documentation](#documentation)** block, is also welcome\nHowever, in any case, your changes after they are added will be spelled out in the documentation, the question is - by me, or by you\n\nI hope you enjoyed reading the documentation of this project, and I wish you a good work and familiarization with this project. Have a nice day!\n\n---\n## Contacts\n\u003e[Back to top](#quizizz-telegram-bot)\n\nLet's find out what to do if my **documentation** didn't help you or didn't give you a sufficient answer\nIn such a case, it would be useful to get help from the person who directly developed this project for which you have **documentation**\nThere is such a possibility, and this is my **[community](https://t.me/feliks_novo)**\n\nThis **[community](https://t.me/feliks_novo)** discusses not only this project, but my other projects as well\nSo, join and learn something new for yourself! 😄\n\n#### We will be glad to see you!\n\n---\n## Project future\n\u003e[Back to top](#quizizz-telegram-bot)\n\nAlso, the project has some pretty big plans for the future:\n- Platform design project\n- Detailed structure and logic of both the **platform** and the **bot** in the form of a diagram\n- Adaptability of the **platform** for all kinds of devices\n- More detailed quiz results (Bot)\n- Saving of quiz results, linked to teacher's account (Bot)\n- Ability to change password for teacher (Bot)\n- Administrator account in the **bot**\n- Registration and authorization on the **platform**\n- Modern validation of authorization and registration on the **platform**\n- Adaptability of the **bot** for groups, with the ability to universally conduct quiz for a large number of group members at the same time. At the same time retaining its functionality in private messages\n- Improved user interface for creating quiz on the **platform**\n- Different types of questions, both for creation (**platform**) and for passing (**bot**). For example - on inputting information, not just clicking buttons\n- Fully finalize `readme.md` file\n- Create Ukrainian copy of `readme.md` file\n- Hosting **bot** and **platform**\n\nAs you can see, the project has a great future\n\nWhat I'm saying is that what we have now is only the beginning\n\n\u003e**Further - more**\n\n\u003e**Further - better 😊**\n\n---\n## Want to get back to the top?\n\u003e[Back to top](#quizizz-telegram-bot)\n# Enjoy using it!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeliksnovoselskyi%2Fquizizzbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeliksnovoselskyi%2Fquizizzbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeliksnovoselskyi%2Fquizizzbot/lists"}