├── .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 |
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
--------------------------------------------------------------------------------