├── .gitignore ├── LICENSE ├── README.md ├── data ├── category_math.png ├── gif_working.gif ├── image.png ├── image_2.png ├── image_3.png ├── image_4.png ├── image_5.png ├── image_6.png ├── image_7.png ├── image_8.png ├── image_9.png ├── math_1.jpg ├── math_2.jpg ├── schem_flashcards.png ├── stats_category_math.png ├── stats_flashcards.png ├── stats_mentally_math.png └── text_scipio.jpg ├── data_b ├── data_transfer_json.py ├── dp_control.py ├── parsing │ ├── async_parsing.py │ ├── download_img.py │ └── references_to_tasks.py └── scipio.db ├── handlers ├── admins │ ├── admins.py │ ├── delete_tasks.py │ ├── send_message_all.py │ └── statistics_info_admins.py ├── cmd.py ├── flashcards │ ├── base.jpg │ ├── create_flashcard_photo.py │ ├── flashcard.py │ ├── flashcards_managing.py │ ├── flashcards_training.py │ └── flc_users │ │ └── pillow.ttf ├── keyboards │ ├── default │ │ ├── admin_menu.py │ │ ├── flashcard_menu.py │ │ ├── logic_menu.py │ │ ├── math_menu.py │ │ ├── statistics_menu.py │ │ └── timer_menu.py │ └── inline │ │ ├── logic_menu_inline.py │ │ └── math_menu_inline.py ├── logic │ ├── logic.py │ └── tasks_category_logic.py ├── math │ ├── math.py │ ├── math_formulas.py │ ├── mentally_math.py │ └── tasks_category_math.py ├── register_cmd.py ├── statistics │ ├── charts.py │ ├── statistics.py │ └── statistics_info.py └── timer │ ├── timer.py │ ├── timer_cycle.py │ └── timer_managing.py ├── main.py ├── middlewares ├── __init__.py └── throttling.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /venv 2 | .idea/* 3 | config.py 4 | __pycache__/ 5 | /shit 6 | handlers/cart.py 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |
5 | 6 | 7 | 8 | 9 |
10 | 11 |

ScipIO

12 |
Обучающий телеграм бот
13 | 14 | ## Оглавление 15 | - [Описание](#Описание) 16 | - [Инструменты](#Инструменты) 17 | - [База Данных](#База_Данных) 18 | - dp_control 19 | - [Flashcards](#Flashcards) 20 | - [Category math](#Category_math) 21 | - [Mentaly math](#Mentaly_math) 22 | - [Timer](#Timer) 23 | - [Statistics](#Statistics) 24 | - [admin](#admin) 25 | - [Дополнительное](#Дополнительное) 26 | - [Установка](#Установка) 27 | - [Полезные ссылки](#Полезные_ссылки) 28 | - [Благодарности](#Благодарности) 29 | - [Заключение](#Заключение) 30 | 31 | ## Описание 32 |

33 | 34 | Scipio - это telegram-bot, который позволяет пользователям решать задачи по математике, логике, создавать свои собственные карточки - в общем, обучаться. 35 | 36 | В боте представленны следующие алгоритмы: 37 | 38 | 1. **Mentally_math(ment_math)** - Математические примеры для подсчёта в уме. 39 | 40 | 2. **Categoty_marh(cat_math)** - Математические задачи. Категории (Алгебра, Текстовые задачи, Тригонометрия, Вероятность и т.д) 41 | 42 | 3. **Category_logic(cat_logic)** - Задачи на логику (данетки, загадки, логические задачи) 43 | 44 | 4. **Flashcards(flc)** - Пользовательские карточки для обучения 45 | 46 | 5. **Timer** -Пользовательское время для напоминания от бота о тренировках (flc, cat_math, cat_logic, ment_math) 47 | 48 | 6. **Statistics(stat)** - пользовательская статистика 49 | 50 | 7. **Admin** - просмотри статистики о количестве пользователей для администраторов 51 | 52 | # Инструменты 53 | 54 | - **aiogram - основной инстумент для разработки** 55 | - sqlite - бд 56 | - matplotlib - для графиков 57 | - APScheduler - для таймера 58 | - PIL - для рисования flc 59 | - BeautifulSoup - для парсинга задач 60 | 61 | ## База Данных 62 | ### База данных состоит из: 63 | 64 | | Таблица | Описание | 65 | | ------------- | ------------- | 66 | | actions | Таблица действий пользователя. Например при выполнение flc, выполняется функция *action_add(message.from_user.id, 'flc')*, которая добавляет данные в таблицу | 67 | | category | Категории заданий | 68 | | flashcards |Карточки пользователей | 69 | | task_logic | Задачи по категории логика | 70 | | task_math | Задачи по категории математика | 71 | | time | Таймера пользователей | 72 | | users | Все пользователи, пользующиеся ботом | 73 | 74 | ### Структура самих таблиц: 75 | 76 | 1. **actions** 77 |

78 | 79 | 2. **category** 80 |

81 | 82 | 3. **flashcards** 83 |

84 | 85 | 4. **task_math** (и в task_logic одинаковые структруры) 86 |

87 | 88 | 5. **time** 89 |

90 | 91 | 6. **Users** 92 |

93 | 94 | > **Почему у нас есть 2 одинаковые по стоению таблицы task_logic и task_math?** 95 | > Потому что таким образом мы решили отделить задачи разных алгоритмов. Это намного удобнее, чем хранить все в одной таблице. 96 | 97 | ### dp_control 98 | 99 | Все функции для работы с бд хранятся в данном файле. Файл имеент чёткую иерархию: 100 | 101 | 102 | 103 | ## Flashcards 104 | Суть данной алгоритма заключаеться в том чтобы создавать карточки и в будующем проходить их. 105 | 106 | Основная идея данного алгоритма в том, что из-за того что всё происходит в основной функции, то при нажатии "Правильно" и "Неправильно" следующее что делает функция fls_game, это создаёт новую flashcard и вызывает саму себя. Из-за этого алгоритм после "Правильно"/"Неправильно" сразу создаёт новую flashcard и показывает её, не требуя ввести сообщение от пользователя. 107 | 108 |

109 | 110 | **Основной алгоритм.** Функция гененирует карточку, присылает пользователю фото карточки и создаёт кнопки, а дальше вызывает саму себя, и ждёт следующих действий от пользователя: 111 | 112 | 113 | 1. *"Обратная сторона".* Тогда вызывается функция (flc_game_reverse_side), которая срабатывает 114 | поверх функции flc_game. Она отправляет пользователю обратную сторону карточки и выключается, STATE оно не меняет! 115 | (flc_game) дальше ждёд действий от пользователя. 116 | 117 | 2. "Правильно" или "Неправильно". При нажатии на кнопку "Правильно" - пользователю при прохождении дальнейшей тренировки больше не будет высвечиватся это карточка (карточка удаляется из user_data['flashcards']). При нажатии на "Неправильно" - эта карточка при тренировке ещё БУДЕТ показываться 118 | 119 | 3. *"Закончить"*. Вызывает функцию (flc_game_end), которая присылвает статистику пользователю и соответственно 120 | заканчивает тренировку. 121 | 122 | Статистика: 123 | 124 |

125 | 126 | ## Category math 127 | Основная идея алгоритма в том чтобы отправлять задачи пользвателю после того как он нажмет "Правильно" или "Неправильно". 128 | 129 |

130 | 131 | 132 | Сначал пользователю выберает основной категории (tasks_category_math_start), после пользователь может выбрать подкатегорию(функция: one_tasks_category), если подкатегории нет, то пользователю сразу присылется задача. 133 | 134 | Основной алгоритм: 135 | 136 | 1. Выбор основной категории. Функция: tasks_category_math_start 137 | 2. Проверка есть ли подкатегория. 138 | * 2.1) Если подкатегории нет, то задача отправляется сразу. 139 | 140 | Функция: tasks_category_math_print_keyboard_inline 141 | * 2.2) Если подкатегория есть, то пользователь выбирает подкатегорию. Функция: one_tasks_category 142 | 3. После выбора подкатегории, пользователю отправляется первая задача. Функция: tasks_category_math_print_keyboard_inline 143 | 4. Когда пользователь ответит "Правильно" или "Неправильно" то вызывается функция: tasks_category_math_print_keyboard_default 144 | 5. Чтобы закончить решение задач, пользователь может в боте нажать на кнопку "Закончить математикут. 145 | 6. После того как пользователь закончил тренировку, ему высылается статистика: 146 | 147 | 148 |

149 | 150 | Были созданы две функции по отправке задач. 151 | 152 | - *Первая функция* - tasks_category_math_print_keyboard_inline, отправляет задачу после нажатия на инлайн кнопку выбора категории. 153 | - *Вторая функция* - tasks_category_math_print_keyboard_default, отправляет задачу после нажатия на кнопки: "Правильно", "Неправильно" 154 | 155 | 156 | Эти две функциии очень схожи, разница только в запуске этих функций. 157 | Подробное описание работы данных функций 158 | 159 | 160 | Из базы данных рандомно выбирается задача. После этого создается сообщение в котором будет: id или название задачи, ссылка на задачу, подкатегория, сложность, для каких классов эта задача. 161 | Если это сообщение создается без ошибки то пользователю отправляется это сообщение, при ошибке эта задача автоматически удаляется из базы данных и пользователя предупреждают об ошибке. 162 | 163 | 164 | ## Mentaly math 165 | Основной алгоритм очень схож с (flashcards_training), но у него есть некоторое отличие.Так как тут требуется постоянно проверять ответ ввод пользователя из за этого алгоритм выглядит следующим образом. Есть вводная функция (equation_mentally_beginning), которая: 166 | 167 | 1. Проверяет, что написали "Да" (пользователь готов к тренировке) 168 | 2. Создаёт user_data (в которой храниться: условие, ответ, количество попыток) 169 | 3. Создаёт пример и создаёт вход в главную функцию (equation_mentally) 170 | 171 | Потом уже функция (equation_mentally), генерирует примеры; отсекает неправильные варианты; и вызывает САМУ СЕБЯ. 172 | 173 | **Работа основного алгоритма(equation_mentally):** 174 | Сначала введенное значение проверяется на то что оно является числом. После этого проверяется ввденный ответ с правильным.Это делается с помощью state в который мы ранее записали условие ответ и количество попыток. Елси пользователь ответит более 3 раз неверно, то ему предлагается прочитать подсказки(/mell_theory). Когда пользовтель решит закончить тренировку ему присылается статистика: 175 | 176 |

177 | 178 | ## Timer 179 | Алгоритм таймера работает следующим образом: 180 | Раз в 60 секунд запускается проверка, есть ли данное время (например 16:03) в базе данных time и если есть то выводит "Ежедневное задание" для пользователя. Пока она ждёт, она работает ассинхронно, поэтому ничего просто так не стоит. 181 | 182 | > **Почему мы пользуемся библиотекой apscheduler, если мы просто вручную можем прописать `asyncio.sleep(60)`?** 183 | 184 | > Это необходимо для того, чтобы одновременно с запуском бота запускался ассинхронный таймер, иначе приходиться запускать таймер вручную с помощью команды (в одной из первых версий бота, был реализован такой 'плохой' таймер, вы можете посмотреть на него в версии *d2ab674090b93ed318933e69b95c612a3423dd4a*) 185 | 186 | ## Statistics 187 | 188 | Статистика пользоватлей сделанна так: 189 | 190 | - В таблицу actions добавляются действия пользователя, такие как flc, cat_math, cat_logic, ment_math 191 | 192 | - Пользователь запускает /statistics, которое показывает: 193 | - Количество показов flc, cat_math, cat_logic, ment_math пользователя за всё время 194 | - Составляет круглую диаграмму по данным выше. Функция `pie_chart` 195 | 196 |

197 | - Составляет диаграмму bar действий разных типов actions пользователя за неделю 198 |

199 | 200 | 201 | Все диаграммы реализованны с помощью matplotlib. 202 | 203 | 204 | ## admin 205 | 206 | Данный алгоритм имеет функционал: 207 | 208 | 1. **Отправка сообщения всем пользователям**. Алгоритм отправляет определённое сообщение, которое потом циклом (с задержкой 0.1 секунда, это сделанно навсякий случай) отправляется каждому пользователю таблицы *Users*. 209 | 210 | 3. **Показ статистики о всех и о новых пользователях за определённое время**. Алгоритм реалезован также с помощью таблицы users, которая сохраняет дату регистрации пользователя в бд. 211 | 212 | 213 | 214 | ## Дополнительное 215 | 216 | ### register_cmd 217 | Чтобы в файле main не прописывать каждый `register_handler`, все данные функции вынесены в одельный файл. 218 | 219 | 220 | ### Антифлуд 221 | Отличается от официального [aiogram примера](https://docs.aiogram.dev/en/latest/examples/middleware_and_antiflood.html), только лишь тем, что, если сообщение отправленно меньше, чем за 1 секунду, то пользователь блокируется ровно на 5 секунд. 222 | 223 | 224 | ## Установка 225 | Для начала вам потребуется получить bot api от **@BotFather**. 226 | 227 | После чего вам нужно будет создать файл **config** в который вы поместите bot api и id админов телеграм бота. 228 | 229 | Для запуска бота вам нужно будет его скачать с [нашего github](https://github.com/AndrewKentavr/ScipIO). Можно скачать zip файл и распаковать его, или вы можете сделать forc таким образом скопировав наш репозиторий себе. 230 | 231 | **Config:** 232 |

233 | 234 | 235 | ## License 236 | Распростаняеться по лицензии GPLv3. Откройте LICENSE для получения допольнительной информации. 237 | 238 | ## Полезные ссылки 239 | 240 | Есть список полезных ссылок от официального [aiogram](https://telegra.ph/aiogram-ru-01-28) сообщества и от [MasterGroosha](https://telegra.ph/faq-02-07-6) 241 | 242 | - [Вводная статья на хабре про ботов](https://habr.com/ru/post/543676) 243 | - [Отличная статья про многопоточность и многопроцессорность на анг](https://www.geeksforgeeks.org/difference-between-multithreading-vs-multiprocessing-in-python) 244 | - [Официальная документация про многопот. и многопроц.](https://docs.python.org/3/library/multiprocessing.html?source=post_page-----48b59bfcc89e----------------------) 245 | - [Парсинг сайтов на Python: подробный видеокурс ](https://www.youtube.com/watch?v=AFqYxaOyqy0&list=PLqGS6O1-DZLprgEaEeKn9BWKZBvzVi_la) 246 | - [Асинхронность в Python](https://botfather.dev/blog/async-in-python#asyncio-lib) 247 | - [Асинхронное программирование в Python: краткий обзор](https://habr.com/ru/company/ruvds/blog/475246) 248 | - [Учебник по git](https://githowto.com/ru) 249 | 250 | 251 | #### **Aiogram**: 252 | - [Mastergroosha - лучшая статья](https://mastergroosha.github.io/telegram-tutorial-2) 253 | - [Плейлист tutorial по aiogram (Physics is Simple)](https://www.youtube.com/watch?v=wj1Vwq3IrL4&list=PLwVBSkoL97Q3phZRyInbM4lShvS1cBl-U) 254 | - [Официальные примеры aiogram docs](https://docs.aiogram.dev/en/latest/examples/index.html) 255 | - [Как сделать функции таймера в боте](https://botfather.dev/blog/zapusk-funkczij-v-bote-po-tajmeru) 256 | 257 | #### Чаты 258 | - [Официальный чат aiogram](https://t.me/aiogram_ru), где сидит главный разработчик 259 | - [Чат MasterGroosha](https://t.me/joinchat/TsftDfnevFLQS1ts) 260 | 261 | 262 | ## Заключение 263 | 264 | Отдельно хочется выразить благорарность MasterGroosha и сообществу aiogram, без них этого проекта не было бы. 265 | 266 | - Андрей Тощаков - создатель проекта, основной разработчик (ник в telegram: @Endrey_k) 267 | - Ахмед Шагбанов - основной разработчик (ник в telegram: @ShagbanovAhmed) 268 | 269 | По всем вопросам можете писать или AndrewKentavr или MrDoberman -------------------------------------------------------------------------------- /data/category_math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/category_math.png -------------------------------------------------------------------------------- /data/gif_working.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/gif_working.gif -------------------------------------------------------------------------------- /data/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image.png -------------------------------------------------------------------------------- /data/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image_2.png -------------------------------------------------------------------------------- /data/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image_3.png -------------------------------------------------------------------------------- /data/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image_4.png -------------------------------------------------------------------------------- /data/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image_5.png -------------------------------------------------------------------------------- /data/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image_6.png -------------------------------------------------------------------------------- /data/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image_7.png -------------------------------------------------------------------------------- /data/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image_8.png -------------------------------------------------------------------------------- /data/image_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/image_9.png -------------------------------------------------------------------------------- /data/math_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/math_1.jpg -------------------------------------------------------------------------------- /data/math_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/math_2.jpg -------------------------------------------------------------------------------- /data/schem_flashcards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/schem_flashcards.png -------------------------------------------------------------------------------- /data/stats_category_math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/stats_category_math.png -------------------------------------------------------------------------------- /data/stats_flashcards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/stats_flashcards.png -------------------------------------------------------------------------------- /data/stats_mentally_math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/stats_mentally_math.png -------------------------------------------------------------------------------- /data/text_scipio.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data/text_scipio.jpg -------------------------------------------------------------------------------- /data_b/data_transfer_json.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import json 4 | from data_b.dp_control import get_cursor 5 | 6 | all_files_names = os.listdir(path="C:/Users/andrt/PycharmProjects/ConTia/data_b/json") 7 | 8 | 9 | def transfer_data_json(): 10 | for file_name in all_files_names: 11 | print('------------------------------------------') 12 | print(file_name) 13 | 14 | with open(f'C:/Users/andrt/PycharmProjects/ConTia/data_b/json/{file_name}', 15 | encoding='utf-8') as f: 16 | templates = json.load(f) 17 | count = 0 18 | for example_info in templates.values(): 19 | task_info = {} 20 | keys = [i for i in templates.keys()] 21 | 22 | for i in example_info: 23 | if 'Условие' in i: 24 | task_info['conditions'] = i 25 | elif 'Решение 2' in i: 26 | task_info['decisions_2'] = i 27 | elif ('Решение' in i) or ('Решение 1' in i): 28 | task_info['decisions_1'] = i 29 | elif 'Ответ' in i: 30 | task_info['answer'] = i 31 | elif ('Подсказка' in i) or ('Замечания' in i): 32 | task_info['remarks'] = i 33 | convert_list = converter_string(task_info) 34 | # print(convert_list[0]) 35 | # print(convert_list[1]) 36 | 37 | file_name = file_name.split('.json')[0] 38 | 39 | cur = get_cursor() 40 | 41 | info = cur.execute(f'''SELECT id FROM category 42 | WHERE value = "{file_name}";''') 43 | for i in info: 44 | id_category = i[0] 45 | 46 | print(keys[count]) 47 | 48 | cur.execute( 49 | f'''INSERT INTO tasks (id_category, title, href, subcategory, complexity, classes, {convert_list[0]}) 50 | VALUES ("{id_category}", "{keys[count]}", "{example_info[0]}", "{example_info[1]}", "{example_info[2]}", "{example_info[3]}", {convert_list[1]});''') 51 | cur.connection.commit() 52 | 53 | count += 1 54 | 55 | 56 | def converter_string(task_info): 57 | all_strings = [] 58 | string_insert = '' 59 | string_values = '' 60 | 61 | tk_0 = [i for i in task_info.keys()] 62 | 63 | tk_1 = [i for i in task_info.values()] 64 | 65 | for i in range(len(tk_0)): 66 | if i + 1 == len(tk_0): 67 | string_insert += f'{tk_0[i]}' 68 | else: 69 | string_insert += f'{tk_0[i]}, ' 70 | 71 | for i in range(len(tk_1)): 72 | if i + 1 == len(tk_1): 73 | string_values += f'"{tk_1[i]}"' 74 | else: 75 | string_values += f'"{tk_1[i]}", ' 76 | all_strings.append(string_insert) 77 | all_strings.append(string_values) 78 | return all_strings 79 | 80 | 81 | if __name__ == '__main__': 82 | transfer_data_json() 83 | -------------------------------------------------------------------------------- /data_b/dp_control.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import sqlite3 3 | 4 | import pytz 5 | 6 | CONN = sqlite3.connect('data_b/scipio.db') 7 | cur = CONN.cursor() 8 | 9 | 10 | def get_cursor(): 11 | return cur 12 | 13 | 14 | # -----------------------------Users----------------------------------------- 15 | def dp_all_users_list(): 16 | cur.execute("""SELECT telegram_user_id FROM users;""") 17 | result = cur.fetchall() 18 | users_telegram_id_list = [i[0] for i in result] 19 | return users_telegram_id_list 20 | 21 | 22 | def dp_user_create(telegram_user_id): 23 | cur.execute(f"""INSERT INTO users (telegram_user_id, date_reg, flc_show) 24 | VALUES ({telegram_user_id}, '{datetime.datetime.now()}', 0);""") 25 | cur.connection.commit() 26 | return 27 | 28 | 29 | # -----------------------------ANYTHING----------------------------------------- 30 | 31 | 32 | def dp_all_telegram_id_flc_list(): 33 | cur.execute("""SELECT DISTINCT user_id FROM flashcards;""") 34 | result = cur.fetchall() 35 | flashcards_telegram_id_list = [i[0] for i in result] 36 | return flashcards_telegram_id_list 37 | 38 | 39 | def dp_all_telegram_id_time_list(): 40 | cur.execute("""SELECT DISTINCT user_id FROM time;""") 41 | result = cur.fetchall() 42 | time_telegram_id_list = [i[0] for i in result] 43 | return time_telegram_id_list 44 | 45 | 46 | def problem_category_random(name_category, tasks_theme): 47 | """ 48 | :param name_category: Название категории вида: 'fractions' 49 | :param tasks_theme: Передаётся название таблици 50 | из которой будут брать задачи. Например: 'math' 51 | 52 | :return: Вся информация В СЛОВАРЕ, что есть по задаче. Например в задаче 53 | 2 Условия и Ответ. Значит так и будет передоваться 54 | """ 55 | cur.execute( 56 | f"""SELECT id, title, href, subcategory, complexity, classes, conditions, decisions_1, 57 | decisions_2, answer, remarks FROM tasks_{tasks_theme} 58 | WHERE id_category = (SELECT id FROM category 59 | WHERE value = '{name_category}') 60 | ORDER BY RANDOM() 61 | LIMIT 1;""") 62 | columns = ['id', 'title', 'href', 'subcategory', 'complexity', 'classes', 'conditions', 'decisions_1', 63 | 'decisions_2', 'answer', 'remarks'] 64 | result_0 = cur.fetchall() 65 | result = {} 66 | 67 | for i in range(len(result_0[0])): 68 | if result_0[0][i] is not None: 69 | result[columns[i]] = result_0[0][i] 70 | else: 71 | result[columns[i]] = '' 72 | 73 | return result 74 | 75 | 76 | def finding_categories_table(tasks_theme): 77 | cur.execute(f"""SELECT value, translate_category FROM category 78 | WHERE id in (SELECT DISTINCT id_category FROM tasks_{tasks_theme});""") 79 | result_0 = cur.fetchall() 80 | return result_0 81 | 82 | 83 | def finding_main_categories_table(tasks_theme): 84 | cur.execute(f"""SELECT main_value, main_translate_category FROM category 85 | WHERE id in (SELECT DISTINCT id_category FROM tasks_{tasks_theme});""") 86 | result_0 = set(cur.fetchall()) 87 | return result_0 88 | 89 | 90 | def finding_one_categories_table(tasks_theme): 91 | cur.execute(f"""SELECT value, translate_category FROM category 92 | WHERE main_value = '{tasks_theme}';""") 93 | result_0 = cur.fetchall() 94 | return result_0 95 | 96 | 97 | def del_task(name_task, category): 98 | """ 99 | Удаление задачи из admins 100 | :param name_task: Название задачи или id 101 | :param category: категория задачи('Математика' и т.д) 102 | """ 103 | if str(category) == 'Математика': 104 | cur.execute(f"""DELETE FROM tasks_math WHERE title = {name_task};""") 105 | else: 106 | try: 107 | int(name_task) 108 | cur.execute(f"""DELETE FROM tasks_logic WHERE id = {name_task};""") 109 | except: 110 | cur.execute(f"""DELETE FROM tasks_logic WHERE title = '{name_task}';""") 111 | cur.connection.commit() 112 | return 113 | 114 | # -----------------------------MATH----------------------------------------- 115 | 116 | 117 | def problem_search_random(): # <-------- Эта функция вообще где-то применяется? 118 | cur.execute(f"""SELECT * FROM math_problems 119 | ORDER BY RANDOM() LIMIT 1;""") 120 | result = cur.fetchall() 121 | return result[0] 122 | 123 | 124 | def formulas_search_random(): 125 | cur.execute(f"""SELECT formulas, answer, explanation FROM math_formulas 126 | ORDER BY RANDOM() LIMIT 1;""") 127 | result = cur.fetchall() 128 | return result[0] 129 | 130 | 131 | # -----------------------------LOGIC----------------------------------------- 132 | # -----------------------------FLASHCARD----------------------------------------- 133 | def flashcard_dp_create(user_id, front, back, show): 134 | cur.execute(f"""INSERT INTO flashcards (user_id, front_card, back_card, show_card) 135 | VALUES ({user_id}, '{front}', '{back}', {show});""") # Без этого новые карточки не сохранялись 136 | cur.connection.commit() 137 | return 138 | 139 | 140 | def flashcard_dp_info(user_id): 141 | cur.execute(f"""select id, front_card, back_card from flashcards 142 | where user_id = {user_id};""") 143 | result = cur.fetchall() 144 | return result 145 | 146 | 147 | def flashcard_dp_info_game(user_id): 148 | cur.execute(f"""select id, front_card, back_card, show_card from flashcards 149 | where user_id = {user_id}""") 150 | result = cur.fetchall() 151 | return result 152 | 153 | 154 | def flashcard_del_check(card_id): 155 | cur.execute(f"""select count(*) from flashcards 156 | where id = {card_id};""") 157 | result = cur.fetchall() 158 | if result[0][0] == 0: 159 | return False 160 | return True 161 | 162 | 163 | def flashcard_one(user_id, id): 164 | cur.execute(f"""select id, front_card, back_card from flashcards 165 | where user_id = {user_id} and id = {id};""") 166 | result = cur.fetchall() 167 | return result 168 | 169 | 170 | def flashcard_del(user_id, front, back): 171 | cur.execute(f"""DELETE FROM flashcards 172 | where user_id = {user_id} and front_card = '{front}' and back_card = '{back}';""") 173 | cur.connection.commit() 174 | return 175 | 176 | 177 | def flashcard_setting_photo_text(telegram_user_id, photo_text): 178 | """ 179 | Настройка показа flc 180 | :param photo_text: bool значение. "1" - Фото; "0" - Текст 181 | """ 182 | cur.execute(f"""UPDATE users SET flc_show = {photo_text} WHERE 183 | telegram_user_id = {telegram_user_id};""") 184 | cur.connection.commit() 185 | return 186 | 187 | 188 | def flashcard_check_show(telegram_user_id): 189 | cur.execute(f"""SELECT flc_show FROM users WHERE telegram_user_id = {telegram_user_id}""") 190 | flc_show = cur.fetchall()[0][0] 191 | return flc_show 192 | 193 | # -----------------------------TIMER----------------------------------------- 194 | 195 | def timer_create_dp(user_id, time, tasks): 196 | cur.execute(f"""INSERT INTO Time (user_id, time, tasks) 197 | VALUES ({user_id}, '{time}', '{tasks}');""") 198 | cur.connection.commit() 199 | return 200 | 201 | 202 | def timer_del_dp(user_id, time): 203 | cur.execute(f"""DELETE FROM Time 204 | where user_id = {user_id} and time = '{time}';""") 205 | cur.connection.commit() 206 | return 207 | 208 | 209 | def timer_info_dp(user_id): 210 | cur.execute(f"""SELECT time FROM Time 211 | where user_id == {user_id};""") 212 | c = cur.fetchall() 213 | all_timers = list(map(lambda x: x[0], c)) 214 | return all_timers 215 | 216 | 217 | def dp_timer_circle_user_time(time_now): 218 | cur.execute(f"""SELECT user_id, tasks FROM Time 219 | where time == '{time_now}';""") 220 | results = cur.fetchall() 221 | return results 222 | 223 | 224 | def del_user(user_id): 225 | cur.execute(f""" 226 | DELETE FROM time WHERE user_id = {user_id}; 227 | """) 228 | cur.connection.commit() 229 | return 230 | 231 | 232 | # -----------------------------add_action----------------------------------------- 233 | def action_add(telegram_user_id, action, correct=None, id_category=None): 234 | """ 235 | :param action: 'flc', 'mentally_math', 'cat_logic', 'cat_math' 236 | :param correct: добавляется для flashcard и mentally_math 237 | :param id_category: нужно только когда это задача из category 238 | """ 239 | 240 | # ---------Данный алгоритм, лишь на короткое время------------- 241 | all_users_list = dp_all_users_list() 242 | if telegram_user_id not in all_users_list: 243 | dp_user_create(telegram_user_id) 244 | # ------------------------------------------------------------- 245 | 246 | if id_category == None: 247 | id_category = 'Null' 248 | 249 | if correct == None: 250 | correct = 'Null' 251 | 252 | cur.execute(f"""INSERT INTO actions (telegram_user_id, action, correct, time_action, id_category) 253 | VALUES ({telegram_user_id}, '{action}', {correct}, '{datetime.datetime.now()}', {id_category});""") 254 | cur.connection.commit() 255 | return 256 | 257 | 258 | # -----------------------------statistics----------------------------------------- 259 | def stat_general_bd(telegram_user_id): 260 | """ 261 | :param 262 | :return: info[0] - количество показов flashcard(flc) 263 | info[1] - количество попыток mentally_math 264 | info[2] - количество показов category_math 265 | info[3] - количество показов category_logic 266 | """ 267 | cur.execute(f"""SELECT count(*) AS flc, 268 | (SELECT count(*) FROM actions WHERE action='men_math' and telegram_user_id={telegram_user_id}) AS men_math, 269 | (SELECT count(*) FROM actions WHERE action='cat_math' and telegram_user_id={telegram_user_id}) AS cat_math, 270 | (SELECT count(*) FROM actions WHERE action='cat_logic' and telegram_user_id={telegram_user_id}) AS cat_logic 271 | FROM actions WHERE action='flc' and telegram_user_id={telegram_user_id};""") 272 | info = cur.fetchall() 273 | return info 274 | 275 | 276 | def stat_bar_general(telegram_user_id): 277 | """ 278 | Вся данная функция работает очень медленно и требует дальнейшей доработки 279 | """ 280 | 281 | time_moscow = datetime.datetime.now(pytz.timezone('Europe/Moscow')) 282 | arr_time_week = [(time_moscow - datetime.timedelta(days=6 - i)).strftime("%m-%d") for i in range(7)] 283 | 284 | list_time = [] 285 | actions = ['flc', 'men_math', 'cat_math', 'cat_logic'] 286 | 287 | for action in actions: 288 | cur.execute(f"""SELECT time_action FROM actions 289 | WHERE action='{action}' and telegram_user_id={telegram_user_id} and (strftime('%m-%d', `time_action`) = '{arr_time_week[0]}' or strftime('%m-%d', `time_action`) = '{arr_time_week[1]}' or strftime('%m-%d', `time_action`) = '{arr_time_week[2]}' or strftime('%m-%d', `time_action`) = '{arr_time_week[3]}' or strftime('%m-%d', `time_action`) = '{arr_time_week[4]}' or strftime('%m-%d', `time_action`) = '{arr_time_week[5]}' or strftime('%m-%d', `time_action`) = '{arr_time_week[6]}');""") 290 | list_time.append([i[0][5:10] for i in cur.fetchall()]) 291 | 292 | return list_time 293 | 294 | 295 | def dp_admin_stat(): 296 | cur.execute("""SELECT telegram_user_id, date_reg FROM users;""") 297 | result = cur.fetchall() 298 | return result 299 | 300 | 301 | def dp_admin_stat_actions(): 302 | cur.execute("""SELECT telegram_user_id, time_action FROM actions;""") 303 | result = cur.fetchall() 304 | return result 305 | 306 | # -----------------------------main----------------------------------------- 307 | 308 | # https://cloud.google.com/bigquery/docs/reference/standard-sql/arrays 309 | -------------------------------------------------------------------------------- /data_b/parsing/async_parsing.py: -------------------------------------------------------------------------------- 1 | """ 2 | Надо сделать, но пока лень 3 | """ -------------------------------------------------------------------------------- /data_b/parsing/download_img.py: -------------------------------------------------------------------------------- 1 | """ 2 | Надо сделать, но пока лень 3 | """ -------------------------------------------------------------------------------- /data_b/parsing/references_to_tasks.py: -------------------------------------------------------------------------------- 1 | import json 2 | from math import ceil 3 | from random import randrange 4 | from time import sleep 5 | 6 | import requests 7 | from bs4 import BeautifulSoup 8 | from user_agent import generate_user_agent 9 | 10 | 11 | def checking_quantity_start(): 12 | number_tasks_0 = [tag for tag in soup.select('div[id]')][2].text.split('\n')[1] 13 | number_tasks_1 = number_tasks_0.split('Всего задач: ')[1] 14 | number_tasks_2 = int(number_tasks_1.split(']')[0]) 15 | number_start = ceil(number_tasks_2 / 100) 16 | return number_start 17 | 18 | 19 | def add_example_info(soup): 20 | table_example = soup.find_all(class_="problemsmallcaptiontable") 21 | count = 1 22 | for i in table_example[:10]: 23 | try: 24 | all_info = [] 25 | href = i.find(class_="componentboxlink") 26 | all_info.append(href.text.split('\n\t\t\t\t\t\t')[1]) 27 | print(f"\033[37m Задание: {count} ID: {all_info[0]}") 28 | count += 1 29 | all_info.append('https://www.problems.ru' + href.get("href")) 30 | all_info.append( 31 | i.find(class_="problemsmallsubjecttablecell").find(class_="componentboxlink").text.split( 32 | '\t\t\t\t\t\t\t')[ 33 | 1].split('\t')[1]) 34 | difficult = (i.find(class_="problemsmalldifficulty").find_all('nobr')) 35 | for i in difficult: 36 | all_info.append(i.text) 37 | sleep(randrange(4, 6)) 38 | 39 | headers_0 = { 40 | "Accept": "*/*", 41 | "User-Agent": generate_user_agent() 42 | } 43 | 44 | req_example = requests.get(all_info[1], form_data, headers=headers_0) 45 | src_example = req_example.text 46 | soup_example = BeautifulSoup(src_example, "lxml") 47 | check = True 48 | for link in soup_example.select("img"): 49 | lnk = link["src"] 50 | print(f'\033[33m {lnk}') 51 | if "show_document" in lnk: 52 | print(f'\033[31m note') 53 | check = False 54 | break 55 | 56 | if not check: 57 | continue 58 | 59 | tables_example = soup_example.find(class_="componentboxcontents") 60 | headings_h3 = tables_example.find_all("h3") 61 | 62 | list_text_trash = tables_example.text.split(headings_h3[0].text) 63 | for i in range(len(headings_h3) - 1): 64 | c_1 = headings_h3[i + 1] 65 | c_2 = list_text_trash[-1].split(c_1.text) 66 | 67 | # text = re.sub("[\n|\t| |\r]", " ", c_2[0]) 68 | text = c_2[0] 69 | all_info.append(f"{headings_h3[i].text}: {text}") 70 | list_text_trash.append(c_2[1]) 71 | 72 | all_examples_hrefs.append(all_info) 73 | 74 | print('\033[32m correct') 75 | except BaseException: 76 | print(f'\033[31m ERROR, check {i}') 77 | 78 | 79 | url = "https://www.problems.ru/view_by_subject_new.php" 80 | all_examples_hrefs = [] 81 | problems_dict = {} 82 | 83 | parent = 273 84 | 85 | headers = { 86 | "Accept": "*/*", 87 | "User-Agent": generate_user_agent() 88 | } 89 | 90 | form_data = { 91 | 'parent': parent, 92 | 'start': 0, 93 | 'viewing_params[view_amount]': 100, 94 | 'difficulty_min': 2, 95 | 'difficulty_max': 10, 96 | 'grade_min': 8, 97 | 'grade_max': 11 98 | } 99 | 100 | req = requests.get(url, form_data, headers=headers) 101 | src = req.text 102 | soup = BeautifulSoup(src, "lxml") 103 | 104 | add_example_info(soup) 105 | 106 | number_start = checking_quantity_start() 107 | if number_start > 1: 108 | for page in range(number_start - 1): 109 | sleep(randrange(2, 4)) 110 | page += 1 111 | headers = { 112 | "Accept": "*/*", 113 | "User-Agent": generate_user_agent() 114 | } 115 | 116 | form_data = { 117 | 'parent': parent, 118 | 'start': page, 119 | 'viewing_params[view_amount]': 100, 120 | 'difficulty_min': 2, 121 | 'difficulty_max': 10, 122 | 'grade_min': 8, 123 | 'grade_max': 11 124 | } 125 | 126 | req = requests.get(url, form_data, headers=headers) 127 | src = req.text 128 | soup = BeautifulSoup(src, "lxml") 129 | 130 | add_example_info(soup) 131 | 132 | for i in range(len(all_examples_hrefs)): 133 | item = all_examples_hrefs[i] 134 | id = item[0] 135 | 136 | problems_dict[id] = item[1:] 137 | 138 | with open("../json/triangles.json", "w", encoding="utf-8") as file: 139 | json.dump(problems_dict, file, indent=4, ensure_ascii=False) 140 | -------------------------------------------------------------------------------- /data_b/scipio.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/data_b/scipio.db -------------------------------------------------------------------------------- /handlers/admins/admins.py: -------------------------------------------------------------------------------- 1 | from aiogram import types, Dispatcher 2 | from aiogram.dispatcher import FSMContext 3 | from aiogram.dispatcher.filters import IDFilter 4 | from config import ADMINS 5 | from handlers.keyboards.default.admin_menu import admin_start_menu 6 | 7 | 8 | async def admin_start(message: types.Message, state: FSMContext): 9 | await state.finish() 10 | await message.answer('Выберете:', reply_markup=admin_start_menu()) 11 | 12 | 13 | def register_handlers_send_msg(dp: Dispatcher): 14 | dp.register_message_handler(admin_start, IDFilter(user_id=ADMINS), commands='admin', state="*") 15 | -------------------------------------------------------------------------------- /handlers/admins/delete_tasks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Быстрое и удобное удаление задачи из категории математика или логика для админов 3 | 4 | Пояснение: 5 | Удобно в случае, когда необходимо удалить поломанную задачу, но не хочется заходить в базу данных. Тогда мы просто 6 | пишем id задачи или его title и он удаляется из бд 7 | """ 8 | 9 | from aiogram.dispatcher import FSMContext 10 | from aiogram.dispatcher.filters.state import StatesGroup, State 11 | from aiogram import Bot, Dispatcher, types 12 | from aiogram.dispatcher.filters import Text 13 | 14 | from handlers.keyboards.default.admin_menu import choose_category 15 | from data_b.dp_control import del_task 16 | 17 | 18 | async def del_task_start(message: types.Message): 19 | await message.answer('Выберете', reply_markup=choose_category()) 20 | await AdminDelTask.name_category.set() 21 | 22 | 23 | async def del_task_middle(message: types, state: FSMContext): 24 | await message.answer('Введите название или id задачи', reply_markup=types.ReplyKeyboardRemove()) 25 | await state.update_data(category=message.text) 26 | await AdminDelTask.name_task.set() 27 | 28 | 29 | async def del_task_end(message: types, state: FSMContext): 30 | msg = message.text 31 | user_data = await state.get_data() 32 | category = user_data['category'] 33 | try: 34 | del_task(msg, category) 35 | await message.answer('Задача успешно удалена') 36 | except: 37 | await message.answer('Что-то пошло не так') 38 | 39 | 40 | class AdminDelTask(StatesGroup): 41 | name_category = State() 42 | name_task = State() 43 | 44 | 45 | def register_handlers_del_task(dp: Dispatcher): 46 | dp.register_message_handler(del_task_start, Text(equals="Удалить задачу"), state='*') 47 | dp.register_message_handler(del_task_middle, state=AdminDelTask.name_category) 48 | dp.register_message_handler(del_task_end, state=AdminDelTask.name_task) 49 | -------------------------------------------------------------------------------- /handlers/admins/send_message_all.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aiogram.dispatcher import FSMContext 4 | from aiogram.dispatcher.filters import IDFilter 5 | from aiogram.dispatcher.filters.state import StatesGroup, State 6 | from aiogram import Bot, Dispatcher, types 7 | from aiogram.dispatcher.filters import Text 8 | 9 | from config import BOT_TOKEN, ADMINS 10 | from data_b.dp_control import dp_admin_stat 11 | from handlers.keyboards.default.admin_menu import choose_send, add_text 12 | 13 | bot = Bot(token=BOT_TOKEN, parse_mode=types.ParseMode.HTML) 14 | 15 | 16 | async def send_message_start(message: types.Message, state: FSMContext): 17 | await message.answer('Введите сообщение для отправки всем', reply_markup=types.ReplyKeyboardRemove()) 18 | user_data = await state.get_data() 19 | # Список c - список в котором хранятся все сообщения 20 | # Если он не существует то он создается и добавляется в state 21 | try: 22 | c = user_data['c'] 23 | c.append(user_data['msg']) 24 | await state.update_data(c=c) 25 | except: 26 | c = [] 27 | await state.update_data(c=c) 28 | 29 | await AdminSendMessage.main_message_1.set() 30 | 31 | 32 | async def send_dop_msg(message: types, state: FSMContext): 33 | # Добавление сообщения в state 34 | await state.update_data(msg=message.text) 35 | await message.answer('Хотите добавить ещё сообщение?', reply_markup=add_text()) 36 | await AdminSendMessage.main_message_2.set() 37 | 38 | 39 | async def send_message_middle(message: types.Message, state: FSMContext): 40 | user_data = await state.get_data() 41 | 42 | await state.update_data(msg=message.text) 43 | # Добавление последнего сообщеняи в state 44 | c = user_data['c'] 45 | c.append(user_data['msg']) 46 | await state.update_data(c=c) 47 | 48 | await message.answer('Вы уверенные что хотите отправить это сообщение?', reply_markup=choose_send()) 49 | await AdminSendMessage.main_message_3.set() 50 | 51 | 52 | async def send_message_end(message: types.Message, state: FSMContext): 53 | await message.answer('Сообщения отправляются', reply_markup=types.ReplyKeyboardRemove()) 54 | user_data = await state.get_data() 55 | # Все сообщения 56 | c = user_data['c'] 57 | # Список всех пользователей. Пример: [(id, 'Время регистрации в боте'), (930136261, '2022-03-22 11:29:03.159285')] 58 | all_users = list(dp_admin_stat()) 59 | for i in range(len(all_users)): 60 | await asyncio.sleep(0.1) 61 | user_id = all_users[i][0] 62 | # if str(user_id) != str(ADMINS): 63 | for j in range(len(c)): 64 | try: 65 | await bot.send_message(user_id, c[j]) 66 | except: 67 | None 68 | # Создать удаление пользователей 69 | await state.finish() 70 | 71 | 72 | class AdminSendMessage(StatesGroup): 73 | main_message_1 = State() 74 | main_message_2 = State() 75 | main_message_3 = State() 76 | 77 | 78 | def register_handlers_send_message_all(dp: Dispatcher): 79 | dp.register_message_handler(send_message_start, Text(equals="Отправка сообщения всем"), state='*') 80 | dp.register_message_handler(send_dop_msg, state=AdminSendMessage.main_message_1) 81 | 82 | dp.register_message_handler(send_message_start, Text(equals="Добавить"), IDFilter(user_id=ADMINS), 83 | state=AdminSendMessage.main_message_2) 84 | 85 | dp.register_message_handler(send_message_middle, Text(equals="Нет, хочу отправить"), 86 | state=AdminSendMessage.main_message_2) 87 | 88 | dp.register_message_handler(send_message_end, Text(equals="Да"), state=AdminSendMessage.main_message_3) 89 | -------------------------------------------------------------------------------- /handlers/admins/statistics_info_admins.py: -------------------------------------------------------------------------------- 1 | """ 2 | Присылает администраторам бота информацию: 3 | 1. Количество пользователй за всё время 4 | 2. Количество новых пользователей за день 5 | 3. Количество новых пользователей за последние 7 дней 6 | 4. Количество новых пользователей за последние 30 дней 7 | 8 | """ 9 | 10 | from aiogram import types, Dispatcher 11 | from aiogram.dispatcher.filters import IDFilter, Text 12 | 13 | from config import ADMINS 14 | from data_b.dp_control import dp_admin_stat 15 | import datetime 16 | import pytz 17 | 18 | 19 | def users_new(users_list, time): 20 | """ 21 | Подсчёт новый пользователй за определённое время 22 | :param users_list: массив время регистрации пользователей 23 | :param time: нужное время 24 | :return: количество новый пользователей 25 | """ 26 | time_moscow = datetime.datetime.now(pytz.timezone('Europe/Moscow')) 27 | arr_time_week = [(time_moscow - datetime.timedelta(days=time - 1 - i)).strftime("%Y-%m-%d") for i in range(time)] 28 | 29 | count = 0 30 | for i in users_list: 31 | if i[1][0:10] in arr_time_week: 32 | count += 1 33 | 34 | return count 35 | 36 | 37 | async def stat_admins(message: types.Message): 38 | all_users_list = dp_admin_stat() 39 | users_today = users_new(all_users_list, 1) 40 | users_week = users_new(all_users_list, 7) 41 | 42 | # Новых пользователей за последние 30 дней 43 | users_month = users_new(all_users_list, 30) 44 | 45 | await message.answer(f'Всего пользователей: {len(all_users_list)}\n' 46 | f'Новых за день: {users_today}\n' 47 | f'Новых за неделю: {users_week}\n' 48 | f'Новых за месяц: {users_month}\n', reply_markup=types.ReplyKeyboardRemove()) 49 | return 50 | 51 | 52 | def register_handlers_statistics_info_admins(dp: Dispatcher): 53 | dp.register_message_handler(stat_admins, IDFilter(user_id=ADMINS), Text(equals='Статистика пользователей'), 54 | state="*") 55 | -------------------------------------------------------------------------------- /handlers/cmd.py: -------------------------------------------------------------------------------- 1 | from aiogram import types, Dispatcher 2 | from aiogram.dispatcher.filters.builtin import CommandStart, CommandHelp, Text 3 | from aiogram.dispatcher import FSMContext 4 | from aiogram.types import InputFile 5 | from aiogram.utils import emoji 6 | 7 | from data_b.dp_control import dp_all_users_list, dp_user_create 8 | 9 | 10 | async def cmd_start(message: types.Message, state: FSMContext): 11 | await state.finish() 12 | 13 | all_users_list = dp_all_users_list() 14 | if message.from_user.id not in all_users_list: 15 | dp_user_create(message.from_user.id) 16 | 17 | photo = InputFile("data/text_scipio.jpg") 18 | await message.answer_photo(photo=photo) 19 | await message.answer(f'Приветствуем на нашем обучающем проекте!' + emoji.emojize(":fire:")) 20 | await message.answer( 21 | f'Мы создали его для людей, которые хотят развить свои навыки или получить новые. ' + emoji.emojize( 22 | ":mortar_board:")) 23 | await message.answer(f'В функционал проекта входят:' 24 | f'\n 1) Математические задачи. Категории (Алгебра, Текстовые задачи, Тригонометрия, Вероятность и т.д) ' + emoji.emojize( 25 | ":book:") + 26 | f'\n 2) Математические примеры для подсчёта в уме ' + emoji.emojize(":brain:") + 27 | f'\n 3) Задачи на логику (данетки, загадки, логические задачи) ' + emoji.emojize( 28 | ":book:") + 29 | f'\n 4) Пользовательские карточки для обучения ' + emoji.emojize(":label:") + 30 | f'\n 5) Таймер с оповещениями о занятиях ' + emoji.emojize(":clock1:")) 31 | 32 | await message.answer(f'Основные команды для взаимодействия с ботом:' 33 | f'\n 1) /math - Задачи по математике' 34 | f'\n 2) /logic - Задачи на логику' 35 | f'\n 3) /flashcard - Пользовательские карточки' 36 | f'\n 4) /timer - Таймер' 37 | f'\n 5) /help - Просмотр и описание всей информации' 38 | f'\n 6) /cancel - Отмена текущего действия') 39 | 40 | await message.answer( 41 | emoji.emojize( 42 | ":gear:") + f' Это только первая версия (V.1.0), в дальнейшем проект будет улучшаться, и функционал будет расширяться.\n' 43 | f'Наша команда: ' + emoji.emojize(":busts_in_silhouette:") + '\n' 44 | f'· Андрей Тощаков - создатель этого проекта, основной разработчик\n' 45 | f'· Шагбанов Ахмед - основной разработчик\n' 46 | f'· Жанна Клыпо - создание логотипа и т.д\n') 47 | 48 | 49 | async def cmd_help(message: types.Message, state: FSMContext): 50 | await state.finish() 51 | await message.answer("Основные команды:" 52 | f"\n 1) /cancel - Отмена текущего действия. Если бот не работает, то после ввода " 53 | f"этой функции, программа починиться" + emoji.emojize(":stop_sign:") + 54 | 55 | f"\n\n 2) /math - Задачи по математике. В данном алгоритме вы вибираете категорию задания," 56 | f" а потом просто проходите и нарешиваете соответствующие задания" + emoji.emojize(":book:") + 57 | 58 | f"\n\n 3) /logic - Задачи на логику. Тоже самое что и задачи по математике, только другие" 59 | f" категории заданий" + emoji.emojize(":book:") + 60 | 61 | f"\n\n 4) /flashcard - Пользовательские карточки. Флеш-карточки - это удобный способ " 62 | f"запоминания и повторения изучаемого материала. На одной стороне карточки пишется слово, " 63 | f"фраза или термин, а на другой - перевод или значение." + emoji.emojize(":label:") + 64 | 65 | f"\n\n 5) /timer - Таймер. Вы можете поставить время выполнения определйнных заданий. " 66 | f"Например: вы хотите, чтобы в 08:30 утра, вы стабильно тренировали карточки, тогда вам необходимо сделать следующие действия:\n" 67 | f"Прописать /timer --> нажать на 'Создать таймер' --> Ввести нужное время --> Всё, готово, таймер создан! " + emoji.emojize(":clock1:"), 68 | reply_markup=types.ReplyKeyboardRemove()) 69 | 70 | await message.answer('Второстепенные команды:' 71 | '\n 1) /start - Покажет начально сообщение бота' 72 | '\n 2) /equation_mentally - тренировка для подсчёта в уме' 73 | '\n 3) /mell_theory - теория для подсчёта в уме' 74 | '\n 4) /flc_mg - управление карточек' 75 | '\n 5) /flc_train - тренировка с карточками' 76 | '\n 6) /flc_theory - теория по карточкам') 77 | 78 | 79 | async def cmd_cancel(message: types.Message, state: FSMContext): 80 | await state.finish() 81 | await message.answer("Действие отменено", reply_markup=types.ReplyKeyboardRemove()) 82 | 83 | 84 | def register_handlers_start(dp: Dispatcher): 85 | dp.register_message_handler(cmd_start, CommandStart(), state='*') 86 | dp.register_message_handler(cmd_help, CommandHelp(), state='*') 87 | dp.register_message_handler(cmd_cancel, commands='cancel', state='*') 88 | dp.register_message_handler(cmd_cancel, Text(equals="отмена", ignore_case=True), state="*") 89 | -------------------------------------------------------------------------------- /handlers/flashcards/base.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/handlers/flashcards/base.jpg -------------------------------------------------------------------------------- /handlers/flashcards/create_flashcard_photo.py: -------------------------------------------------------------------------------- 1 | from PIL import Image, ImageDraw, ImageFont 2 | 3 | 4 | # Функция определяющая размер шрифта текста 5 | def find_font_size(text, font, image, target_width_ratio): 6 | tested_font_size = 100 7 | tested_font = ImageFont.truetype(font, tested_font_size) 8 | observed_width, observed_height = get_text_size(text, image, tested_font) 9 | estimated_font_size = tested_font_size / (observed_width / image.width) * target_width_ratio 10 | # Чтобы текст не был слишком большим или слишком маленьким 11 | if estimated_font_size > 35: 12 | estimated_font_size = 35 13 | elif estimated_font_size < 18: 14 | estimated_font_size = 18 15 | return round(estimated_font_size) 16 | 17 | 18 | # Функция определяющая размер текста по пикселям 19 | def get_text_size(text, image, font): 20 | im = Image.new('RGB', (image.width, image.height)) 21 | draw = ImageDraw.Draw(im) 22 | return draw.textsize(text, font) 23 | 24 | 25 | def create_photo(msg, id, side): 26 | width_ratio = 2.25 27 | font_family = "handlers/flashcards/flc_users/pillow.ttf" 28 | text = msg.replace('\n',' ') 29 | 30 | image = Image.open('handlers/flashcards/base.jpg') 31 | width, height = image.size 32 | editable_image = ImageDraw.Draw(image) 33 | font_size = find_font_size(text, font_family, image, width_ratio) 34 | 35 | # Список строчек 36 | list_line = [] 37 | # Список всех слов из сообщения 38 | list_words = text.split() 39 | 40 | font = ImageFont.truetype(font_family, font_size) 41 | 42 | if len(list_words) > 1: 43 | 44 | font = ImageFont.truetype(font_family, font_size) 45 | # Если длина сообщения(не количество букв) больше 300, то сообщение делится на строки 46 | if get_text_size(text, image, font)[0] > 300: 47 | count = '' 48 | for i in range(len(list_words)): 49 | if get_text_size(count + list_words[i] + ' ', image, font)[0] <= 300: 50 | count += list_words[i] + ' ' 51 | else: 52 | list_line.append(count[:-1]) 53 | count = list_words[i] + ' ' 54 | list_line.append(count[:-1]) 55 | else: 56 | list_line.append(text) 57 | # Если колечество строк четное то сообщение центруется по середине между центральными строками 58 | if len(list_line) % 2 == 0: 59 | # get_text_size(text, image, font)[1] - высота одной строчки 60 | # count - на какой количество пикселей надо отпустить текст чтобы он был по центру 61 | count = (len(list_line) // 2) * get_text_size(text, image, font)[1] 62 | for i in range(len(list_line)): 63 | editable_image.text((width / 2, (height + 30) / 2 - count), list_line[i], font=font, fill='black', 64 | anchor="mm") 65 | count -= get_text_size(text, image, font)[1] 66 | # Если количесвто строк нечетное то сообщение центруется по центру центральной строки 67 | else: 68 | # get_text_size(text, image, font)[1] - высота одной строчки 69 | # count - на какой количество пикселей надо отпустить текст чтобы он был по центру 70 | count = (len(list_line) - 1) // 2 * get_text_size(text, image, font)[1] + get_text_size(text, image, font)[ 71 | 1] / 2 72 | for i in range(len(list_line)): 73 | editable_image.text((width / 2, (height + 30) / 2 - count), list_line[i], font=font, fill='black', 74 | anchor="mm") 75 | count -= get_text_size(text, image, font)[1] 76 | 77 | else: 78 | editable_image.text((width / 2, height / 2), text, font=font, fill='black', anchor="mm") 79 | 80 | image.save(f'handlers/flashcards/flc_users/{id}_{side}.png') -------------------------------------------------------------------------------- /handlers/flashcards/flashcard.py: -------------------------------------------------------------------------------- 1 | from aiogram import types, Dispatcher 2 | from aiogram.dispatcher import FSMContext 3 | 4 | from handlers.keyboards.default import flashcard_menu 5 | 6 | 7 | async def flashcard_start(message: types.Message, state: FSMContext): 8 | await state.finish() 9 | await message.answer('"Начать учить карточки" - начнёт процесс тренировки с карточками\n\n' 10 | '"Управление карточками" - переправит в раздел для изменения карточек', 11 | reply_markup=flashcard_menu.get_keyboard_flashcard_start()) 12 | 13 | 14 | def register_handlers_flashcard(dp: Dispatcher): 15 | dp.register_message_handler(flashcard_start, commands='flashcard', state="*") 16 | -------------------------------------------------------------------------------- /handlers/flashcards/flashcards_managing.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | from aiogram import types, Dispatcher 4 | from aiogram.dispatcher import FSMContext 5 | from aiogram.dispatcher.filters import Text 6 | from aiogram.dispatcher.filters.state import StatesGroup, State 7 | from aiogram.utils import emoji 8 | 9 | from data_b.dp_control import flashcard_dp_create, flashcard_dp_info, flashcard_del, flashcard_setting_photo_text 10 | from handlers.keyboards.default import flashcard_menu 11 | from handlers.keyboards.default.flashcard_menu import get_keyboard_flashcard_start 12 | 13 | # Максимальное количество символов на одной стороне 14 | MAX_LEN = 450 15 | 16 | 17 | async def flashcards_managing_start(message: types.Message): 18 | await message.answer('Вы можете создать или удалить собственные карточки, а также просмотреть информацию о них', 19 | reply_markup=flashcard_menu.get_keyboard_flashcard_managing()) 20 | 21 | 22 | # -----------------------------CREATE FUNC----------------------------------------- 23 | 24 | 25 | async def flashcards_managing_create_start(message: types.Message): 26 | await message.answer(f'Введите слово или фразу, которое/ую хотите выучить\n' 27 | f'Максимальное количество символов: {MAX_LEN}', 28 | reply_markup=types.ReplyKeyboardRemove()) 29 | await FlashcardManaging.flashcards_managing_create_middle.set() 30 | 31 | 32 | # Запист передней стороны 33 | async def flashcards_managing_create_middle(message: types.Message, state: FSMContext): 34 | msg = message.text 35 | if len(msg) > MAX_LEN: 36 | await message.answer(f'Вы превысили максимальное количество символов\n' 37 | f'Повторите ещё раз') 38 | await FlashcardManaging.flashcards_managing_create_middle.set() 39 | else: 40 | await state.update_data(front=msg) 41 | 42 | await message.answer(f'Введите значение или перевод первой стороны карточки\n' 43 | f'Максимальное количество символов: {MAX_LEN}') 44 | await FlashcardManaging.next() 45 | 46 | 47 | # Запист задней стороны 48 | async def flashcards_managing_create_middle_2(message: types.Message, state: FSMContext): 49 | msg = message.text 50 | if len(msg) > MAX_LEN: 51 | await message.answer(f'Вы превысили максимальное количество символов\n' 52 | f'Повторите ещё раз') 53 | await FlashcardManaging.flashcards_managing_create_middle_2.set() 54 | else: 55 | await state.update_data(back=msg) 56 | await message.answer(f'Вы хотите, чтобы при повторении карточки показывалась любая сторона?', 57 | reply_markup=flashcard_menu.get_keyboard_flashcard_end_que()) 58 | await FlashcardManaging.next() 59 | 60 | 61 | async def flashcards_managing_create_end(message: types.Message, state: FSMContext): 62 | msg = message.text 63 | # Если пользователь нажал "да" то при тренировке карточка будет показываться с двух сторон 64 | if msg == 'Да' or msg == 'Нет': 65 | if msg == 'Да': 66 | show_card = True 67 | else: 68 | show_card = False 69 | else: 70 | await message.answer(f'Вы выбрали не то\n' 71 | 'Напишите "Да" или "Нет" или выберете кнопки в боте') 72 | await FlashcardManaging.flashcards_managing_create_end.set() 73 | 74 | user_data = await state.get_data() 75 | try: 76 | flashcard_dp_create(message.from_user.id, user_data["front"], user_data["back"], show_card) 77 | await message.answer(f'Карточка успешно создана', reply_markup=flashcard_menu.get_keyboard_flashcard_start()) 78 | await message.answer(f'Передняя сторона - {user_data["front"]}\n' 79 | f'Задняя сторона - {user_data["back"]}\n' 80 | f'Показывать карточку с двух сторон? - {msg}') 81 | except Exception: 82 | await message.answer(f'Что - то пошло не так, попробуйте снова') 83 | await state.finish() 84 | 85 | 86 | # -----------------------------DEL FUNC----------------------------------------- 87 | async def flashcards_managing_del_start(message: types.Message): 88 | # Список всех карточек. Пример: [(54, "cat", "кошка"),(55, "dog", "собака")] 89 | all_cards = flashcard_dp_info(message.from_user.id) 90 | if len(all_cards) == 0: 91 | await message.answer(f'У вас нет карточек, которые вы могли бы удалалять\n' 92 | f'Сначала создайте их', reply_markup=flashcard_menu.get_keyboard_flashcard_start()) 93 | return 94 | 95 | await message.answer(f'Чтобы удалить карточку - введите её id\n' 96 | 'Первый пример: 1\n' 97 | 'Второй пример: 1 2 5', 98 | reply_markup=types.ReplyKeyboardRemove()) 99 | 100 | # Создания сообщения длинной максимум 4096 символов 101 | arr_mes_flc_info = print_info_card(message.from_user.id) 102 | 103 | for i in range(len(arr_mes_flc_info)): 104 | await message.answer(arr_mes_flc_info[i]) 105 | 106 | await FlashcardManaging.flashcards_managing_del_end.set() 107 | 108 | 109 | async def flashcards_managing_del_end(message: types.Message, state: FSMContext): 110 | msg = message.text 111 | # Список всех карточек. Пример: [(54, "cat", "кошка"),(55, "dog", "собака")] 112 | all_flash = flashcard_dp_info(message.from_user.id) 113 | list_id = msg.split() 114 | list_id = sorted(list_id, reverse=True) 115 | for card_id in list_id: 116 | if card_id.isdigit(): 117 | # Провекра что номер меньше чем количество карточек 118 | if int(card_id) <= len(flashcard_dp_info(message.from_user.id)): 119 | # Удаление карточки 120 | # all_flash[int(card_id) - 1][1] - передняя сторона карточки, 121 | # all_flash[int(card_id) - 1][2] - задняя сторона карточки 122 | flashcard_del(message.from_user.id, all_flash[int(card_id) - 1][1], all_flash[int(card_id) - 1][2]) 123 | await message.reply(f'Карточка {card_id} успешно удалена', reply_markup=get_keyboard_flashcard_start()) 124 | 125 | await state.finish() 126 | 127 | else: 128 | await message.answer('Такого id карточки - не существует') 129 | await FlashcardManaging.flashcards_managing_del_end.set() 130 | else: 131 | await message.answer('Вы неправильно ввели id карточки\n' 132 | 'Напишите как показано в примере:\n' 133 | 'Если карточка одна: 1\n' 134 | 'Если карточек несколько: 1 2 5\n') 135 | await FlashcardManaging.flashcards_managing_del_end.set() 136 | 137 | 138 | async def flashcards_managing_info(message: types.Message): 139 | if len(flashcard_dp_info(message.from_user.id)) == 0: 140 | await message.answer('У вас нет карточек') 141 | return 142 | else: 143 | await message.answer('Все ваши карточки:', reply_markup=flashcard_menu.get_keyboard_flashcard_start()) 144 | 145 | # Создания сообщения длинной максимум 4096 символов 146 | arr_mes_flc_info = print_info_card(message.from_user.id) 147 | 148 | for i in range(len(arr_mes_flc_info)): 149 | await message.answer(arr_mes_flc_info[i]) 150 | 151 | 152 | def print_info_card(telegram_user_id): 153 | """ 154 | Создаёт сообщения (информации о карточках), каждое из которых максимум 4096 символов, чтобы избавиться от ошибки 155 | 'Message too long' 156 | """ 157 | 158 | # Список всех карточек. Пример: [(54, "cat", "кошка"),(55, "dog", "собака")] 159 | all_cards = flashcard_dp_info(telegram_user_id) 160 | # Создание сообщения с информацией о всех каточках 161 | 162 | arr_mes_flc_info = [] 163 | mes_print = '' 164 | for i in range(len(all_cards)): 165 | string = f'{i + 1}: {all_cards[i][1]} - {all_cards[i][2]}\n' 166 | if len(mes_print) + len(string) >= 4096: 167 | arr_mes_flc_info.append(mes_print) 168 | mes_print = f'{i + 1}: {all_cards[i][1]} - {all_cards[i][2]}\n' 169 | else: 170 | mes_print += f'{i + 1}: {all_cards[i][1]} - {all_cards[i][2]}\n' 171 | arr_mes_flc_info.append(mes_print) 172 | 173 | return arr_mes_flc_info 174 | 175 | 176 | async def setting_show(message: types.Message): 177 | msg = message.text 178 | if msg == 'Показ карточек': 179 | await message.answer('Вы можете настроить показ карточек(flashcards):\n' 180 | '1. Сделать показ карточек фотографиями\n' 181 | '2. Сделать показ карточек текстом', 182 | reply_markup=flashcard_menu.setting_show()) 183 | elif msg == 'Фото': 184 | flashcard_setting_photo_text(message.from_user.id, 1) 185 | await message.answer("Показ карточке: Фото", reply_markup=flashcard_menu.get_keyboard_flashcard_start()) 186 | elif msg == 'Текст': 187 | flashcard_setting_photo_text(message.from_user.id, 0) 188 | await message.answer("Показ карточке: Текст", reply_markup=flashcard_menu.get_keyboard_flashcard_start()) 189 | return 190 | 191 | 192 | class FlashcardManaging(StatesGroup): 193 | flashcards_managing_create_middle = State() 194 | flashcards_managing_create_middle_2 = State() 195 | flashcards_managing_create_end = State() 196 | flashcards_managing_del_end = State() 197 | 198 | 199 | def register_handlers_flashcards_managing(dp: Dispatcher): 200 | dp.register_message_handler(flashcards_managing_start, commands='flc_mg', state='*') 201 | dp.register_message_handler(flashcards_managing_start, 202 | Text(equals=emoji.emojize(":gear:") + " Управление карточками"), state='*') 203 | dp.register_message_handler(flashcards_managing_create_start, 204 | Text(equals=emoji.emojize(":pencil2:") + ' Создать карточку'), state='*') 205 | dp.register_message_handler(flashcards_managing_del_start, 206 | Text(equals=emoji.emojize(":stop_sign:") + ' Удалить карточку'), state='*') 207 | dp.register_message_handler(flashcards_managing_info, 208 | Text(equals=emoji.emojize(":information_source:") + ' Информация о карточках'), 209 | state='*') 210 | 211 | dp.register_message_handler(flashcards_managing_create_middle, 212 | state=FlashcardManaging.flashcards_managing_create_middle) 213 | dp.register_message_handler(flashcards_managing_create_middle_2, 214 | state=FlashcardManaging.flashcards_managing_create_middle_2) 215 | dp.register_message_handler(flashcards_managing_create_end, 216 | state=FlashcardManaging.flashcards_managing_create_end) 217 | 218 | dp.register_message_handler(flashcards_managing_del_end, 219 | state=FlashcardManaging.flashcards_managing_del_end) 220 | dp.register_message_handler(setting_show, Text(["Показ карточек", "Фото", "Текст"]), state='*') 221 | -------------------------------------------------------------------------------- /handlers/flashcards/flashcards_training.py: -------------------------------------------------------------------------------- 1 | """ 2 | Данный алгоритм построен на основе ОДНОГО шага, основной функцией которого является (flc_game) 3 | 4 | Основная идея данного алгоритма в том, что из-за того что всё происходит в основной функции, то при нажатии "Правильно" 5 | и "Неправильно" следующее что делает функция fls_game, это создаёт новую flashcard и вызывает саму себя. Из-за этого 6 | алгоритм после "Правильно"/"Неправильно" сразу создаёт новую flashcard и показывает её, 7 | не требуя ввести сообщение от пользователя 8 | 9 | Алгоритм работает так: 10 | Пользователь вызвал /flc_train или нажал на кнопки в боте. Бот спросил готов ли он (flashcards_training_start), 11 | пользователь ответил, что готов, а дальше вызывается функция flc_game, которая проверяет, что пользователь верно 12 | нажал на кнопку "Да" (Если ответил "Нет, то возвращает в основное меню") и дальше... 13 | 14 | Основной алгоритм. Функция гененирует карточку, присылает пользователю информацию о карточке и создаёт кнопки: 15 | "Обратная сторона", "Правильно", "Неправильно", а дальше вызывает саму себя, и ждёт следующих действий от 16 | пользователя: 17 | 1. "Обратная сторона". Тогда вызывается функция flc_game_reverse_side, которая срабатывает 18 | поверх функции flc_game. Она отправляет пользователю card_back и выключается, STATE оно не меняет! 19 | flc_game остаётся дальше ждать действий от пользователя 20 | 21 | 2. "Правильно" или "Неправильно". При нажатии на кнопку "Правильно" - пользователю при прохождении дальнейшей 22 | тренировки больше не будет высвечиватся это карточка (карточка удаляется из user_data['flashcards']). 23 | При нажатии на "Неправильно" - эта карточка при тренировке ещё БУДЕТ показываться 24 | 25 | 3. "Закончить". Вызывает функцию flc_game_end, которая присылает статистику пользователю и соответственно 26 | заканчивает тренировку. 27 | """ 28 | import sqlite3 29 | from random import choice 30 | 31 | from aiogram import types, Dispatcher 32 | from aiogram.dispatcher import FSMContext 33 | from aiogram.dispatcher.filters import Text 34 | from aiogram.dispatcher.filters.state import StatesGroup, State 35 | from aiogram.utils import emoji 36 | 37 | from data_b.dp_control import flashcard_dp_info_game, action_add, flashcard_one, flashcard_check_show 38 | from handlers.keyboards.default import flashcard_menu 39 | 40 | from handlers.flashcards.create_flashcard_photo import create_photo 41 | import os 42 | 43 | from handlers.keyboards.default.flashcard_menu import get_keyboard_flashcard_start, get_keyboard_flashcard_training_game 44 | 45 | 46 | async def flashcards_training_theory(message: types.Message): 47 | await message.answer('Флеш-карточки - это удобный способ запоминания и повторения изучаемого материала. ' 48 | 'На одной стороне карточки пишется слово, фраза или термин, а на другой - ' 49 | 'перевод или значение.') 50 | await message.answer('Чтобы процесс обучения был более эффективным, вы можете придерживаться нескольких советов:') 51 | await message.answer( 52 | '1) Разбейте учебные сеансы на отрезки по 10-15 минут, так как их вполне хватит для повторения более 100 слов,' 53 | ' и такое кол-во свободного времени найдется у любого человека.' 54 | '\n2) Чаще устраивайте себе экзамены, ведь чем чаще вы будете себя испытывать, тем лучше.' 55 | '\n3) Создайте подходящую для вас систему обучения, в данном случае дисциплина гораздо полезнее' 56 | ' случайных занятий.') 57 | 58 | 59 | async def flashcards_training_start(message: types.Message): 60 | await message.answer( 61 | 'Можно выбрать тип показа карточек (Или в виде фото, или в виде текста). Это можно сделать в "Управление карточками" - "Показ карточек"\n\n' 62 | 'Принцип работы с карточками и советы /flc_theory') 63 | await message.answer('Вы готовы?', reply_markup=flashcard_menu.get_keyboard_flashcard_training_start()) 64 | await Flash_game.flc_game.set() 65 | 66 | 67 | async def flc_game(message: types.Message, state: FSMContext): 68 | """ 69 | Основной алгоритм 70 | :param message: Ждёт сообщения: "Да"; "Правильно"; "Неправильно" всё остальное отсекается 71 | """ 72 | global flc_show 73 | flc_show = flashcard_check_show(message.from_user.id) 74 | 75 | if message.text == 'Да': 76 | 77 | # Генерация массива карточек пользователя 78 | flashcards = flashcard_generate(message.from_user.id) 79 | if flc_show: 80 | for i in range(len(flashcards)): 81 | card_id = flashcards[i][0] 82 | if type(card_id) == int: 83 | card_front = flashcards[i][1] 84 | card_back = flashcards[i][2] 85 | create_photo(card_front, card_id, 'front') 86 | create_photo(card_back, card_id, 'back') 87 | 88 | if not flashcards: 89 | await message.answer('У вас ещё нет карточек', reply_markup=types.ReplyKeyboardRemove()) 90 | await message.answer('Чтобы создать их, вам нужно зайти в ' 91 | '"Управление карточками" и нажать на кнопку "Создать карточку"', 92 | reply_markup=get_keyboard_flashcard_start()) 93 | await state.finish() 94 | return 95 | 96 | await message.answer('Чтобы закончить изучение напишите /flash_end') 97 | await state.update_data(flashcards=flashcards) 98 | # Генерация массива правильных карточек (потом для статистики используется) 99 | await state.update_data(correct=[]) 100 | 101 | elif message.text == emoji.emojize(":white_check_mark:") + ' Правильно' or message.text == emoji.emojize( 102 | ":x:") + ' Неправильно': 103 | 104 | if message.text == emoji.emojize(":white_check_mark:") + ' Правильно': 105 | user_data = await state.get_data() 106 | # если "правильно", то в user_data['correct'] добавляется id карточки 107 | correct = user_data['correct'] 108 | correct.append(user_data['card_id']) 109 | await state.update_data(correct=correct) 110 | 111 | # удаление карточки из user_data['flashcards'] по его id 112 | flashcards = user_data['flashcards'] 113 | for i in range(len(flashcards)): 114 | if user_data['card_id'] == flashcards[i][0]: 115 | del flashcards[i] 116 | await state.update_data(flashcards=flashcards) 117 | break 118 | # добавление action flc в бд 119 | action_add(message.from_user.id, 'flc', True) 120 | else: 121 | action_add(message.from_user.id, 'flc', False) 122 | elif message.text == 'Нет': 123 | # Возврат в основное меню 124 | await message.answer('Действие отменено', reply_markup=flashcard_menu.get_keyboard_flashcard_start()) 125 | return 126 | else: 127 | await message.answer('Вы написали что-то не то') 128 | return 129 | 130 | user_data = await state.get_data() 131 | # Выбор РАНДОМНОЙ карточки из user_data['flashcards'] 132 | flashcard = user_data['flashcards'] 133 | # если карточки закончились то END 134 | if not flashcard: 135 | await flc_game_end(message, state) 136 | else: 137 | flashcard = choice(flashcard) 138 | 139 | # card_id содежит либо номер карточки, Пример: 54, либо номер каточки и сторону, Пример: 54 обрат.карт 140 | card_id, card_front, card_back, show_card = flashcard 141 | list_words = card_front.split() 142 | card_id_split = str(card_id).split() 143 | # Если id может быть: 138 или '138 обрат.сторона'. То есть если id число значить это лицевая сторона 144 | if type(card_id) == int: 145 | side = 'Лицевая сторона' 146 | side_file = 'front' 147 | else: 148 | side = 'Обратная сторона' 149 | side_file = 'back' 150 | 151 | await state.update_data(card_id=card_id) 152 | await state.update_data(card_back=card_back) 153 | await state.update_data(side=side) 154 | 155 | card_id = str(card_id).split()[0] 156 | 157 | # Если количество букв будет больше 250, то сообщение будет в виде обычного текста(не в виде фото) 158 | if ((len(list_words) == 1 and len(list_words[0]) <= 50) or ( 159 | len(list_words) > 1 and len(card_front) <= 250)) and flc_show: 160 | 161 | photo = open(f'handlers/flashcards/flc_users/{card_id}_{side_file}.png', 'rb') 162 | 163 | await message.answer_photo(photo=photo, caption=side, reply_markup=get_keyboard_flashcard_training_game()) 164 | 165 | else: 166 | await message.answer(f'{side}:\n{card_front}', 167 | reply_markup=flashcard_menu.get_keyboard_flashcard_training_game()) 168 | 169 | await Flash_game.flc_game.set() 170 | 171 | 172 | async def flc_game_end(message: types.Message, state: FSMContext): 173 | """ 174 | Функция присылает статистику по тренировке и закачивает тренировку 175 | Вызов: 1.Если написали /flash_end 176 | 2.Если закончились flashcards у пользователя 177 | 3. Нажали на кнопку закончить 178 | :return: Конец тренировки, state.finish() 179 | """ 180 | await message.answer('Тренировка карточек закончена', 181 | reply_markup=flashcard_menu.get_keyboard_flashcard_start()) 182 | user_data = await state.get_data() 183 | correct = user_data['correct'] 184 | # Создание статистики 185 | string_correct = '' 186 | for i in range(len(correct)): 187 | if type(correct[i]) == int: 188 | # Пример списка info_one_card: [55, "cat", "кошка"] или ["55 обрат.карт", "dog", "собака"] 189 | info_one_card = flashcard_one(message.from_user.id, correct[i])[0] 190 | string_correct += f"{i + 1}: {info_one_card[1]} => {info_one_card[2]}\n" 191 | else: 192 | info_one_card = flashcard_one(message.from_user.id, correct[i].split()[0])[0] 193 | string_correct += f"{i + 1}: {info_one_card[2]} => {info_one_card[1]}\n" 194 | 195 | await message.answer(emoji.emojize(":bar_chart:") + f' Количество правильно отвеченных карточек: {len(correct)}\n' 196 | f'{string_correct}') 197 | if flc_show: 198 | flashcards = flashcard_generate(message.from_user.id) 199 | for i in range(len(flashcards)): 200 | card_id = flashcards[i][0] 201 | if type(card_id) == int: 202 | os.remove(f'handlers/flashcards/flc_users/{card_id}_front.png') 203 | os.remove(f'handlers/flashcards/flc_users/{card_id}_back.png') 204 | await state.finish() 205 | 206 | 207 | async def flc_game_reverse_side(message: types.Message, state: FSMContext): 208 | """ 209 | Показ обратной стороны 210 | """ 211 | user_data = await state.get_data() 212 | card_back = user_data['card_back'] 213 | card_id = user_data['card_id'] 214 | list_words = card_back.split() 215 | side = user_data['side'] 216 | if side == 'Лицевая сторона': 217 | side = 'Обратная сторона' 218 | side_file = 'back' 219 | else: 220 | card_id = card_id.split()[0] 221 | side = 'Лицевая сторона' 222 | side_file = 'front' 223 | 224 | if ((len(list_words) == 1 and len(list_words[0]) <= 50) or ( 225 | len(list_words) > 1 and len(card_back) <= 250)) and flc_show == 1: 226 | 227 | photo = open(f'handlers/flashcards/flc_users/{card_id}_{side_file}.png', 'rb') 228 | 229 | await message.answer_photo(photo=photo, caption=side) 230 | else: 231 | await message.answer(f'{side}:\n{card_back}', 232 | reply_markup=flashcard_menu.get_keyboard_flashcard_training_game()) 233 | await Flash_game.flc_game.set() 234 | 235 | 236 | def flashcard_generate(user_id): 237 | """ 238 | :return: массив карточек + карточки, которые должны показываться в обратную сторону 239 | """ 240 | flashcards = flashcard_dp_info_game(user_id) 241 | if len(flashcards) == 0: 242 | return False 243 | flashcards_2 = [] 244 | for i in flashcards: 245 | if i[3] == True or i[3] == 'True': 246 | flashcards_2.append((str(i[0]) + ' обрат.карт', i[2], i[1], i[3])) 247 | return flashcards + flashcards_2 248 | 249 | 250 | class Flash_game(StatesGroup): 251 | flc_game = State() 252 | 253 | 254 | def register_handlers_flashcards_training(dp: Dispatcher): 255 | dp.register_message_handler(flashcards_training_theory, commands='flc_theory', state='*') 256 | dp.register_message_handler(flashcards_training_start, commands='flc_train', state='*') 257 | dp.register_message_handler(flc_game_end, commands='flash_end', state='*') 258 | 259 | # Вот тут проблема с тем, что если писать "Закончить", то конец программы mentally_math 260 | dp.register_message_handler(flc_game_end, Text(equals="Закончить тренировку"), state='*') 261 | 262 | dp.register_message_handler(flc_game_reverse_side, Text(equals="Обратная сторона"), state='*') 263 | dp.register_message_handler(flashcards_training_start, 264 | Text(equals=emoji.emojize(":brain:") + ' Начать учить карточки'), state='*') 265 | dp.register_message_handler(flc_game, state=Flash_game.flc_game) 266 | -------------------------------------------------------------------------------- /handlers/flashcards/flc_users/pillow.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/handlers/flashcards/flc_users/pillow.ttf -------------------------------------------------------------------------------- /handlers/keyboards/default/admin_menu.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | 3 | 4 | def choose_send(): 5 | buttons = [ 6 | 'Да', 7 | 'Отмена' 8 | ] 9 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 10 | keyboard.add(*buttons) 11 | return keyboard 12 | 13 | 14 | def add_text(): 15 | buttons = [ 16 | 'Добавить', 17 | 'Нет, хочу отправить' 18 | ] 19 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 20 | keyboard.add(*buttons) 21 | return keyboard 22 | 23 | 24 | def choose_category(): 25 | buttons = [ 26 | 'Математика', 27 | 'Логика' 28 | ] 29 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 30 | keyboard.add(*buttons) 31 | return keyboard 32 | 33 | 34 | def admin_start_menu(): 35 | buttons = [ 36 | 'Отправка сообщения всем', 37 | 'Статистика пользователей', 38 | 'Удалить задачу' 39 | ] 40 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 41 | keyboard.add(*buttons) 42 | return keyboard -------------------------------------------------------------------------------- /handlers/keyboards/default/flashcard_menu.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | from aiogram.utils import emoji 3 | 4 | 5 | def get_keyboard_flashcard_training_game(): 6 | buttons = [ 7 | emoji.emojize(":white_check_mark:") + ' Правильно', 8 | emoji.emojize(":x:") + ' Неправильно', 9 | 'Обратная сторона', 10 | 'Закончить тренировку' 11 | ] 12 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 13 | keyboard.add(*buttons) 14 | return keyboard 15 | 16 | 17 | def get_keyboard_flashcard_training_start(): 18 | buttons = [ 19 | 'Да', 20 | 'Нет', 21 | ] 22 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 23 | keyboard.add(*buttons) 24 | return keyboard 25 | 26 | 27 | def get_keyboard_flashcard_start(): 28 | buttons = [ 29 | emoji.emojize(":brain:") + ' Начать учить карточки', 30 | emoji.emojize(":gear:") + ' Управление карточками', 31 | ] 32 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 33 | keyboard.add(*buttons) 34 | return keyboard 35 | 36 | 37 | def get_keyboard_flashcard_managing(): 38 | buttons = [ 39 | emoji.emojize(":pencil2:") + ' Создать карточку', 40 | emoji.emojize(":stop_sign:") + ' Удалить карточку', 41 | emoji.emojize(":information_source:") + ' Информация о карточках', 42 | 'Показ карточек' 43 | ] 44 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 45 | keyboard.add(*buttons) 46 | return keyboard 47 | 48 | 49 | def get_keyboard_flashcard_end_que(): 50 | buttons = [ 51 | 'Да', 52 | 'Нет', 53 | ] 54 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 55 | keyboard.add(*buttons) 56 | return keyboard 57 | 58 | 59 | def setting_show(): 60 | buttons = [ 61 | 'Фото', 62 | 'Текст', 63 | ] 64 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 65 | keyboard.add(*buttons) 66 | return keyboard 67 | -------------------------------------------------------------------------------- /handlers/keyboards/default/logic_menu.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | from aiogram.utils import emoji 3 | 4 | 5 | def get_keyboard_logic_start(): 6 | buttons = [ 7 | emoji.emojize(":book:") + ' Задания из категорий', 8 | ] 9 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 10 | keyboard.add(*buttons) 11 | return keyboard 12 | 13 | 14 | def get_keyboard_logic_category(): 15 | buttons = [ 16 | emoji.emojize(":white_check_mark:") + ' Правильно', 17 | emoji.emojize(":x:") + ' Неправильно', 18 | emoji.emojize(":stop_sign:") + ' Закончить' 19 | ] 20 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 21 | keyboard.add(*buttons) 22 | return keyboard 23 | -------------------------------------------------------------------------------- /handlers/keyboards/default/math_menu.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | from aiogram.utils import emoji 3 | 4 | 5 | def get_keyboard_math_start(): 6 | buttons_1 = [ 7 | emoji.emojize(":book:") + ' Задания из категорий', 8 | emoji.emojize(":brain:") + ' Примеры для подсчёта в уме' 9 | ] 10 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 11 | keyboard.add(*buttons_1) 12 | return keyboard 13 | 14 | 15 | def get_keyboard_math_formulas(): 16 | buttons = [ 17 | 'Продолжаем', 18 | 'Закончить повторение' 19 | ] 20 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 21 | keyboard.add(*buttons) 22 | return keyboard 23 | 24 | 25 | def get_keyboard_math_mentally_start(): 26 | buttons = [ 27 | 'Да', 28 | 'Отмена' 29 | ] 30 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 31 | keyboard.add(*buttons) 32 | return keyboard 33 | 34 | 35 | def get_keyboard_math_mentally_end(): 36 | buttons = [ 37 | emoji.emojize(":stop_sign:") + " Закончить", 38 | ] 39 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 40 | keyboard.add(*buttons) 41 | return keyboard 42 | 43 | 44 | def get_keyboard_math_category(): 45 | buttons = [ 46 | emoji.emojize(":white_check_mark:") + ' Правильно', 47 | emoji.emojize(":x:") + ' Неправильно', 48 | emoji.emojize(":stop_sign:") + ' Закончить' 49 | ] 50 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 51 | keyboard.add(*buttons) 52 | return keyboard 53 | -------------------------------------------------------------------------------- /handlers/keyboards/default/statistics_menu.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | from aiogram.utils import emoji 3 | 4 | 5 | def get_keyboard_statistics_start(): 6 | buttons = [ 7 | emoji.emojize(":bar_chart:") + ' Общая', 8 | ] 9 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 10 | keyboard.add(*buttons) 11 | return keyboard 12 | -------------------------------------------------------------------------------- /handlers/keyboards/default/timer_menu.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | from aiogram.utils import emoji 3 | 4 | 5 | def get_keyboard_timer(): 6 | buttons = [ 7 | emoji.emojize(":pencil2:") + ' Создать таймер', 8 | emoji.emojize(":stop_sign:") + ' Удалить таймер', 9 | emoji.emojize(":information_source:") + ' Посмотреть ваши таймеры' 10 | ] 11 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 12 | keyboard.add(*buttons) 13 | return keyboard 14 | 15 | 16 | def get_keyboard_question_tasks(): 17 | buttons = [ 18 | 'Карточки (Flashcards)', 19 | 'Математика в уме', 20 | 'Задачи по математике', 21 | 'Задачи по логике' 22 | ] 23 | keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) 24 | keyboard.add(*buttons) 25 | return keyboard 26 | -------------------------------------------------------------------------------- /handlers/keyboards/inline/logic_menu_inline.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | 3 | from data_b.dp_control import finding_categories_table 4 | from handlers.logic.tasks_category_logic import callback_problems_logic, callback_problems_info_logic 5 | 6 | 7 | def get_inline_logic_problems_category(): 8 | buttons = [] 9 | 10 | # Список всех категории 'Logic' 11 | list_all_categorys = finding_categories_table('logic') 12 | 13 | for i in list_all_categorys: 14 | # НАПРИМЕР --- "riddles" 15 | category_name = i[0] 16 | # НАПРИМЕР --- "Загадки" 17 | translated_name = i[1] 18 | buttons.append( 19 | types.InlineKeyboardButton(text=translated_name, 20 | callback_data=callback_problems_logic.new(category_logic=category_name))) 21 | keyboard = types.InlineKeyboardMarkup(row_width=2) 22 | keyboard.add(*buttons) 23 | 24 | return keyboard 25 | 26 | 27 | def get_inline_logic_problems_category_info(info_problem): 28 | """ 29 | columns = 'decisions_1', 'decisions_2', 'answer', 'remarks' 30 | 31 | :param info_problem: Принимает значения columns 32 | :return: Возвращает INLINE - кнопки columns 33 | """ 34 | 35 | buttons = [] 36 | 37 | if info_problem['decisions_1'] != '': 38 | buttons.append(types.InlineKeyboardButton(text='Решение 1', 39 | callback_data=callback_problems_info_logic.new( 40 | info_logic='Decision 1', 41 | translate_logic='Решение 1'))) 42 | if info_problem['decisions_2'] != '': 43 | buttons.append(types.InlineKeyboardButton(text='Решение 2', 44 | callback_data=callback_problems_info_logic.new( 45 | info_logic='Decision 2', 46 | translate_logic='Решение 2'))) 47 | if info_problem['answer'] != '': 48 | buttons.append( 49 | types.InlineKeyboardButton(text='Ответ', 50 | callback_data=callback_problems_info_logic.new(info_logic='Answer', 51 | translate_logic='Ответ'))) 52 | if info_problem['remarks'] != '': 53 | buttons.append( 54 | types.InlineKeyboardButton(text='Замечания', 55 | callback_data=callback_problems_info_logic.new(info_logic='Remarks', 56 | translate_logic='Замечания'))) 57 | 58 | keyboard = types.InlineKeyboardMarkup(row_width=3) 59 | keyboard.add(*buttons) 60 | 61 | return keyboard 62 | -------------------------------------------------------------------------------- /handlers/keyboards/inline/math_menu_inline.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | 3 | from data_b.dp_control import finding_categories_table, finding_one_categories_table 4 | from data_b.dp_control import finding_main_categories_table 5 | from handlers.math.tasks_category_math import callback_main_problems_math, callback_problems_info_math, \ 6 | callback_problems_math 7 | 8 | 9 | def get_inline_math_url(): 10 | buttons = [ 11 | types.InlineKeyboardButton(text="Хабр", url="https://habr.com/ru/post/207034/"), 12 | ] 13 | keyboard = types.InlineKeyboardMarkup(row_width=1) 14 | keyboard.add(*buttons) 15 | return keyboard 16 | 17 | 18 | def get_inline_math_formulas(): 19 | buttons = [ 20 | types.InlineKeyboardButton(text="Вывести подсказку", callback_data="hint_f"), 21 | types.InlineKeyboardButton(text="Вывести ответ", callback_data="answer_f") 22 | ] 23 | keyboard = types.InlineKeyboardMarkup(row_width=2) 24 | keyboard.add(*buttons) 25 | 26 | return keyboard 27 | 28 | 29 | def get_inline_math_problems_category(): 30 | """ 31 | НУЖНО НАПИСАТЬ ЕЩЁ 32 | 33 | :return: Создаёт ко всем категориям Logic - INLINE кнопки 34 | """ 35 | buttons = [] 36 | 37 | # Находит все категории, которые есть в таблице math 38 | list_all_categorys = finding_categories_table('math') 39 | 40 | for i in list_all_categorys: 41 | category_name = i[0] # НАПРИМЕР --- "riddles" 42 | translated_name = i[1] # НАПРИМЕР --- "Загадки" 43 | buttons.append( 44 | types.InlineKeyboardButton(text=translated_name, 45 | callback_data=callback_main_problems_math.new(category=category_name))) 46 | keyboard = types.InlineKeyboardMarkup(row_width=2) 47 | keyboard.add(*buttons) 48 | 49 | return keyboard 50 | 51 | 52 | def get_inline_main_math_problems_category(): 53 | """ 54 | НУЖНО НАПИСАТЬ ЕЩЁ 55 | 56 | :return: Создаёт ко всем категориям Logic - INLINE кнопки 57 | """ 58 | buttons = [] 59 | 60 | # Находит все категории, которые есть в таблице math 61 | list_all_categorys = sorted(finding_main_categories_table('math')) 62 | for i in list_all_categorys: 63 | category_name = i[0] # НАПРИМЕР --- "riddles" 64 | translated_name = i[1] # НАПРИМЕР --- "Загадки" 65 | if types.InlineKeyboardButton(text=translated_name, callback_data=callback_main_problems_math.new( 66 | category=category_name)) not in buttons: 67 | buttons.append( 68 | types.InlineKeyboardButton(text=translated_name, 69 | callback_data=callback_main_problems_math.new(category=category_name))) 70 | keyboard = types.InlineKeyboardMarkup(row_width=2) 71 | keyboard.add(*buttons) 72 | 73 | return keyboard 74 | 75 | 76 | def get_inline_one_main_math_problems_category(category): 77 | 78 | list_all_categorys = sorted(finding_one_categories_table(category)) 79 | buttons = [] 80 | for i in list_all_categorys: 81 | category_name = i[0] # НАПРИМЕР --- "riddles" 82 | translated_name = i[1] # НАПРИМЕР --- "Загадки" 83 | if types.InlineKeyboardButton(text=translated_name, callback_data=callback_problems_math.new( 84 | category=category_name)) not in buttons: 85 | buttons.append( 86 | types.InlineKeyboardButton(text=translated_name, 87 | callback_data=callback_problems_math.new(category=category_name))) 88 | keyboard = types.InlineKeyboardMarkup(row_width=2) 89 | keyboard.add(*buttons) 90 | 91 | return keyboard 92 | 93 | 94 | def get_inline_math_problems_category_info(info_problem): 95 | """ 96 | columns = 'decisions_1', 'decisions_2', 'answer', 'remarks' 97 | 98 | :param info_problem: Принимает значения columns 99 | :return: Возвращает INLINE - кнопки columns 100 | """ 101 | 102 | buttons = [] 103 | 104 | if info_problem['decisions_1'] != '': 105 | buttons.append(types.InlineKeyboardButton(text='Решение 1', 106 | callback_data=callback_problems_info_math.new( 107 | info='Decision 1'))) 108 | 109 | if info_problem['decisions_2'] != '': 110 | buttons.append(types.InlineKeyboardButton(text='Решение 2', 111 | callback_data=callback_problems_info_math.new( 112 | info='Decision 2'))) 113 | 114 | if info_problem['answer'] != '': 115 | buttons.append(types.InlineKeyboardButton(text='Ответ', 116 | callback_data=callback_problems_info_math.new( 117 | info='Answer'))) 118 | 119 | if info_problem['remarks'] != '': 120 | buttons.append(types.InlineKeyboardButton(text='Замечания', 121 | callback_data=callback_problems_info_math.new( 122 | info='Remarks'))) 123 | 124 | keyboard = types.InlineKeyboardMarkup(row_width=3) 125 | keyboard.add(*buttons) 126 | 127 | return keyboard 128 | -------------------------------------------------------------------------------- /handlers/logic/logic.py: -------------------------------------------------------------------------------- 1 | from aiogram import types, Dispatcher 2 | from aiogram.dispatcher import FSMContext 3 | from aiogram.dispatcher.filters.state import StatesGroup, State 4 | 5 | from handlers.keyboards.default import logic_menu 6 | 7 | 8 | class LogicButCategory(StatesGroup): 9 | """Данные state нужен, чтобы кнопки 'Задания из категорий' """ 10 | logic_category_step = State() 11 | 12 | 13 | async def math_start(message: types.Message, state: FSMContext): 14 | await state.finish() 15 | await message.answer('Выберите:', reply_markup=logic_menu.get_keyboard_logic_start()) 16 | await LogicButCategory.logic_category_step.set() 17 | 18 | 19 | def register_handlers_logic(dp: Dispatcher): 20 | dp.register_message_handler(math_start, commands='logic', state="*") 21 | -------------------------------------------------------------------------------- /handlers/logic/tasks_category_logic.py: -------------------------------------------------------------------------------- 1 | from aiogram import types, Dispatcher 2 | from aiogram.dispatcher import FSMContext 3 | from aiogram.dispatcher.filters import Text 4 | from aiogram.dispatcher.filters.state import StatesGroup, State 5 | from aiogram.utils import emoji 6 | from aiogram.utils.callback_data import CallbackData 7 | from aiogram.utils.markdown import hlink 8 | 9 | from data_b.dp_control import problem_category_random, finding_categories_table, action_add 10 | from handlers.keyboards.default import logic_menu 11 | from handlers.logic.logic import LogicButCategory 12 | 13 | callback_problems_logic = CallbackData("problems_logic", "category_logic") 14 | callback_problems_info_logic = CallbackData("values_logic", "info_logic", "translate_logic") 15 | 16 | 17 | async def tasks_category_logic_start(message: types.Message, state: FSMContext): 18 | from handlers.keyboards.inline import logic_menu_inline 19 | 20 | await state.update_data(correct=[]) 21 | 22 | await message.answer('Выберите категорию заданий:', 23 | reply_markup=logic_menu_inline.get_inline_logic_problems_category()) 24 | 25 | link_endrey = hlink('в этот телеграм', 'https://t.me/Endrey_k') 26 | await message.answer(f'Если задание неправильное или неправильно выводиться, то прошу написать {link_endrey}' 27 | ' сообщение вида:\n' 28 | '(категория) - (id задачи или название) - (и часть условия)\n' 29 | 'Например: Математика - 35793 - Дан тетраэдр, у которого пери...', 30 | disable_web_page_preview=True) 31 | await LogicCategory.logic_step.set() 32 | 33 | 34 | async def tasks_category_logic_print_keyboard_inline(call: types.CallbackQuery, callback_data: dict, state: FSMContext): 35 | # НУЖНЫ ИЗМЕНЕНИЯ В КОММЕНТАРИИ 36 | 37 | """ 38 | :param call: Это ответ на нажатие INLINE кнопки КАТЕГОРИЯ 39 | :param callback_data: Это значения INLINE кнопки, то есть это информация 40 | о категории (её вроде бы info_logic, translate_logic) 41 | :return: 42 | """ 43 | from handlers.keyboards.inline import logic_menu_inline 44 | 45 | # объявлен global, чтобы при нажатии "Следующее задание" выводило туже категорию 46 | 47 | await state.update_data(category=callback_data["category_logic"]) 48 | 49 | category = callback_data["category_logic"] 50 | # Берёт из бд рандомную задачу и данные хранятся в СЛОВАРЕ 51 | dictionary_info_problem = problem_category_random(category, 'logic') 52 | 53 | id = dictionary_info_problem['id'] 54 | title = dictionary_info_problem['title'] 55 | href = dictionary_info_problem['href'] 56 | subcategory = dictionary_info_problem['subcategory'] 57 | complexity, classes = dictionary_info_problem['complexity'], dictionary_info_problem['classes'] 58 | condition = dictionary_info_problem['conditions'] 59 | if str(title) == 'None': 60 | title = id 61 | # Образка словаря 62 | info_problem = dict(list(dictionary_info_problem.items())[6:]) 63 | 64 | await state.update_data(problems_info_data_logic=info_problem) 65 | 66 | link_problems = hlink('Ссылка на задачу', href) 67 | dop_info = f'\nПодкатегория: {subcategory}\nСложность: {complexity}\nКлассы: {classes}' 68 | await call.message.answer( 69 | f'Название задания или его ID: {title}\n{link_problems}', 70 | reply_markup=logic_menu.get_keyboard_logic_category(), disable_web_page_preview=True) 71 | await call.message.answer(f'{condition}', 72 | reply_markup=logic_menu_inline.get_inline_logic_problems_category_info(info_problem)) 73 | 74 | await call.answer() 75 | 76 | 77 | async def tasks_category_logic_print_keyboard_default(message: types.Message, state: FSMContext): 78 | from handlers.keyboards.inline import logic_menu_inline 79 | user_data = await state.get_data() 80 | # Берёт из бд рандомную задачу и данные хранятся в СЛОВАРЕ 81 | category = user_data['category'] 82 | dictionary_info_problem = problem_category_random(category, 'logic') 83 | 84 | id = dictionary_info_problem['id'] 85 | title = dictionary_info_problem['title'] 86 | href = dictionary_info_problem['href'] 87 | subcategory = dictionary_info_problem['subcategory'] 88 | complexity, classes = dictionary_info_problem['complexity'], dictionary_info_problem['classes'] 89 | condition = dictionary_info_problem['conditions'] 90 | 91 | # если "правильно", то в user_data['correct'] добавляется id карточки 92 | if message.text == emoji.emojize(":white_check_mark:") + ' Правильно': 93 | correct = user_data['correct'] 94 | # Если названия задачки не существует, то в вывод подается не название задача и id задачи 95 | if title == 'None': 96 | correct.append(id) 97 | else: 98 | correct.append(title) 99 | correct.append(href) 100 | await state.update_data(correct=correct) 101 | 102 | # добавление action cat_logic в бд 103 | action_add(message.from_user.id, 'cat_logic', True) 104 | else: 105 | action_add(message.from_user.id, 'cat_logic', False) 106 | 107 | # Образка словаря 108 | info_problem = dict(list(dictionary_info_problem.items())[6:]) 109 | 110 | await state.update_data(problems_info_data_logic=info_problem) 111 | 112 | if str(title) == 'None': 113 | title = id 114 | 115 | link_problems = hlink('Ссылка на задачу', href) 116 | # В задачках логики нет сложности, классов и подкатегорий, поэтому вынес в отдельную переменную 117 | dop_info = f'\nПодкатегория: {subcategory}\nСложность: {complexity}\nКлассы: {classes}' 118 | await message.answer( 119 | f'Название задания или его ID: {title}\n{link_problems}', 120 | reply_markup=logic_menu.get_keyboard_logic_category(), disable_web_page_preview=True) 121 | await message.answer(f'{condition}', 122 | reply_markup=logic_menu_inline.get_inline_logic_problems_category_info(info_problem)) 123 | 124 | 125 | async def tasks_category_logic_print_info(call: types.CallbackQuery, callback_data: dict, state:FSMContext): 126 | """ 127 | ВОТ ТУТ НУЖНО ИСПРАВЛЯТЬ, Т.К ТУТ НЕПОНЯТНО ЗАЧЕМ НУЖЕН TRANSLATE, ЕСЛИ ЕСТЬ info_logic 128 | """ 129 | user_data = await state.get_data() 130 | problems_info_data_logic = user_data['problems_info_data_logic'] 131 | translate = callback_data['translate_logic'] 132 | 133 | if translate == 'Решение 1': 134 | await call.message.answer(f'{problems_info_data_logic["decisions_1"]}') 135 | elif translate == 'Решение 2': 136 | await call.message.answer(f'{problems_info_data_logic["decisions_2"]}') 137 | elif translate == 'Ответ': 138 | await call.message.answer(f'{problems_info_data_logic["answer"]}') 139 | elif translate == 'Замечания': 140 | await call.message.answer(f'{problems_info_data_logic["remarks"]}') 141 | 142 | await call.answer() 143 | 144 | 145 | async def tasks_category_logic_end(message: types.Message, state: FSMContext): 146 | user_data = await state.get_data() 147 | # Список correct содержит id задач и сразу после id идет ссылка на задачу 148 | correct = user_data['correct'] 149 | await state.finish() 150 | string_correct = '' 151 | count = 1 152 | # Создание статистики 153 | for i in range(0, len(correct), 2): 154 | link_problems = hlink('Ссылка на задачу', correct[i + 1]) 155 | string_correct += f"{count}: id - {correct[i]} ({link_problems})\n" 156 | count += 1 157 | 158 | await message.answer( 159 | emoji.emojize(":bar_chart:") + f"Количество правильно решённых задач: {len(correct) // 2}\n{string_correct}", 160 | disable_web_page_preview=True) 161 | 162 | await message.answer(emoji.emojize(":red_circle: ") + ' Выполнение задачек закончилось', 163 | reply_markup=types.ReplyKeyboardRemove()) 164 | 165 | 166 | class LogicCategory(StatesGroup): 167 | """Данные state нужен, чтобы отделять одинаковые кнопки 'Закончить' и 'Следующая задача'""" 168 | logic_step = State() 169 | 170 | 171 | def register_handlers_tasks_logic_category(dp: Dispatcher): 172 | dp.register_message_handler(tasks_category_logic_start, 173 | Text(equals=emoji.emojize(":book:") + ' Задания из категорий'), 174 | state=LogicButCategory.logic_category_step) 175 | 176 | all_files_names = [i[0] for i in finding_categories_table('logic')] 177 | dp.register_callback_query_handler(tasks_category_logic_print_keyboard_inline, 178 | callback_problems_logic.filter(category_logic=all_files_names), state='*') 179 | choose = [emoji.emojize(":white_check_mark:") + ' Правильно', emoji.emojize(":x:") + ' Неправильно'] 180 | dp.register_message_handler(tasks_category_logic_print_keyboard_default, 181 | Text(choose), 182 | state=LogicCategory.logic_step) 183 | dp.register_message_handler(tasks_category_logic_end, 184 | Text(equals=emoji.emojize(":stop_sign:") + ' Закончить'), 185 | state=LogicCategory.logic_step) 186 | 187 | info = ['Decision 1', 'Decision 2', 'Answer', 'Remarks'] 188 | dp.register_callback_query_handler(tasks_category_logic_print_info, 189 | callback_problems_info_logic.filter(info_logic=info), state='*') 190 | -------------------------------------------------------------------------------- /handlers/math/math.py: -------------------------------------------------------------------------------- 1 | from aiogram import types, Dispatcher 2 | from aiogram.dispatcher import FSMContext 3 | from aiogram.dispatcher.filters.state import StatesGroup, State 4 | 5 | from handlers.keyboards.default import math_menu 6 | 7 | 8 | class MathButCategory(StatesGroup): 9 | """Данные state нужен, чтобы кнопки 'Задания из категорий' """ 10 | math_category_step = State() 11 | 12 | 13 | async def math_start(message: types.Message, state: FSMContext): 14 | await state.finish() 15 | await message.answer('Выберите:', reply_markup=math_menu.get_keyboard_math_start()) 16 | await MathButCategory.math_category_step.set() 17 | 18 | 19 | def register_handlers_math(dp: Dispatcher): 20 | dp.register_message_handler(math_start, commands='math', state="*") 21 | -------------------------------------------------------------------------------- /handlers/math/math_formulas.py: -------------------------------------------------------------------------------- 1 | """ 2 | В данный момент тут ничего не работает, будем или перерабатывать или вообще удалять из-за ненадобности кода 3 | 4 | ДАЖЕ НЕ ЗАПУСКАЕТСЯ, удалите комментарий из register_cmd 5 | """ 6 | 7 | from aiogram.dispatcher import FSMContext 8 | from aiogram import types, Dispatcher 9 | from aiogram.dispatcher.filters import Text 10 | from aiogram.dispatcher.filters.state import State, StatesGroup 11 | from aiogram.utils.callback_data import CallbackData 12 | 13 | from data_b.dp_control import formulas_search_random 14 | from handlers.keyboards.default.math_menu import get_keyboard_math_formulas 15 | from handlers.keyboards.inline.math_menu_inline import get_inline_math_formulas 16 | 17 | 18 | async def math_formulas_start(message: types.Message): 19 | await message.answer('Вы готовы?', reply_markup=types.ReplyKeyboardRemove()) 20 | await Formulas.math_formulas.set() 21 | 22 | 23 | async def math_formulas(message: types.Message, state: FSMContext): 24 | c = formulas_search_random() 25 | condition_dp = c[0] 26 | answer_dp = c[1] 27 | 28 | user_data = await state.get_data() 29 | 30 | if len(user_data) == 0: 31 | await state.update_data(explanation=[]) 32 | await state.update_data(condition=[]) 33 | await state.update_data(answer=[]) 34 | user_data = await state.get_data() 35 | 36 | if c[2]: 37 | explanation_dp = c[2] 38 | explanations = user_data['explanation'] 39 | explanations.append(explanation_dp) 40 | await state.update_data(explanation=explanations) 41 | 42 | answers = user_data['answer'] 43 | answers.append(answer_dp) 44 | 45 | conditions = user_data['condition'] 46 | conditions.append(condition_dp) 47 | 48 | await state.update_data(condition=conditions, answer=answers) 49 | 50 | await message.answer(f"Формула:\n{condition_dp}", reply_markup=get_inline_math_formulas()) 51 | await message.answer(f'Посмотрите Продолжить или закончить?', reply_markup=get_keyboard_math_formulas()) 52 | await Formulas.math_formulas.set() 53 | 54 | 55 | async def hint_func(call: types.CallbackQuery, state: FSMContext): 56 | user_data = await state.get_data() 57 | explanation = user_data['explanation'][-1] 58 | 59 | await call.message.answer(f'Вот объяснение:\n{explanation}') 60 | await call.answer() 61 | 62 | 63 | async def answer_func(call: types.CallbackQuery, state: FSMContext): 64 | user_data = await state.get_data() 65 | answer = user_data['answer'][-1] 66 | 67 | await call.message.answer(f'Вот ответ:\n{answer}') 68 | await call.answer() 69 | 70 | 71 | async def math_formulas_end(message: types.Message, state: FSMContext): 72 | await message.answer('Отличная работа', reply_markup=types.ReplyKeyboardRemove()) 73 | user_data = await state.get_data() 74 | 75 | conditions = user_data['condition'] 76 | 77 | for i in range(len(conditions)): 78 | await message.answer( 79 | f'Формула номер {i + 1}:\n{conditions[i]}\n') 80 | await state.finish() 81 | 82 | 83 | class Formulas(StatesGroup): 84 | math_formulas = State() 85 | 86 | 87 | def register_handlers_math_formulas(dp: Dispatcher): 88 | dp.register_message_handler(math_formulas_start, Text(equals="Формулы")) 89 | dp.register_message_handler(math_formulas_start, commands="math_formulas") 90 | dp.register_message_handler(math_formulas_end, Text(equals="закончить повторение", ignore_case=True), state="*") 91 | dp.register_message_handler(math_formulas, state=Formulas.math_formulas) 92 | dp.register_callback_query_handler(hint_func, text="hint_f", state="*") 93 | dp.register_callback_query_handler(answer_func, text="answer_f", state="*") 94 | -------------------------------------------------------------------------------- /handlers/math/mentally_math.py: -------------------------------------------------------------------------------- 1 | """ 2 | Основной алгоритм очень схож с flashcards_training, но у него есть некоторое отличие 3 | Из-за того, что тут требуется проверять постоянный ввод пользователя, то тут используется такая вещь - есть вводная 4 | функция equation_mentally_beginning, которая 5 | 1) Проверяет, что написали "Да" 6 | 2) Создаёт user_data 7 | 3) Создаёт пример и создаёт вход в главную функцию equation_mentally 8 | 9 | Потом уже функция equation_mentally, генерирует примеры; отсекает неправильные варианты; и вызывает САМУ СЕБЯ. 10 | 11 | Если бы мы сделали сразу основную функцию без такой вводной, то нам было бы очень сложно различать сообщения 12 | неправильные от правильных. Например: Пользователь присылает "Да" --> Пользователю присылается карточка и он 13 | присылает "1570" --> Дальше программа будет сначала сравнивать, что это не "Да", что сходится ли оно с ответом и 14 | т.д Поэтому мы укоротили этот пути и алгоритм работает с equation_mentally 15 | 16 | """ 17 | 18 | from random import choice, randint 19 | from aiogram.dispatcher import FSMContext 20 | from aiogram import types, Dispatcher 21 | from aiogram.dispatcher.filters import Text 22 | from aiogram.dispatcher.filters.state import StatesGroup, State 23 | from aiogram.types import InputFile 24 | from aiogram.utils import emoji 25 | 26 | from data_b.dp_control import action_add 27 | from handlers.keyboards.default import math_menu 28 | from handlers.keyboards.inline import math_menu_inline 29 | 30 | 31 | async def equation_mentally_theory(message: types.Message): 32 | await message.answer( 33 | 'Мы должны использовать: Круглые Числа\nОдин из самых распространённых приёмов устного счёта' 34 | ' заключается в том, что любое число можно представить в виде суммы или разности чисел, одно или ' 35 | 'несколько из которых «круглое»', reply_markup=math_menu_inline.get_inline_math_url()) 36 | photo = InputFile("data/math_1.jpg") 37 | await message.answer_photo(photo=photo) 38 | await message.answer( 39 | 'Упростим умножение делением\nПри устном счёте бывает удобнее оперировать делимым и делителем нежели ' 40 | 'целым числом (например, 5 представлять в виде 10:2, а 50 в виде 100:2):') 41 | await message.answer( 42 | '68 x 50 = (68 x 100) : 2 = 6800 : 2 = 3400;\n' 43 | '3400 : 50 = (3400 x 2) : 100 = 6800 : 100 = 68.') 44 | await message.answer( 45 | '625 x 53 = 625 x 50 + 625 x 3 = (625 x 100) : 2 + 600 x 3 + 25 x 3 = (625 x 100) : 2 + 1800 + ' 46 | '(20 + 5) x 3 = (60000 + 2500) : 2 + 1800 + 60 + 15 = 30000 + 1250 + 1800 + 50 + 25 = 33000 + ' 47 | '50 + 50 + 25 = 33125.') 48 | await message.answer( 49 | 'Возведение в квадрат двузначного числа\nОказывается, чтобы просто возвести любое двузначное число в ' 50 | 'квадрат, достаточно запомнить квадраты всех чисел от 1 до 25. Благо, квадраты до 10 мы уже знаем из таблицы ' 51 | 'умножения. Остальные квадраты можно посмотреть в нижеприведённой таблице:') 52 | 53 | photo_2 = InputFile("data/math_2.jpg") 54 | await message.answer_photo(photo=photo_2) 55 | await message.answer( 56 | 'Приём Рачинского заключается в следующем. Для того чтобы найти квадрат любого двузначного числа, надо разность' 57 | ' между этим числом и 25 умножить на 100 и к получившемуся произведению прибавить квадрат ' 58 | 'дополнения данного числа до 50 или квадрат избытка его над 50-ю. Например:') 59 | await message.answer( 60 | '37^2 = 12 x 100 + 13^2 = 1200 + 169 = 1369;\n84^2 = 59 x 100 + 34^2 = 5900 + 9 x' 61 | ' 100 + 16^2 = 6800 + 256 = 7056;') 62 | 63 | 64 | async def equation_mentally_start(message: types.Message): 65 | await message.answer('Чтобы вызвать подсказку напишите /mell_theory') 66 | await message.answer('Вы готовы?', reply_markup=math_menu.get_keyboard_math_mentally_start()) 67 | await Equation.equation_mentally_beginning.set() 68 | 69 | 70 | async def equation_mentally_beginning(message: types.Message, state: FSMContext): 71 | if message.text != 'Да': 72 | await message.answer('Вы написали что-то не то') 73 | return 74 | else: 75 | await message.answer( 76 | 'Чтобы закончить выполненение или пропишите /end_mental, или нажмите на кнопку "Закончить"') 77 | 78 | # Генерирует пример ввида [equation, answer] 79 | equation = equation_generate() 80 | 81 | # ------ Создание user_data --------- 82 | 83 | # В начале программы 'user_data' - пуста и создаются сделующие списки 84 | await state.update_data(condition=[]) 85 | await state.update_data(answer=[]) 86 | await state.update_data(attempts=[]) 87 | 88 | user_data = await state.get_data() 89 | 90 | # ------ Определение user_data ------- 91 | # хранит все условия примеров 92 | conditions = user_data['condition'] 93 | conditions.append(equation[0]) 94 | await state.update_data(condition=conditions) 95 | 96 | # хранит все ответы примеров 97 | answers = user_data['answer'] 98 | answers.append(equation[1]) 99 | await state.update_data(answer=answers) 100 | 101 | # хранит всё количество попыток на ответы примеров 102 | attempt = user_data['attempts'] 103 | # Количество попыток мы создаём 0 - это сделанно для того, чтобы если пользователь завершит задание, не 104 | # приступив к нему, то это не покажется в статистике, т.к попыток НОЛЬ 105 | attempt.append(0) 106 | await state.update_data(attempts=attempt) 107 | 108 | await message.answer(f'Решите в уме:\n{equation[0]}', reply_markup=math_menu.get_keyboard_math_mentally_end()) 109 | await Equation.equation_mentally.set() 110 | 111 | 112 | async def equation_mentally(message: types.Message, state: FSMContext): 113 | """ 114 | Основаня функция 115 | 116 | :param message: Ждёт сообщения соостоящее из цифр. Например "8371" 117 | 118 | :return Вызывает саму себя, пока пользователь не закончит 119 | """ 120 | 121 | # Проверка что сообщение - число 122 | try: 123 | msg = int(message.text) 124 | except ValueError: 125 | await message.answer(f'Неправильные знаки, введите число') 126 | else: 127 | user_data = await state.get_data() 128 | 129 | answers = user_data['answer'] 130 | conditions = user_data['condition'] 131 | attempts = user_data['attempts'] 132 | 133 | # считает количество попыток и прибавляет 134 | """ 135 | Cделанно это вначале, чтобы потом отсекать 0 варианты, потому что сообщение может показаться пользователю, 136 | а он просто без попыток закончит тренировку 137 | """ 138 | cc = int(attempts[-1]) + 1 139 | attempts[-1] = cc 140 | await state.update_data(attempts=attempts) 141 | 142 | if msg != int(answers[-1]): 143 | await message.answer('Неправильно, попробуйте ещё раз') 144 | # Если было уже 3 попытки, то пользователю предложит пройти теорию ещё раз 145 | if int(attempts[-1]) % 3 == 0: 146 | await message.answer('Посмотрите ещё раз "подсказку":\n' 147 | 'Для этого нажмите или наберите /mell_theory') 148 | await message.answer(f'Решите в уме:\n{conditions[-1]}') 149 | 150 | # добавление action men_math в бд 151 | action_add(message.from_user.id, 'men_math', False) 152 | return 153 | else: 154 | await message.answer('Правильно, следующее задание') 155 | 156 | # Генерирует пример ввида [equation, answer] 157 | equation = equation_generate() 158 | 159 | # ------ Добавляем примеры, ответы, попытки в user_data ------- 160 | 161 | conditions.append(equation[0]) 162 | await state.update_data(condition=conditions) 163 | answers.append(equation[1]) 164 | await state.update_data(answer=answers) 165 | # Количество попыток мы создаём 0 - это сделанно для того, чтобы если пользователь завершит задание, не 166 | # приступив к нему, то это не покажется в статистике, т.к попыток НОЛЬ 167 | attempts.append(0) 168 | await state.update_data(attempts=attempts) 169 | 170 | await message.answer(f'Решите в уме:\n{equation[0]}') 171 | 172 | action_add(message.from_user.id, 'men_math', True) 173 | 174 | await Equation.equation_mentally.set() 175 | 176 | 177 | async def equation_mentally_end(message: types.Message, state: FSMContext): 178 | """ 179 | Функция присылает статистику по тренировке и закачивает тренировку 180 | 181 | Вызов: 1.Если написали /end_mental 182 | 2.Если нажали на кнопку "Закончить" 183 | 184 | :return: Конец тренировки, state.finish() 185 | """ 186 | await message.answer('Отличная работа', reply_markup=math_menu.get_keyboard_math_start()) 187 | user_data = await state.get_data() 188 | 189 | conditions = user_data['condition'] 190 | answer = user_data['answer'] 191 | attempt = user_data['attempts'] 192 | 193 | # мини статистика: 194 | for i in range(len(conditions)): 195 | # если количество попыток 0, то не присылает статистику по этому заданию 196 | if int(attempt[i]) != 0: 197 | await message.answer( 198 | f'Условие задачи:\n{conditions[i]}\n Ответ: {answer[i]}\n Количество попыток: {attempt[i]}') 199 | await state.finish() 200 | 201 | 202 | class Equation(StatesGroup): 203 | equation_mentally_beginning = State() 204 | equation_mentally = State() 205 | 206 | 207 | def equation_generate(): 208 | """ 209 | Функция создаёт математический пример состоящий из 2'х значных чисел и 210 | перемножет их или возводит в степень 211 | 212 | :return: equation - Математический пример 213 | :return: answer - Ответ на пример 214 | """ 215 | mathematically_signs = ['*', '**'] 216 | sign = choice(mathematically_signs) 217 | A = str(randint(11, 99)) 218 | if sign == '**': 219 | B = 2 220 | else: 221 | B = str(randint(11, 99)) 222 | equation = f'{A} {sign} {B}' 223 | answer = eval(equation) 224 | 225 | return [equation, answer] 226 | 227 | 228 | def register_handlers_math_mentally(dp: Dispatcher): 229 | """ 230 | Если меняете алгоритм, то незабудьте поменять state в таймере 231 | """ 232 | dp.register_message_handler(equation_mentally_start, 233 | Text(equals=emoji.emojize(":brain:") + ' Примеры для подсчёта в уме'), state='*') 234 | dp.register_message_handler(equation_mentally_start, commands="equation_mentally") 235 | 236 | dp.register_message_handler(equation_mentally_theory, commands='mell_theory', state='*') 237 | 238 | dp.register_message_handler(equation_mentally_end, commands='end_mental', state='*') 239 | 240 | # state кнопки "Закончить" наследуется от Equation.equation_mentally, чтобы отделить аналогичные кнопки 241 | # от category_math, category_logic. Отделяется именно от Equation.equation_mentally, потому что кнопка "Закончить" 242 | # работает только в рамках этого алгоритма 243 | dp.register_message_handler(equation_mentally_end, 244 | Text(equals=emoji.emojize(":stop_sign:") + ' Закончить'), 245 | state=Equation.equation_mentally) 246 | 247 | dp.register_message_handler(equation_mentally_beginning, state=Equation.equation_mentally_beginning) 248 | dp.register_message_handler(equation_mentally, state=Equation.equation_mentally) 249 | -------------------------------------------------------------------------------- /handlers/math/tasks_category_math.py: -------------------------------------------------------------------------------- 1 | """ 2 | Основная идея алгоритма в том чтобы отправлять задачи пользвателю после того как он нажмет "Правильно" или "Неправильно". 3 | 4 | Сначал пользователю предоставляется выбор основной категории(функция: tasks_category_math_start), после того как пользователь выберет основную категори 5 | пользователь должен будет выбрать подкатегрию(функция: one_tasks_category), если подкатегории нет, то пользователю сразу присылется задача. 6 | 7 | Основной алгоритм: 8 | 1) Предоставляется выбор основной категории. Функция: tasks_category_math_start 9 | 2) Проверка есть ли подкатегория. Если подкатегории нет, то задача отправляется сразу. Если подкатегория есть 10 | то пользователь выбирает подкатегорию. Функция: one_tasks_category 11 | 3) После выбора подкатегории, пользователю отправляется первая задача. Функция: tasks_category_math_print_keyboard_inline 12 | 4) Когда пользователь ответит "Правильно" или "Неправильно" то вызывается функция: tasks_category_math_print_keyboard_default 13 | 5) Чтобы закончить решение задач, пользователь может прописать "Закончить математику" 14 | 15 | """ 16 | 17 | from aiogram import types, Dispatcher 18 | from aiogram.dispatcher import FSMContext 19 | from aiogram.dispatcher.filters import Text 20 | from aiogram.dispatcher.filters.state import StatesGroup, State 21 | from aiogram.utils import emoji 22 | from aiogram.utils.callback_data import CallbackData 23 | from aiogram.utils.markdown import hlink 24 | 25 | from data_b.dp_control import problem_category_random, finding_categories_table, finding_one_categories_table, \ 26 | finding_main_categories_table, action_add 27 | from handlers.keyboards.default import math_menu 28 | from handlers.keyboards.inline import math_menu_inline 29 | from handlers.math.math import MathButCategory 30 | 31 | callback_problems_math = CallbackData("problems", "category") 32 | callback_problems_info_math = CallbackData("values", "info") 33 | callback_main_problems_math = CallbackData("problems", "category") 34 | 35 | 36 | async def tasks_category_math_start(message: types.Message, state: FSMContext): 37 | await state.update_data(correct=[]) 38 | await message.answer('Выберите категорию заданий:', 39 | reply_markup=math_menu_inline.get_inline_main_math_problems_category()) 40 | link_endrey = hlink('в этот телеграм', 'https://t.me/Endrey_k') 41 | await message.answer(f'Если задание неправильное или неправильно выводиться, то прошу написать {link_endrey}' 42 | ' сообщение вида:\n' 43 | '(категория) - (id задачи или название) - (и часть условия)\n' 44 | 'Например: Математика - 35793 - Дан тетраэдр, у которого пери...', 45 | disable_web_page_preview=True) 46 | 47 | 48 | async def one_tasks_category(call: types.CallbackQuery, callback_data: dict, state: FSMContext): 49 | categories = finding_one_categories_table(call["data"][9:]) 50 | if len(categories) == 1: 51 | global category 52 | category = callback_data["category"][:-5] 53 | # Берёт из бд рандомную задачу и данные хранятся в СЛОВАРЕ 54 | dictionary_info_problem = problem_category_random(category, 'math') 55 | 56 | title = dictionary_info_problem['title'] 57 | href = dictionary_info_problem['href'] 58 | subcategory = dictionary_info_problem['subcategory'] 59 | complexity, classes = dictionary_info_problem['complexity'], dictionary_info_problem['classes'] 60 | condition = dictionary_info_problem['conditions'] 61 | 62 | await state.update_data(card_id=href) 63 | 64 | # Образка словаря 65 | info_problem = dict(list(dictionary_info_problem.items())[6:]) 66 | 67 | global problems_info_data_math 68 | problems_info_data_math = info_problem 69 | try: 70 | link_problems = hlink('Ссылка на задачу', href) 71 | dop_info = f'\nПодкатегория: {subcategory}\nСложность: {complexity}\nКлассы: {classes}' 72 | await call.message.answer( 73 | f'Название задания или его ID: {title}\n{link_problems}{dop_info}', 74 | reply_markup=math_menu.get_keyboard_math_category()) 75 | await call.message.answer(f'{condition}', 76 | reply_markup=math_menu_inline.get_inline_math_problems_category_info( 77 | info_problem)) 78 | 79 | await call.answer() 80 | await MathCategory.math_step.set() 81 | 82 | except Exception: 83 | await call.message.answer('Сломанная задача') 84 | else: 85 | await call.answer() 86 | await call.message.answer('Выберите подкатегорию заданий:', 87 | reply_markup=math_menu_inline.get_inline_one_main_math_problems_category( 88 | callback_data["category"])) 89 | 90 | 91 | async def tasks_category_math_print_keyboard_inline(call: types.CallbackQuery, callback_data: dict, state: FSMContext): 92 | global category 93 | category = callback_data["category"] 94 | # Берёт из бд рандомную задачу и данные хранятся в СЛОВАРЕ 95 | dictionary_info_problem = problem_category_random(category, 'math') 96 | title = dictionary_info_problem['title'] 97 | href = dictionary_info_problem['href'] 98 | subcategory = dictionary_info_problem['subcategory'] 99 | complexity, classes = dictionary_info_problem['complexity'], dictionary_info_problem['classes'] 100 | condition = dictionary_info_problem['conditions'] 101 | 102 | # dict(list(dictionary_info_problem.items())) - Словарь сожержащий всю информацию о задаче 103 | # dict(list(dictionary_info_problem.items())[6:]) - словарь с двумя решениями, ответом и подсказкой 104 | info_problem = dict(list(dictionary_info_problem.items())[6:]) 105 | 106 | global problems_info_data_math 107 | problems_info_data_math = info_problem 108 | 109 | try: 110 | link_problems = hlink('Ссылка на задачу', href) 111 | dop_info = f'\nПодкатегория: {subcategory}\nСложность: {complexity}\nКлассы: {classes}' 112 | await call.message.answer( 113 | f'Название задания или его ID: {title}\n{link_problems}{dop_info}', 114 | reply_markup=math_menu.get_keyboard_math_category()) 115 | await call.message.answer(f'{condition}', 116 | reply_markup=math_menu_inline.get_inline_math_problems_category_info(info_problem)) 117 | 118 | await call.answer() 119 | await MathCategory.math_step.set() 120 | 121 | except Exception: 122 | await call.message.answer('Сломанная задача') 123 | 124 | 125 | async def tasks_category_math_print_keyboard_default(message: types.Message, state: FSMContext): 126 | dictionary_info_problem = problem_category_random(category, 'math') 127 | title = dictionary_info_problem['title'] 128 | href = dictionary_info_problem['href'] 129 | subcategory = dictionary_info_problem['subcategory'] 130 | complexity, classes = dictionary_info_problem['complexity'], dictionary_info_problem['classes'] 131 | condition = dictionary_info_problem['conditions'] 132 | 133 | # dict(list(dictionary_info_problem.items())) - Словарь сожержащий всю информацию о задаче 134 | # dict(list(dictionary_info_problem.items())[6:]) - словарь с двумя решениями, ответом и подсказкой 135 | info_problem = dict(list(dictionary_info_problem.items())[6:]) 136 | 137 | global problems_info_data_math 138 | problems_info_data_math = info_problem 139 | 140 | try: 141 | # если "правильно", то в user_data['correct'] добавляется id карточки 142 | if message.text == emoji.emojize(":white_check_mark:") + ' Правильно': 143 | user_data = await state.get_data() 144 | correct = user_data['correct'] 145 | correct.append(href) 146 | await state.update_data(correct=correct) 147 | 148 | # добавление action cat_math в бд 149 | action_add(message.from_user.id, 'cat_math', True) 150 | else: 151 | action_add(message.from_user.id, 'cat_math', False) 152 | 153 | link_problems = hlink('Ссылка на задачу', href) 154 | dop_info = f'\nПодкатегория: {subcategory}\nСложность: {complexity}\nКлассы: {classes}' 155 | await message.answer( 156 | f'Название задания или его ID: {title}\n{link_problems}{dop_info}', 157 | reply_markup=math_menu.get_keyboard_math_category()) 158 | await message.answer(f'{condition}', 159 | reply_markup=math_menu_inline.get_inline_math_problems_category_info(info_problem)) 160 | await MathCategory.math_step.set() 161 | 162 | except Exception: 163 | await message.answer('Сломанная задача') 164 | 165 | 166 | async def tasks_category_math_print_info(call: types.CallbackQuery, callback_data: dict): 167 | 168 | info = callback_data['info'] 169 | try: 170 | if info == 'Decision 1': 171 | await call.message.answer(f'{problems_info_data_math["decisions_1"]}') 172 | 173 | elif info == 'Decision 2': 174 | await call.message.answer(f'{problems_info_data_math["decisions_2"]}') 175 | 176 | elif info == 'Answer': 177 | await call.message.answer(f'{problems_info_data_math["answer"]}') 178 | 179 | elif info == 'Remarks': 180 | await call.message.answer(f'{problems_info_data_math["remarks"]}') 181 | 182 | except Exception: 183 | await call.message.answer(f'Ответ не выводится') 184 | 185 | await call.answer() 186 | 187 | 188 | async def tasks_category_math_end(message: types.Message, state: FSMContext): 189 | user_data = await state.get_data() 190 | # Список correct содержит ссылки на задачи(в каждой ссылке есть id задачи) 191 | correct = user_data['correct'] 192 | await state.finish() 193 | string_correct = '' 194 | # Создание статистики 195 | for i in range(len(correct)): 196 | link_problems = hlink('Ссылка на задачу', correct[i]) 197 | string_correct += f"{i + 1}: id - {correct[i][52:]} ({link_problems})\n" 198 | 199 | await message.answer( 200 | emoji.emojize( 201 | ":bar_chart:") + f"Количество правильно решённых задач: {len(correct)}\n{string_correct}", 202 | disable_web_page_preview=True) 203 | 204 | await message.answer(emoji.emojize(":red_circle: ") + ' Выполнение задачек закончилось', 205 | reply_markup=math_menu.get_keyboard_math_start()) 206 | 207 | 208 | class MathCategory(StatesGroup): 209 | """Данные state нужен, чтобы отделять одинаковые кнопки 'Закончить' и 'Следующая задача'""" 210 | math_step = State() 211 | math_choose = State() 212 | 213 | 214 | def register_handlers_tasks_math_category(dp: Dispatcher): 215 | dp.register_message_handler(tasks_category_math_start, 216 | Text(equals=emoji.emojize(":book:") + ' Задания из категорий'), 217 | state=MathButCategory.math_category_step) 218 | 219 | all_main_files_names = [i[0] for i in finding_main_categories_table('math')] 220 | dp.register_callback_query_handler(one_tasks_category, 221 | callback_main_problems_math.filter(category=all_main_files_names), state='*') 222 | 223 | all_files_names = [i[0] for i in finding_categories_table('math')] 224 | dp.register_callback_query_handler(tasks_category_math_print_keyboard_inline, 225 | callback_problems_math.filter(category=all_files_names), state='*') 226 | 227 | choose = [emoji.emojize(":white_check_mark:") + ' Правильно', emoji.emojize(":x:") + ' Неправильно'] 228 | dp.register_message_handler(tasks_category_math_print_keyboard_default, 229 | Text(choose), 230 | state=MathCategory.math_step) 231 | dp.register_message_handler(tasks_category_math_end, 232 | Text(equals=emoji.emojize(":stop_sign:") + ' Закончить'), 233 | state=MathCategory.math_step) 234 | 235 | info = ['Decision 1', 'Decision 2', 'Answer', 'Remarks'] 236 | dp.register_callback_query_handler(tasks_category_math_print_info, 237 | callback_problems_info_math.filter(info=info), state='*') 238 | -------------------------------------------------------------------------------- /handlers/register_cmd.py: -------------------------------------------------------------------------------- 1 | """ 2 | Распределяйте всё по блокам, чтобы не запутаться 3 | """ 4 | 5 | # ------main------- 6 | # from handlers.cart import register_handlers_cart 7 | from handlers.cmd import register_handlers_start 8 | 9 | # ------math------- 10 | from handlers.math.math import register_handlers_math 11 | # from handlers.math.math_formulas import register_handlers_math_formulas 12 | from handlers.math.mentally_math import register_handlers_math_mentally 13 | from handlers.math.tasks_category_math import register_handlers_tasks_math_category 14 | 15 | # ------flashcards------- 16 | from handlers.flashcards.flashcard import register_handlers_flashcard 17 | from handlers.flashcards.flashcards_managing import register_handlers_flashcards_managing 18 | from handlers.flashcards.flashcards_training import register_handlers_flashcards_training 19 | 20 | # ------logic------- 21 | from handlers.logic.logic import register_handlers_logic 22 | from handlers.logic.tasks_category_logic import register_handlers_tasks_logic_category 23 | 24 | # ------timer------- 25 | from handlers.admins.admins import register_handlers_send_msg 26 | 27 | from handlers.timer.timer import register_handlers_timer 28 | from handlers.timer.timer_managing import register_handlers_timer_managing 29 | 30 | # ------statistics------- 31 | from handlers.statistics.statistics import register_handlers_statistics 32 | from handlers.statistics.statistics_info import register_handlers_statistics_info 33 | 34 | # ------admins------- 35 | from handlers.admins.send_message_all import register_handlers_send_message_all 36 | from handlers.admins.statistics_info_admins import register_handlers_statistics_info_admins 37 | from handlers.admins.delete_tasks import register_handlers_del_task 38 | 39 | def reg_cmd(dp): 40 | # ------main------- 41 | register_handlers_start(dp) 42 | # register_handlers_cart(dp) 43 | 44 | # ------math------- 45 | register_handlers_math(dp) 46 | register_handlers_math_mentally(dp) 47 | # register_handlers_math_formulas(dp) # ----- ФОРМУЛЫ 48 | register_handlers_tasks_math_category(dp) 49 | 50 | # ------flashcards------- 51 | register_handlers_flashcard(dp) 52 | register_handlers_flashcards_managing(dp) 53 | register_handlers_flashcards_training(dp) 54 | 55 | # ------logic------- 56 | register_handlers_tasks_logic_category(dp) 57 | register_handlers_logic(dp) 58 | 59 | # ------timer------- 60 | register_handlers_timer(dp) 61 | register_handlers_timer_managing(dp) 62 | 63 | # ------statistics------- 64 | register_handlers_statistics(dp) 65 | register_handlers_statistics_info(dp) 66 | 67 | # ------admins------- 68 | register_handlers_send_msg(dp) 69 | register_handlers_send_message_all(dp) 70 | register_handlers_statistics_info_admins(dp) 71 | register_handlers_del_task(dp) -------------------------------------------------------------------------------- /handlers/statistics/charts.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | import datetime 3 | 4 | import pytz 5 | 6 | import matplotlib.pyplot as plt 7 | from matplotlib.ticker import MaxNLocator 8 | 9 | 10 | def pie_chart(info_general, telegram_user_id): 11 | """ 12 | Круглая диаграмма действий пользоватля за всё время 13 | :param info_general: массив количества типов action. Например: (20, 17, 4, 6) 14 | :param telegram_user_id: id пользователя 15 | :return: круглая диаграмма формата .png 16 | """ 17 | 18 | flc, men_math, cat_math, cat_logic = info_general 19 | vals = [cat_math, cat_logic, men_math, flc] 20 | labels = ['Категория Математика', 'Категория Логики', 'Задачки в уме', 'Flashcards'] 21 | color = ['#EF3038', '#FFCF40', '#DD80CC', '#1dceb2'] 22 | fig = plt.figure(figsize=(5, 4), facecolor='#b7e5db') 23 | ax = fig.add_subplot() 24 | wedges, texts, autotexts = ax.pie(vals, labels=labels, colors=color, autopct='%.1f%%', startangle=90, 25 | textprops=dict(fontfamily="Arial")) 26 | plt.setp(texts, fontsize=10) 27 | plt.setp(autotexts, fontsize=10) 28 | ax.set_title('Процент всех задач', fontsize=18, fontweight='bold') 29 | 30 | # dpi - настройка разрешения, чтобы фотография весила меньше 31 | fig.savefig(f'handlers/statistics/{telegram_user_id}.png', dpi=100) 32 | return 33 | 34 | 35 | def bar_chart(list_time, telegram_user_id): 36 | """ 37 | Диаграмма bar действий разных типов actions пользователя за неделю 38 | :param list_time: высылает типы actions и время за последнюю неделю (включая сегодняшний день). 39 | Например: [['03-20', '03-20'], ['03-22'], [], ['03-22']] 40 | :param telegram_user_id: id пользователя 41 | :return: диаграмма bar формата .png 42 | """ 43 | 44 | time_moscow = datetime.datetime.now(pytz.timezone('Europe/Moscow')) 45 | 46 | # Генерация массива дат(формата %m-%d %w) и дней недели за послелнюю неделю 47 | arr_time_week_0 = [(time_moscow - datetime.timedelta(days=6 - i)).strftime("%m-%d %w").split() for i in 48 | range(7)] 49 | 50 | # Массив дней недели 51 | arr_time_week_days = [i[1] for i in arr_time_week_0] 52 | 53 | # Массив дат 54 | arr_time_week = [i[0] for i in arr_time_week_0] 55 | 56 | # Словарь дат, массивы вида [0, 0, 0, 0] нужны, чтобы впоследствии добавлять туда количество типов actions 57 | # решённых за опрделелённый день 58 | arr_time_week_dict = Counter(dict.fromkeys(arr_time_week, [0, 0, 0, 0])) 59 | 60 | flc_0, men_math_0, cat_math_0, cat_logic_0 = list_time 61 | flc, men_math, cat_math, cat_logic = Counter(flc_0), Counter(men_math_0), Counter(cat_math_0), Counter(cat_logic_0) 62 | 63 | # цикл проходится по датам за неделю и если находит нужную дату в словаре(flc и т.д), то добавляет 64 | # туда количество решений за этот день 65 | for time in arr_time_week_dict: 66 | if time in flc: 67 | flc_count = arr_time_week_dict[time][0] + flc[time] 68 | arr_time_week_dict[time] = [flc_count, arr_time_week_dict[time][1], arr_time_week_dict[time][2], 69 | arr_time_week_dict[time][3]] 70 | 71 | if time in men_math: 72 | men_math_count = arr_time_week_dict[time][1] + men_math[time] 73 | arr_time_week_dict[time] = [arr_time_week_dict[time][0], men_math_count, arr_time_week_dict[time][2], 74 | arr_time_week_dict[time][3]] 75 | 76 | if time in cat_math: 77 | cat_math_count = arr_time_week_dict[time][2] + cat_math[time] 78 | arr_time_week_dict[time] = [arr_time_week_dict[time][0], arr_time_week_dict[time][1], cat_math_count, 79 | arr_time_week_dict[time][3]] 80 | 81 | if time in cat_logic: 82 | cat_logic_count = arr_time_week_dict[time][3] + cat_logic[time] 83 | arr_time_week_dict[time] = [arr_time_week_dict[time][0], arr_time_week_dict[time][1], 84 | arr_time_week_dict[time][2], cat_logic_count] 85 | 86 | time_week_data = sorted(arr_time_week_dict.keys()) 87 | flc_data = [arr_time_week_dict[i][0] for i in time_week_data] 88 | men_math_data = [arr_time_week_dict[i][1] for i in time_week_data] 89 | cat_math_data = [arr_time_week_dict[i][2] for i in time_week_data] 90 | cat_logic_data = [arr_time_week_dict[i][3] for i in time_week_data] 91 | 92 | rus_weekdays = {'0': 'Вс.', '1': 'Пон.', '2': 'Вт.', '3': 'Ср.', '4': 'Чет.', '5': 'Пт.', '6': 'Суб.'} 93 | 94 | x = [rus_weekdays[i] for i in arr_time_week_days] 95 | 96 | fig = plt.figure(figsize=(6, 5), facecolor='#b7e5db') 97 | ax = fig.add_subplot() 98 | 99 | # Чтобы столбцы нормально накладывались - нужно суммировать элементы, иначе, один столб будет закрывать другой 100 | ax.bar(x, flc_data, color='#1dceb2') 101 | ax.bar(x, men_math_data, bottom=flc_data, color='#DD80CC') 102 | ax.bar(x, cat_math_data, bottom=[sum(i) for i in zip(flc_data, men_math_data)], color='#EF3038') 103 | ax.bar(x, cat_logic_data, bottom=[sum(i) for i in zip(flc_data, men_math_data, cat_math_data)], color='#FFCF40') 104 | 105 | ax.yaxis.set_major_locator(MaxNLocator(integer=True)) 106 | 107 | lgd = ax.legend(labels=['Flashcards', 'Задачки в уме', 'Категория Математика', 'Категория Логики'], 108 | title="Категории заданий", loc="upper left", bbox_to_anchor=(1, 0, 0.5, 1)) 109 | text = ax.text(-0.2, 1.05, 'Выполненые задачи за неделю', fontsize=18, fontweight='bold', transform=ax.transAxes) 110 | 111 | fig.savefig(f'handlers/statistics/{telegram_user_id}.png', bbox_extra_artists=(lgd, text), 112 | bbox_inches='tight') 113 | return 114 | -------------------------------------------------------------------------------- /handlers/statistics/statistics.py: -------------------------------------------------------------------------------- 1 | from aiogram import types, Dispatcher 2 | from aiogram.dispatcher import FSMContext 3 | 4 | from handlers.keyboards.default import statistics_menu 5 | 6 | 7 | async def stat_start(message: types.Message, state: FSMContext): 8 | await state.finish() 9 | await message.answer('Выберите:', reply_markup=statistics_menu.get_keyboard_statistics_start()) 10 | 11 | 12 | def register_handlers_statistics(dp: Dispatcher): 13 | dp.register_message_handler(stat_start, commands='statistics', state="*") 14 | -------------------------------------------------------------------------------- /handlers/statistics/statistics_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | Статитстика работает так: 3 | 1. Рисует круглую диаграмму на основе данных функции (stat_general_bd), который возвращает массив количества типов 4 | action (действий пользователя): в общем, от flc, от mentally math, от category_math и от category_logic. 5 | Потом сохроняется, показывается поьзователю, и фотография удаляется 6 | 7 | 2. Рисует диаграмму (bar) на основе данных функции(stat_bar_general), которая высылает типы actions пользователя за 8 | неделю. Потом также сохраняется, отправляется пользователю и фотография удаляется 9 | """ 10 | 11 | from aiogram import types, Dispatcher 12 | from aiogram.dispatcher.filters import Text 13 | from aiogram.utils import emoji 14 | from data_b.dp_control import stat_general_bd, stat_bar_general 15 | from handlers.statistics.charts import pie_chart, bar_chart 16 | from aiogram.types import InputFile 17 | import os 18 | 19 | 20 | # ---------------------------general (общая статистика)------------------------------ 21 | async def stat_general(message: types.Message): 22 | user_id = message.from_user.id 23 | info_general = stat_general_bd(user_id)[0] 24 | await message.answer(f'Ваша общая статистика:\n' 25 | f'Показов flashcard: {info_general[0]}\n' 26 | f'Попыток mentally math: {info_general[1]}\n' 27 | f'Показов category_math: {info_general[2]}\n' 28 | f'Показов category_logic: {info_general[3]}', reply_markup=types.ReplyKeyboardRemove()) 29 | 30 | pie_chart(info_general, user_id) 31 | photo = InputFile(f"handlers/statistics/{user_id}.png") 32 | await message.answer_photo(photo=photo) 33 | os.remove(f"handlers/statistics/{user_id}.png") 34 | 35 | list_time = stat_bar_general(user_id) 36 | bar_chart(list_time, user_id) 37 | photo = InputFile(f"handlers/statistics/{user_id}.png") 38 | await message.answer_photo(photo=photo) 39 | os.remove(f"handlers/statistics/{user_id}.png") 40 | 41 | return 42 | 43 | 44 | def register_handlers_statistics_info(dp: Dispatcher): 45 | dp.register_message_handler(stat_general, 46 | Text(equals=emoji.emojize(":bar_chart:") + ' Общая'), state='*') 47 | -------------------------------------------------------------------------------- /handlers/timer/timer.py: -------------------------------------------------------------------------------- 1 | from aiogram import types, Dispatcher 2 | from aiogram.dispatcher import FSMContext 3 | from aiogram.dispatcher.filters import Text 4 | 5 | from handlers.keyboards.default import timer_menu 6 | 7 | 8 | async def timer_select(message: types.Message, state: FSMContext): 9 | await state.finish() 10 | await message.answer('Таймер позволяет вам выполнять определённые задания в определённое время.' 11 | '\nНапример: в 10:00 бот присылает сообщение, что началось ежедневная тренировка подсчёта в уме' 12 | '\n\nТакже:\n' 13 | '1) Таймеров может быть несколько\n' 14 | '2) Таймеры могут присылать 4 категории заданий: карточки, задания математики, задания логики, ' 15 | 'примеры в уме', reply_markup=timer_menu.get_keyboard_timer()) 16 | 17 | 18 | def register_handlers_timer(dp: Dispatcher): 19 | dp.register_message_handler(timer_select, commands='timer', state="*") 20 | dp.register_message_handler(timer_select, Text(equals="Таймер", ignore_case=True), state="*") 21 | -------------------------------------------------------------------------------- /handlers/timer/timer_cycle.py: -------------------------------------------------------------------------------- 1 | """ 2 | Алгоритм таймера работает следующим образом: 3 | Раз в 60 секунд запускается проверка, есть ли данное время (например 16:03) в базе данных time 4 | и если есть то выводит "Ежедневное задание" 5 | """ 6 | 7 | from aiogram import types 8 | from data_b.dp_control import dp_timer_circle_user_time, del_user 9 | from handlers.flashcards.flashcards_training import Flash_game 10 | from handlers.keyboards.default import flashcard_menu, math_menu 11 | from handlers.keyboards.inline import math_menu_inline 12 | from handlers.math.mentally_math import Equation 13 | import pytz 14 | from datetime import datetime 15 | 16 | 17 | async def time_cycle(dp): 18 | # Определение времени по московскому времени (небходимо для сервера, чтобы он определял какое время сейчас в Москве) 19 | time_moscow = datetime.now(pytz.timezone('Europe/Moscow')) 20 | time_now = time_moscow.strftime("%H:%M") 21 | 22 | # Массив telegram_user_id и tasks, таймер которых совпал с нужным временем 23 | time_results = dp_timer_circle_user_time(time_now) 24 | 25 | if time_results: 26 | for i in time_results: 27 | user_id = i[0] 28 | tasks = i[1] 29 | """ 30 | Без создания state, на версии 2.16 aiogram выдаёт ошибку: 31 | AttributeError: 'NoneType' object has no attribute 'current_state' 32 | Поэтому надо создавать state вручную 33 | """ 34 | state = dp.current_state(chat=user_id, user=user_id) 35 | 36 | await dp.bot.send_message(user_id, 'Ежедневное задание!', 37 | reply_markup=types.ReplyKeyboardRemove()) 38 | 39 | # Идёт проверка какого типо tasks 40 | if tasks == 'Карточки (Flashcards)': 41 | await dp.bot.send_message(user_id, 'Лайфхаки для работы с карточками /cards_info') 42 | await dp.bot.send_message(user_id, 'Вы готовы?', 43 | reply_markup=flashcard_menu.get_keyboard_flashcard_training_start()) 44 | await state.set_state(Flash_game.flc_game) 45 | 46 | elif tasks == 'Математика в уме': 47 | await dp.bot.send_message(user_id, 'Чтобы вызвать подсказку напишите /mell_theory') 48 | await dp.bot.send_message(user_id, 'Вы готовы?', 49 | reply_markup=math_menu.get_keyboard_math_mentally_start()) 50 | await state.set_state(Equation.equation_mentally_beginning) 51 | 52 | elif tasks == 'Задачи по математике': 53 | await dp.bot.send_message(user_id, 'Выберите категорию заданий:', 54 | reply_markup=math_menu_inline.get_inline_math_problems_category()) 55 | 56 | elif tasks == 'Задачи по логике': 57 | from handlers.keyboards.inline import logic_menu_inline 58 | await dp.bot.send_message(user_id, 'Выберите категорию заданий:', 59 | reply_markup=logic_menu_inline.get_inline_logic_problems_category()) 60 | -------------------------------------------------------------------------------- /handlers/timer/timer_managing.py: -------------------------------------------------------------------------------- 1 | """ 2 | Тут 3 функции: 3 | 1. CREATE TIMER (строка 22) 4 | 2. DEL TIMER (строка 81) 5 | 3. INFO TIMER (строка 137) 6 | """ 7 | 8 | from aiogram import types, Dispatcher 9 | from aiogram.dispatcher import FSMContext 10 | from aiogram.dispatcher.filters.state import StatesGroup, State 11 | from aiogram.dispatcher.filters import Text 12 | from aiogram.utils import emoji 13 | 14 | from data_b.dp_control import timer_create_dp, timer_info_dp, timer_del_dp 15 | from handlers.keyboards.default import timer_menu 16 | 17 | 18 | async def timer_select(message: types.Message): 19 | await message.answer('Выберите:', reply_markup=timer_menu.get_keyboard_timer()) 20 | 21 | 22 | # ----------------------------------CREATE TIMER---------------------------------------- 23 | async def timer_create_start(message: types.Message): 24 | """ 25 | Создание таймеров пользователя 26 | """ 27 | await message.answer('Введите нужное вам время в формате:\n' 28 | '16:02\n' 29 | 'Где 16 - это часы, а 02 - минуты\n' 30 | '2 пример: 05:59', reply_markup=types.ReplyKeyboardRemove()) 31 | await Timer.timer_create_middle.set() 32 | 33 | 34 | async def timer_create_middle(message: types.Message, state: FSMContext): 35 | """ 36 | :param message: принимает ВРЕМЯ от пользователся (например: 14:00, 16:03, 03:46) 37 | """ 38 | user_id = message.from_user.id 39 | all_timers = timer_info_dp(user_id) 40 | 41 | msg = message.text 42 | 43 | check_func = checking_message(msg) 44 | 45 | # Проверка на то, что 'check_func' выводит ошибку, типа string 46 | # (проверка нужна, чтобы отличать сообщение с ошибкой от сообщения типа True) 47 | if isinstance(check_func, str): 48 | await message.reply(check_func) 49 | else: 50 | # Проверка что введеный таймер не существует 51 | if msg not in all_timers: 52 | await state.update_data(time=msg) 53 | await message.answer('Выберите, что вы хотите начать повторять', 54 | reply_markup=timer_menu.get_keyboard_question_tasks()) 55 | await Timer.timer_create_end.set() 56 | else: 57 | await message.reply('Такой таймер уже существует') 58 | 59 | 60 | async def timer_create_end(message: types.Message, state: FSMContext): 61 | """ 62 | :param message: принимает от пользователся выбор категории заданий для таймера 63 | """ 64 | msg = message.text 65 | if (msg == 'Карточки (Flashcards)') or (msg == 'Математика в уме') or (msg == 'Задачи по математике') or ( 66 | msg == 'Задачи по логике'): 67 | try: 68 | user_data = await state.get_data() 69 | timer_create_dp(message.from_user.id, user_data["time"], msg) 70 | await message.reply('Таймер успешно установлен', reply_markup=timer_menu.get_keyboard_timer()) 71 | except Exception: 72 | await message.answer(f'Что - то пошло не так') 73 | await state.finish() 74 | 75 | else: 76 | await message.reply('Вы написали что-то не то\n' 77 | 'Нажмите на кнопку, которая вам высветилась ещё раз') 78 | await Timer.timer_create_end.set() 79 | 80 | 81 | # ----------------------------------DEL TIMER---------------------------------------- 82 | 83 | async def timer_del_start(message: types.Message): 84 | """ 85 | Удаление таймеров пользователя 86 | """ 87 | user_id = message.from_user.id 88 | 89 | # массив всех таймером пользователя 90 | all_timers = timer_info_dp(user_id) 91 | if len(all_timers) == 0: 92 | await message.answer('У вас нет таймеров', reply_markup=types.ReplyKeyboardRemove()) 93 | return 94 | 95 | await message.answer('Удалите таймер, написав номер таймера/таймеров\n' 96 | '1 пример: 1\n' 97 | '2 пример: 1 3', reply_markup=types.ReplyKeyboardRemove()) 98 | await message.answer('Какой из таймеров вы хотите удалить?') 99 | 100 | # Вывод информации о таймерах 101 | string_timer = '' 102 | for i in range(len(all_timers)): 103 | string_timer += f'{i + 1}: {all_timers[i]}\n' 104 | 105 | await message.answer(string_timer) 106 | await Timer.timer_del.set() 107 | 108 | 109 | async def timer_del(message: types.Message, state: FSMContext): 110 | msg = message.text 111 | 112 | id_timer_list_str = checking_message_del(msg) 113 | 114 | # Проверка на то что check_func == True 115 | if not (isinstance(id_timer_list_str, str)): 116 | user_id = message.from_user.id 117 | 118 | # Список таймеров пользователя 119 | all_timers = timer_info_dp(user_id) 120 | 121 | for i in range(len(id_timer_list_str)): 122 | 123 | # Проверка на то что номер введенного таймера существует 124 | if len(all_timers) >= int(id_timer_list_str[i]) > 0: 125 | count_id = int(id_timer_list_str[i]) - 1 126 | timer_del_dp(message.from_user.id, all_timers[count_id]) 127 | await message.answer(f'Таймер {all_timers[count_id]} удалён') 128 | else: 129 | await message.answer(f'Таймера под номером {id_timer_list_str[i]} не существует') 130 | await Timer.timer_del.set() 131 | await state.finish() 132 | else: 133 | await message.reply(id_timer_list_str) 134 | await Timer.timer_del.set() 135 | 136 | 137 | # ----------------------------------INFO TIMER---------------------------------------- 138 | 139 | 140 | async def timer_info(message: types.Message): 141 | """ 142 | Вывод информации о таймерах пользователя 143 | """ 144 | user_id = message.from_user.id 145 | all_timers = timer_info_dp(user_id) 146 | if len(all_timers) == 0: 147 | await message.answer('У вас нет таймеров') 148 | else: 149 | await message.answer('Вот все ваши таймеры:') 150 | 151 | string_timer = '' 152 | for i in range(len(all_timers)): 153 | string_timer += f'{i + 1}: {all_timers[i]}\n' 154 | 155 | await message.answer(string_timer) 156 | 157 | 158 | class Timer(StatesGroup): 159 | timer_create_middle = State() 160 | timer_create_end = State() 161 | timer_del = State() 162 | 163 | 164 | def checking_message(msg): 165 | """ 166 | Проверка на то что число написанно вот так: 167 | 13:02 168 | """ 169 | 170 | if ':' in msg: 171 | c = msg.split(':') 172 | if len(c) == 2: 173 | try: 174 | int(c[0]) 175 | int(c[1]) 176 | except ValueError: 177 | return 'Введено не число' 178 | else: 179 | if len(c[0]) == 2 and len(c[1]) == 2: 180 | if (0 <= int(c[0]) < 24) and (0 <= int(c[1]) < 60): 181 | return True 182 | else: 183 | return 'Неправильное время' 184 | else: 185 | return 'Введите числа, как показано в примере' 186 | else: 187 | return 'Переборщили со знаками' 188 | else: 189 | return 'Забыли про знак ":"' 190 | 191 | 192 | def checking_message_del(msg): 193 | """ 194 | Проверка на то что число написанно вот так: 195 | 1 3 или 1 196 | """ 197 | 198 | # Список id таймеров (который ввёл пользователь) 199 | id_timer_list_str = msg.split() 200 | 201 | for i in range(len(id_timer_list_str)): 202 | try: 203 | int(id_timer_list_str[i]) 204 | except ValueError: 205 | return 'Введено не число' 206 | return id_timer_list_str 207 | 208 | 209 | def register_handlers_timer_managing(dp: Dispatcher): 210 | dp.register_message_handler(timer_create_start, 211 | Text(equals=emoji.emojize(":pencil2:") + ' Создать таймер', ignore_case=True)) 212 | dp.register_message_handler(timer_del_start, 213 | Text(equals=emoji.emojize(":stop_sign:") + ' Удалить таймер', ignore_case=True)) 214 | dp.register_message_handler(timer_info, 215 | Text(equals=emoji.emojize(":information_source:") + ' Посмотреть ваши таймеры', 216 | ignore_case=True)) 217 | 218 | dp.register_message_handler(timer_create_middle, state=Timer.timer_create_middle) 219 | dp.register_message_handler(timer_create_end, state=Timer.timer_create_end) 220 | 221 | dp.register_message_handler(timer_del, state=Timer.timer_del) 222 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aiogram import Bot, Dispatcher, executor, types 4 | from aiogram.contrib.fsm_storage.memory import MemoryStorage 5 | from aiogram.types import BotCommand 6 | 7 | import middlewares 8 | from config import BOT_TOKEN, ADMINS 9 | import logging 10 | 11 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 12 | 13 | from handlers.timer.timer_cycle import time_cycle 14 | from handlers.register_cmd import reg_cmd 15 | 16 | from data_b.dp_control import dp_all_users_list, dp_all_telegram_id_flc_list, dp_user_create, \ 17 | dp_all_telegram_id_time_list 18 | 19 | bot = Bot(token=BOT_TOKEN, parse_mode=types.ParseMode.HTML) 20 | dp = Dispatcher(bot, storage=MemoryStorage()) 21 | logging.basicConfig(level=logging.INFO) 22 | 23 | 24 | async def set_commands(bot: Bot): 25 | commands = [ 26 | BotCommand(command="/cancel", description="Отмена действия"), 27 | BotCommand(command="/help", description="Просмотр функционала"), 28 | BotCommand(command="/math", description="Задания по математике"), 29 | BotCommand(command="/logic", description="Задания по логике"), 30 | BotCommand(command="/flashcard", description="Карточки для запомнинания"), 31 | BotCommand(command="/statistics", description="Просмотр статистики"), 32 | BotCommand(command="/timer", description="Таймер") 33 | ] 34 | await bot.set_my_commands(commands) 35 | 36 | 37 | async def main(): 38 | scheduler = AsyncIOScheduler(timezone="Europe/Moscow") 39 | """ 40 | await bot.delete_webhook(drop_pending_updates=True) - в новых версиях aiogram есть проблема, то что при запуске бота, 41 | он реагирует на сообщения, которые были отправленны ему, пока он был выключен и это не чинилось dp.skip_updates() 42 | подробнее об этой ошибке: https://github.com/aiogram/aiogram/issues/418 43 | """ 44 | # Удаление последнего сообщения 45 | await bot.delete_webhook(drop_pending_updates=True) 46 | 47 | # Запуск "антифлуда" 48 | middlewares.setup(dp) 49 | 50 | # Это запуск таймера AsyncIOScheduler 51 | scheduler.add_job(time_cycle, "interval", seconds=60, args=(dp,)) 52 | scheduler.start() 53 | 54 | await set_commands(bot) 55 | 56 | # функция регистрации "register_message_handler" 57 | reg_cmd(dp) 58 | 59 | # Запуск полинга 60 | await dp.start_polling() 61 | 62 | 63 | if __name__ == "__main__": 64 | 65 | # ------------------------------------------------------ 66 | """ 67 | Это блок добавления пользователей в таблицу(users) 68 | 69 | Сделанн он, чтобы добавить всех пользователей в таблицу(users), у которых есть карточки flashcards и timer, 70 | т.к там хранится их 'telegram_user_id' 71 | """ 72 | 73 | all_users_list = dp_all_users_list() 74 | all_telegram_id_flc = dp_all_telegram_id_flc_list() 75 | 76 | for i in all_telegram_id_flc: 77 | if i not in all_users_list: 78 | dp_user_create(i) 79 | 80 | all_telegram_id_time = dp_all_telegram_id_time_list() 81 | 82 | for i in all_telegram_id_time: 83 | if i not in all_users_list: 84 | dp_user_create(i) 85 | # ------------------------------------------------------ 86 | 87 | # Ассинхронный запуск бота 88 | asyncio.run(main()) 89 | -------------------------------------------------------------------------------- /middlewares/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Dispatcher 2 | 3 | from .throttling import ThrottlingMiddleware 4 | 5 | 6 | def setup(dp: Dispatcher): 7 | dp.middleware.setup(ThrottlingMiddleware()) 8 | -------------------------------------------------------------------------------- /middlewares/throttling.py: -------------------------------------------------------------------------------- 1 | """ 2 | Не очень понимаю как работает этот код 3 | """ 4 | 5 | 6 | import asyncio 7 | 8 | from aiogram import types, Dispatcher 9 | from aiogram.dispatcher.handler import CancelHandler, current_handler 10 | from aiogram.dispatcher.middlewares import BaseMiddleware 11 | from aiogram.utils.exceptions import Throttled 12 | 13 | 14 | class ThrottlingMiddleware(BaseMiddleware): 15 | """ 16 | Simple middleware 17 | """ 18 | 19 | def __init__(self, limit=2, key_prefix='antiflood_'): 20 | self.rate_limit = limit 21 | self.prefix = key_prefix 22 | super(ThrottlingMiddleware, self).__init__() 23 | 24 | # noinspection PyUnusedLocal 25 | async def on_process_message(self, message: types.Message, data: dict): 26 | handler = current_handler.get() 27 | dispatcher = Dispatcher.get_current() 28 | if handler: 29 | limit = getattr(handler, 'throttling_rate_limit', self.rate_limit) 30 | key = getattr(handler, 'throttling_key', f"{self.prefix}_{handler.__name__}") 31 | else: 32 | limit = self.rate_limit 33 | key = f"{self.prefix}_message" 34 | try: 35 | await dispatcher.throttle(key, rate=limit) 36 | except Throttled as t: 37 | await self.message_throttled(message, t) 38 | raise CancelHandler() 39 | 40 | async def message_throttled(self, message: types.Message, throttled: Throttled): 41 | handler = current_handler.get() 42 | dispatcher = Dispatcher.get_current() 43 | if handler: 44 | key = getattr(handler, 'throttling_key', f"{self.prefix}_{handler.__name__}") 45 | else: 46 | key = f"{self.prefix}_message" 47 | # delta = throttled.rate - throttled.delta 48 | delta = 4 49 | if throttled.exceeded_count <= 2: 50 | await message.reply('Слишком много запросов!!!') 51 | await asyncio.sleep(delta) 52 | thr = await dispatcher.check_key(key) 53 | if thr.exceeded_count == throttled.exceeded_count: 54 | await message.reply('Разблокировка') 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewKentavr/ScipIO/afb1237b005ee3dc9a6b7faaed91fca2e0534a2f/requirements.txt --------------------------------------------------------------------------------