72 Commits
i18n ... main

Author SHA1 Message Date
a5824dbd6b Merge pull request 'v0.1.3' (#74) from dev into main
Reviewed-on: #74
2024-10-10 12:54:38 +03:00
f8472b1b3f Bump version tp 0.1.3 2024-10-10 12:52:38 +03:00
13be95f0f8 Closes #70 2024-10-09 15:10:55 +00:00
524087f59f Merge pull request 'Update dependency async_pymongo to v0.1.9' (#73) from renovate/async_pymongo-0.x into dev
Reviewed-on: #73
2024-10-08 16:49:38 +03:00
cc66cc386b Update dependency async_pymongo to v0.1.9 2024-10-08 16:29:25 +03:00
39f7904bdc Merge pull request 'Update dependency async_pymongo to v0.1.8' (#72) from renovate/async_pymongo-0.x into dev
Reviewed-on: #72
2024-09-25 22:25:42 +03:00
b5173a8dba Update dependency async_pymongo to v0.1.8 2024-09-25 17:13:14 +03:00
0a06e8493f Merge pull request 'Update dependency async_pymongo to v0.1.7' (#71) from renovate/async_pymongo-0.x into dev
Reviewed-on: #71
2024-09-21 01:52:54 +03:00
c2ee35b3d9 Update dependency async_pymongo to v0.1.7 2024-09-20 17:24:31 +03:00
896262b83e Merge pull request 'Update dependency uvloop to v0.20.0' (#69) from renovate/uvloop-0.x into dev
Reviewed-on: #69
2024-08-15 23:48:30 +03:00
f6b1749408 Update dependency uvloop to v0.20.0 2024-08-15 23:25:25 +03:00
6867b64a18 Should resolve #87 2024-08-10 14:10:59 +02:00
f42117e542 revert a5a513cb82
revert Merge pull request 'Update dependency aiohttp to ~=3.10.0' (#65) from renovate/aiohttp-3.x into dev

Reviewed-on: #65
2024-08-01 00:47:16 +03:00
a5a513cb82 Merge pull request 'Update dependency aiohttp to ~=3.10.0' (#65) from renovate/aiohttp-3.x into dev
Reviewed-on: #65
2024-07-31 02:13:51 +03:00
3fef2eb028 Update dependency aiohttp to ~=3.10.0 2024-07-31 00:47:50 +03:00
11ca3223ab Merge pull request 'Update dependency libbot to v3.2.3' (#64) from renovate/libbot-3.x into dev
Reviewed-on: #64
2024-07-10 08:12:59 +03:00
6b138126c1 Update dependency libbot to v3.2.3 2024-07-10 00:43:50 +03:00
e6adb03f61 Merge pull request 'Update dependency async_pymongo to v0.1.6' (#63) from renovate/async_pymongo-0.x into dev
Reviewed-on: #63
2024-06-23 14:33:51 +03:00
d51fa1e04c Update dependency async_pymongo to v0.1.6 2024-06-23 13:30:14 +03:00
8b2456c2fd Merge pull request 'Update dependency async_pymongo to v0.1.5' (#62) from renovate/async_pymongo-0.x into dev
Reviewed-on: #62
2024-06-02 12:57:32 +03:00
6a6b4cd6cd Selected async_pymongo from PyPi 2024-06-02 12:56:46 +03:00
852f4307f8 Update dependency async_pymongo to v0.1.5 2024-06-01 15:32:20 +03:00
e73797d819 Replaced "source code" with "learn more" 2024-05-31 00:03:24 +02:00
bfd99a44a6 Merge pull request 'Changed pytz<=2024.1 to pytz>=2024.1' (#61) from dev into main
Reviewed-on: #61
2024-05-31 00:55:30 +03:00
d078ab37d8 Changed pytz<=2024.1 to pytz>=2024.1 2024-05-31 00:55:07 +03:00
ea0ab6443f Merge pull request 'v0.1.2' (#60) from dev into main
Reviewed-on: #60
2024-05-31 00:46:09 +03:00
04ee8e9c60 Improved docstrings and set some logging events to INFO level instead of DEBUG 2024-05-30 23:41:52 +02:00
2c15bbb4d2 Merge pull request 'Update dependency pytz to v2024' (#59) from renovate/pytz-2024.x into dev
Reviewed-on: #59
2024-05-31 00:31:01 +03:00
99d621d90f Update dependency pytz to v2024 2024-05-31 00:28:46 +03:00
dc389ac1b7 Merge pull request 'Possibly fixed #8' (#57) from profitroll/timezones-fix into dev
Reviewed-on: #57
2024-05-31 00:25:53 +03:00
3f20fdb46a Removed wrong localize 2024-05-31 00:25:40 +03:00
65e9e830c1 Fixed possible bugs and renamed pytz imports 2024-05-31 00:25:40 +03:00
1c76c8d911 Added automatic timezone update on location change; Update methods now return changed values 2024-05-31 00:25:40 +03:00
e307d60e8e pytz.utc instead of datetime.timezone.utc 2024-05-31 00:25:40 +03:00
0562521f0d Improved typing 2024-05-31 00:25:40 +03:00
7293cafd2e Removed unnecessary checks 2024-05-31 00:25:40 +03:00
b5bfbcd375 Attempted a fix for #8 2024-05-31 00:25:40 +03:00
de483cd450 Default time is now 16:00 2024-05-31 00:25:40 +03:00
94e229949d Added get_reminder_date() and get_reminder_time() methods; update_* methods now also change the attributes of the object 2024-05-31 00:25:40 +03:00
b7fc1715fd Added pytz as a dependency 2024-05-31 00:25:40 +03:00
f6731d5734 Fixed downgrade for strings.url_updater config key 2024-05-30 12:51:47 +02:00
e1a7b6309e Closes #49 2024-05-26 22:56:30 +02:00
2404ee9095 Closes #48 2024-05-26 22:56:11 +02:00
3bd4f794d3 Merge pull request 'Update dependency libbot to v3.2.2' (#56) from renovate/libbot-3.x into dev
Reviewed-on: #56
2024-05-26 23:55:09 +03:00
bebd6b4e4f Update dependency libbot to v3.2.2 2024-05-26 23:12:56 +03:00
fd52c8f74e Merge pull request 'Update dependency libbot to v3.2.1' (#55) from renovate/libbot-3.x into dev
Reviewed-on: #55
2024-05-26 19:41:24 +03:00
b2f09339ee Update dependency libbot to v3.2.1 2024-05-26 19:01:48 +03:00
95a9b5cb2b Merge pull request 'Update dependency libbot to v3.1.0' (#54) from renovate/libbot-3.x into dev
Reviewed-on: #54
2024-05-24 22:46:56 +03:00
b7779fffd0 Update dependency libbot to v3.1.0 2024-05-24 22:42:27 +03:00
e3e9ec0cc8 Merge pull request 'Integrated Terms of service and Privacy policy' (#53) from dev into main
Reviewed-on: #53
2024-05-14 21:57:02 +03:00
6efb20781b Integrated Terms of service and Privacy policy 2024-05-14 20:14:36 +02:00
0ef066ffa1 Update dependency mongodb-migrations to v1.3.1 (#52) 2024-05-14 01:48:02 +03:00
ff63743421 Update dependency mongodb-migrations to v1.3.1 2024-05-14 01:26:00 +03:00
b29d2467d3 Merge pull request 'Added missing files' (#47) from dev into main
Reviewed-on: #47
2024-05-11 12:37:26 +03:00
486a6ac552 Bump aiohttp to ~=3.9.5 2024-05-11 11:36:31 +02:00
957a97f0da Added README.md and LICENSE 2024-05-11 11:36:16 +02:00
7f3803b79b Merge pull request 'v0.1.0' (#46) from dev into main
Reviewed-on: #46
2024-05-11 12:16:43 +03:00
09e44d51b4 ru.json fixed 2024-05-11 11:13:51 +02:00
3650dd75b6 Removed unused language codes 2024-05-11 11:13:24 +02:00
4bb1c7d9d6 Bot profile info added (#45) 2024-05-11 11:10:18 +02:00
88e32b4ea3 Re-added tgcrypto as it won't be a part of libbot 2024-01-03 23:54:29 +01:00
3856d0eab4 Bump pykeyboard to 0.1.7 2024-01-03 23:50:47 +01:00
980c44a850 pykeyboard is now sourced from an internal package index 2024-01-03 23:43:31 +01:00
ccca2dbf9a Bump libbot to 3.0.0 and apscheduler to ~=3.10.4 2024-01-03 23:26:35 +01:00
69013f2947 Merge pull request 'Update dependency libbot to v2.1.0' (#43) from renovate/libbot-2.x into dev
Reviewed-on: #43
2023-12-28 13:35:28 +02:00
2fd7df0939 Update dependency libbot to v2.1.0 2023-12-27 16:07:24 +02:00
93cf6939fe Merge pull request 'Update dependency aiohttp to ~=3.9.1' (#40) from renovate/aiohttp-3.x into dev
Reviewed-on: #40
2023-11-26 21:37:10 +02:00
4ba6530039 Update dependency aiohttp to ~=3.9.1 2023-11-26 20:06:19 +02:00
3925f66882 Merge pull request 'Database changes, new translations' (#32) from dev into main
Reviewed-on: #32
2023-11-05 15:37:21 +02:00
3cff52c56d Fixed users import 2023-11-05 13:26:10 +00:00
116a18eba1 [WIP] Database rework 2023-11-05 13:20:01 +00:00
7a0025c0f9 Merge pull request 'Strings for #11' (#28) from i18n into dev
Reviewed-on: #28
2023-10-29 20:10:41 +02:00
31 changed files with 811 additions and 185 deletions

232
LICENSE Normal file
View File

@@ -0,0 +1,232 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
“This License” refers to version 3 of the GNU General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based on the Program.
To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
The hypothetical commands `show w' and`show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.

72
README.md Normal file
View File

@@ -0,0 +1,72 @@
<h1 align="center">GarbageBot</h1>
<p align="center">
<a href="https://git.end-play.xyz/GarbageReminder/TelegramBot/src/branch/master/LICENSE"><img alt="License: GPL" src="https://img.shields.io/badge/License-GPL-blue"></a>
<a href="https://git.end-play.xyz/GarbageReminder/TelegramBot"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
</p>
This bot reminds you about garbage collection for the location of your choice.
## Dependencies
* [Python 3.8+](https://www.python.org) (3.11 is recommended)
* [MongoDB](https://www.mongodb.com)
Use [MongoDB's installation manual](https://www.mongodb.com/docs/manual/installation).
## Installation
To make this bot run at first you need to have a Python interpreter, MongoDB and optionally git (if you want to update using `git pull`). You can also ignore git and simply download source code, should also work fine. After that you're ready to go.
> In this README I assume that you're using default python in your
> system and your system's PATH contains it. If your default python
> is `python3` or for example `/home/user/.local/bin/python3.9` - use it instead.
1. Install MongoDB:
* Install MongoDB by following [official installation manual](https://www.mongodb.com/docs/manual/installation)
2. Download the bot:
1. `git clone -b dev https://git.end-play.xyz/GarbageReminder/TelegramBot.git` (if you're using git)
2. `cd TelegramBot`
3. Create virtual environment [Recommended]:
1. Install virtualenv module: `pip install virtualenv`
2. Create venv: `python -m venv .venv`
3. Activate it using `source .venv/bin/activate` on Linux, `.venv\Scripts\activate.bat` in CMD or `.venv\Scripts\Activate.ps1` in PowerShell.
4. Install project's dependencies:
`python -m pip install -r requirements.txt`
Without installing those - bot cannot work at all.
5. Configure required keys with your favorite text editor:
1. Copy config file: `cp config_example.json config.json`
2. Open `config.json` using your favorite text editor. For example `nano config.json`, but you can also edit it with vim, mcedit, or Notepad/Notepad++ on Windows
3. Change `"bot.owner"`, `"bot.api_id"`, `"bot.api_hash"` and `"bot.bot_token"` keys' values.
If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id).
6. Configure database and API:
* Configure database:
1. Change database host and port in keys `"database.host"` and `"database.port"`. For default local installation those will be `127.0.0.1` and `27017` respectively
2. Change database name to the one you like in `"database.name"`. It will be automatically created on start
3. If you've changed user and password to access the db, you should also change `"database.user"` and `"database.password"` keys, otherwise leave them `null` (default).
7. Good to go, run it!
Make sure MongoDB is running and use `python main.py` to start the bot.
If you need any further instructions on how to configure your bot or you had any difficulties doing so - please use [wiki in this repository](https://git.end-play.xyz/GarbageReminder/TelegramBot/wiki) to get more detailed instructions.
## Localization
Bot is capable of using custom locales. There are some that are pre-installed (English, German and Ukrainian), however you can add your own locales too. Both locally and by contributing on our [Weblate](https://weblate.end-play.xyz/projects/garbagereminder/telegrambot).
All localization files are located in the `locale`. Just copy locale file of your choice, name it in accordance to [IETF language tags](https://en.wikipedia.org/wiki/IETF_language_tag) (if you want your locale to be compatible with Telegram's locales) or define your own name. Save it as json and you're good to go. If you want to change default locale for messages - edit `"locale"` parameter in the `config.json`.
We recommend to only make changes to your custom locale. Or at least always have your backup of for example `en.json` as your fallback.

View File

@@ -10,7 +10,7 @@ class CallbackLanguage:
language: str language: str
@classmethod @classmethod
def from_callback(cls, callback: CallbackQuery): def from_callback(cls, callback: CallbackQuery) -> "CallbackLanguage":
"""Parse callback query and extract language data from it. """Parse callback query and extract language data from it.
### Args: ### Args:

View File

@@ -23,7 +23,7 @@ class GarbageEntry:
date: datetime date: datetime
@classmethod @classmethod
async def from_dict(cls, data: Mapping[str, Any]): async def from_dict(cls, data: Mapping[str, Any]) -> "GarbageEntry":
"""Generate GarbageEntry object from the mapping provided """Generate GarbageEntry object from the mapping provided
### Args: ### Args:
@@ -60,7 +60,7 @@ class GarbageEntry:
) )
@classmethod @classmethod
async def from_record(cls, data: Mapping[str, Any]): async def from_record(cls, data: Mapping[str, Any]) -> "GarbageEntry":
locations = [ locations = [
await Location.get(location_id) for location_id in data["locations"] await Location.get(location_id) for location_id in data["locations"]
] ]

View File

@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Union
from bson import ObjectId from bson import ObjectId
from classes.importer.abstract import Importer from classes.importer.abstract import Importer
from modules.database import col_entries from modules.database_api import col_entries
class ImporterCSV(Importer): class ImporterCSV(Importer):

View File

@@ -5,7 +5,7 @@ from bson import ObjectId
from ujson import loads from ujson import loads
from classes.importer.abstract import Importer from classes.importer.abstract import Importer
from modules.database import col_entries from modules.database_api import col_entries
class ImporterJSON(Importer): class ImporterJSON(Importer):

View File

@@ -6,7 +6,7 @@ from pytz import timezone as pytz_timezone
from pytz.tzinfo import BaseTzInfo, DstTzInfo from pytz.tzinfo import BaseTzInfo, DstTzInfo
from classes.point import Point from classes.point import Point
from modules.database import col_locations from modules.database_api import col_locations
@dataclass @dataclass
@@ -28,7 +28,7 @@ class Location:
timezone: Union[BaseTzInfo, DstTzInfo] timezone: Union[BaseTzInfo, DstTzInfo]
@classmethod @classmethod
async def get(cls, id: int): async def get(cls, id: int) -> "Location":
db_entry = await col_locations.find_one({"id": id}) db_entry = await col_locations.find_one({"id": id})
if db_entry is None: if db_entry is None:
@@ -40,7 +40,7 @@ class Location:
return cls(**db_entry) return cls(**db_entry)
@classmethod @classmethod
async def find(cls, name: str): async def find(cls, name: str) -> "Location":
db_entry = await col_locations.find_one({"name": {"$regex": name}}) db_entry = await col_locations.find_one({"name": {"$regex": name}})
if db_entry is None: if db_entry is None:
@@ -52,7 +52,7 @@ class Location:
return cls(**db_entry) return cls(**db_entry)
@classmethod @classmethod
async def nearby(cls, lat: float, lon: float): async def nearby(cls, lat: float, lon: float) -> "Location":
db_entry = await col_locations.find_one({"location": {"$near": [lon, lat]}}) db_entry = await col_locations.find_one({"location": {"$near": [lon, lat]}})
if db_entry is None: if db_entry is None:

View File

@@ -1,5 +1,8 @@
import logging
from datetime import datetime, timedelta
from typing import List, Union from typing import List, Union
from aiohttp import ClientSession
from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.cron import CronTrigger
from libbot.pyrogram.classes import PyroClient as LibPyroClient from libbot.pyrogram.classes import PyroClient as LibPyroClient
from pymongo import ASCENDING, GEOSPHERE, TEXT from pymongo import ASCENDING, GEOSPHERE, TEXT
@@ -7,18 +10,32 @@ from pyrogram.types import User
from classes.location import Location from classes.location import Location
from classes.pyrouser import PyroUser from classes.pyrouser import PyroUser
from modules.database import col_locations from classes.updater import Updater
from modules.database_api import col_locations
from modules.reminder import remind from modules.reminder import remind
logger = logging.getLogger(__name__)
class PyroClient(LibPyroClient): class PyroClient(LibPyroClient):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.__version__ = (0, 1, 3)
super().__init__(**kwargs) super().__init__(**kwargs)
self.updater = Updater()
self.contexts = []
if self.scheduler is not None: if self.scheduler is not None:
self.scheduler.add_job( self.scheduler.add_job(
remind, CronTrigger.from_crontab("* * * * *"), args=(self,) remind, CronTrigger.from_crontab("* * * * *"), args=(self,)
) )
self.contexts = [] if self.config["update_checker"]:
self.scheduler.add_job(
self.check_updates,
CronTrigger.from_crontab("0 12 */3 * *"),
next_run_time=datetime.now() + timedelta(seconds=10),
)
async def start(self, **kwargs): async def start(self, **kwargs):
await col_locations.create_index( await col_locations.create_index(
@@ -31,6 +48,10 @@ class PyroClient(LibPyroClient):
await col_locations.create_index([("name", TEXT)], name="location_name") await col_locations.create_index([("name", TEXT)], name="location_name")
return await super().start(**kwargs) return await super().start(**kwargs)
async def stop(self, **kwargs):
await self.updater.client_session.close()
await super().stop(**kwargs)
async def find_user(self, user: Union[int, User]) -> PyroUser: async def find_user(self, user: Union[int, User]) -> PyroUser:
"""Find User by it's ID or User object. """Find User by it's ID or User object.
@@ -68,3 +89,25 @@ class PyroClient(LibPyroClient):
return [ return [
await Location.get(record["id"]) async for record in col_locations.find({}) await Location.get(record["id"]) async for record in col_locations.find({})
] ]
async def check_updates(self) -> None:
"""Check for updates and send a message to the owner if newer version was found"""
if await self.updater.check_updates(
self.__version__, self.config["strings"]["url_updater"]
):
try:
release = await self.updater.get_latest_release(
self.config["strings"]["url_updater"]
)
except Exception as exc:
logger.error("Could not fetch the latest version: %s", exc)
return
await self.send_message(
self.owner,
self._("update_available", "messages").format(
version_current=f"v{'.'.join(str(subversion) for subversion in self.__version__)}",
version_new=release["tag_name"],
release_url=release["html_url"],
),
)

View File

@@ -1,7 +1,9 @@
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Union from datetime import datetime, timedelta
from typing import Any, Mapping, Tuple, Union
import pytz
from bson import ObjectId from bson import ObjectId
from classes.location import Location from classes.location import Location
@@ -42,9 +44,9 @@ class PyroUser:
enabled: bool = True, enabled: bool = True,
location_id: int = 0, location_id: int = 0,
offset: int = 1, offset: int = 1,
time_hour: int = 18, time_hour: int = 16,
time_minute: int = 0, time_minute: int = 0,
): ) -> "PyroUser":
db_entry = await col_users.find_one({"id": id}) db_entry = await col_users.find_one({"id": id})
if db_entry is None: if db_entry is None:
@@ -72,7 +74,7 @@ class PyroUser:
return cls(**db_entry) return cls(**db_entry)
@classmethod @classmethod
async def from_dict(cls, **kwargs): async def from_dict(cls, **kwargs) -> "PyroUser":
if "location" in kwargs: if "location" in kwargs:
try: try:
kwargs["location"] = await Location.get(kwargs["location"]) # type: ignore kwargs["location"] = await Location.get(kwargs["location"]) # type: ignore
@@ -80,48 +82,193 @@ class PyroUser:
kwargs["location"] = None # type: ignore kwargs["location"] = None # type: ignore
return cls(**kwargs) return cls(**kwargs)
async def update_locale(self, locale: Union[str, None]) -> None: async def update_locale(self, locale: Union[str, None]) -> Union[str, None]:
"""Change user's locale stored in the database. """Change user's locale stored in the database.
### Args: ### Args:
* locale (`Union[str, None]`): New locale to be set. * locale (`Union[str, None]`): New locale to be set.
""" """
logger.debug("%s's locale has been set to %s", self.id, locale) logger.info("%s's locale has been set to %s", self.id, locale)
await col_users.update_one({"_id": self._id}, {"$set": {"locale": locale}}) await col_users.update_one({"_id": self._id}, {"$set": {"locale": locale}})
async def update_state(self, enabled: bool = False) -> None: self.locale = locale
logger.debug("%s's state has been set to %s", self.id, enabled)
return self.locale
async def update_state(self, enabled: bool = False) -> bool:
"""Update user's state (enabled/disabled)
### Args:
* enabled (`bool`, *optional*): Whether the user is enabled. Defaults to `False`.
### Returns:
* `bool`: User's current state
"""
logger.info("%s's state has been set to %s", self.id, enabled)
await col_users.update_one({"_id": self._id}, {"$set": {"enabled": enabled}}) await col_users.update_one({"_id": self._id}, {"$set": {"enabled": enabled}})
async def update_location(self, location_id: int = 0) -> None: self.enabled = enabled
logger.debug("%s's location has been set to %s", self.id, location_id)
return self.enabled
async def update_location(self, location_id: int) -> Location:
"""Update user's location and move their time to the new timezone (if the user had a location set previously)
### Args:
* location_id (`int`): ID of the location
### Returns:
`Location`: New location
"""
logger.info("%s's location has been set to %s", self.id, location_id)
await col_users.update_one( await col_users.update_one(
{"_id": self._id}, {"$set": {"location": location_id}} {"_id": self._id}, {"$set": {"location": location_id}}
) )
async def update_offset(self, offset: int = 1) -> None: location = await Location.get(location_id)
logger.debug("%s's offset has been set to %s", self.id, offset)
# Execute if timezones of old and new locations are different
if self.location and (self.location.timezone.zone != location.timezone.zone):
# Get UTC time for selected reminder time
now_utc = datetime.now(pytz.utc).replace(
hour=self.time_hour, minute=self.time_minute, second=0, microsecond=0
)
# Get the time for the reminder time of old and new location
local_old = now_utc.astimezone(self.location.timezone)
local_new = (
location.timezone.localize(local_old.replace(tzinfo=None))
).astimezone(pytz.utc)
# Update the time to match the new timezone
await self.update_time(hour=local_new.hour, minute=local_new.minute)
self.location = location
return self.location
async def update_offset(self, offset: int = 1) -> int:
"""Update the offset of the reminder (in days)
### Args:
* offset (`int`, *optional*): Offset in days. Defaults to `1`.
### Returns:
* `int`: Offset in days
"""
logger.info("%s's offset has been set to %s", self.id, offset)
await col_users.update_one({"_id": self._id}, {"$set": {"offset": offset}}) await col_users.update_one({"_id": self._id}, {"$set": {"offset": offset}})
async def update_time(self, hour: int = 18, minute: int = 0) -> None: self.offset = offset
logger.debug("%s's time has been set to %s h. %s m.", self.id, hour, minute)
return offset
async def update_time(self, hour: int = 16, minute: int = 0) -> Tuple[int, int]:
"""Update the time of the reminder (hour and minute, for UTC timezone)
### Args:
* hour (`int`, *optional*): Hour of the reminder. Defaults to `16`.
* minute (`int`, *optional*): Minute of the reminder. Defaults to `0`.
### Returns:
* `Tuple[int, int]`: Hour and minute of the reminder
"""
logger.info("%s's time has been set to %s h. %s m.", self.id, hour, minute)
await col_users.update_one( await col_users.update_one(
{"_id": self._id}, {"$set": {"time_hour": hour, "time_minute": minute}} {"_id": self._id}, {"$set": {"time_hour": hour, "time_minute": minute}}
) )
self.time_hour = hour
self.time_minute = minute
return self.time_hour, self.time_minute
async def delete(self) -> None: async def delete(self) -> None:
logger.debug("%s's data has been deleted", self.id) """Delete the database record of the user"""
logger.info("%s's data has been deleted", self.id)
await col_users.delete_one({"_id": self._id}) await col_users.delete_one({"_id": self._id})
async def checkout(self) -> Any: async def checkout(self) -> Mapping[str, Any]:
logger.debug("%s's data has been checked out", self.id) """Checkout the user's database record
### Raises:
* `KeyError`: Database record of the user was not found
### Returns:
* `Mapping[str, Any]`: Database record
"""
logger.info("%s's data has been checked out", self.id)
db_entry = await col_users.find_one({"_id": self._id}) db_entry = await col_users.find_one({"_id": self._id})
if db_entry is None: if db_entry is None:
raise KeyError( raise KeyError(
f"DB record with id {self._id} of user {self.id} is not found" f"DB record with id {self._id} of user {self.id} was not found"
) )
del db_entry["_id"] # type: ignore del db_entry["_id"] # type: ignore
return db_entry return db_entry
def get_reminder_date(self) -> datetime:
"""Get next reminder date (year, month and day)
### Raises:
* `AttributeError`: Some attribute(s) are missing
### Returns:
* `datetime`: Datetime object of the next reminder date
"""
if self.location is None:
logger.warning(
"Could not get the reminder date for %s: User does not have some attribute(s) set",
self.id,
)
raise AttributeError(
f"Could not get the reminder date for {self.id}: User does not have some attribute(s) set"
)
if not self.location.timezone:
logger.warning("Location %s does not have a timezone set", self.location.id)
return (
datetime.now(self.location.timezone or pytz.utc) + timedelta(days=1)
).replace(hour=0, minute=0, second=0, microsecond=0)
def get_reminder_time(self) -> datetime:
"""Get reminder time (hour and minute)
### Raises:
* `AttributeError`: Some attribute(s) are missing
### Returns:
* `datetime`: Datetime object of the next reminder date
"""
if self.time_hour is None or self.time_minute is None or self.location is None:
logger.warning(
"Could not get the reminder time for %s: User does not have some attribute(s) set",
self.id,
)
raise AttributeError(
f"Could not get the reminder time for {self.id}: User does not have some attribute(s) set"
)
if not self.location.timezone:
logger.warning("Location %s does not have a timezone set", self.location.id)
return (
datetime.now(pytz.utc)
.replace(
hour=self.time_hour,
minute=self.time_minute,
second=0,
microsecond=0,
)
.astimezone(self.location.timezone or pytz.utc)
)

50
classes/updater.py Normal file
View File

@@ -0,0 +1,50 @@
import logging
from typing import Any, Dict, Tuple, Union
from aiohttp import ClientSession
logger = logging.getLogger(__name__)
class Updater:
def __init__(self, client_session: Union[ClientSession, None] = None) -> None:
self.client_session: Union[ClientSession, None] = client_session
async def check_updates(
self, version_current: Tuple[int, int, int], api_url: str
) -> bool:
if not self.client_session:
self.client_session = ClientSession()
response = await self.client_session.get(api_url)
if response.status != 200:
return False
try:
version_latest = (await response.json())["tag_name"][1:].split(".")
except Exception as exc:
logger.error("Error parsing latest version: %s", exc)
return False
return any(
version_current[index] < int(subversion)
for index, subversion in enumerate(version_latest)
)
async def get_latest_release(self, api_url: str) -> Dict[str, Any]:
if not self.client_session:
self.client_session = ClientSession()
response = await self.client_session.get(api_url)
if response.status != 200:
raise RuntimeError(f"Could not fetch latest release: {response.status}")
try:
return await response.json()
except Exception as exc:
logger.error("Error parsing latest release: %s", exc)
raise RuntimeError(
f"Error parsing latest release: {response.status}"
) from exc

View File

@@ -14,7 +14,14 @@
"password": null, "password": null,
"host": "127.0.0.1", "host": "127.0.0.1",
"port": 27017, "port": 27017,
"name": "garbagebot" "name": "garbage_bot"
},
"database_api": {
"user": null,
"password": null,
"host": "127.0.0.1",
"port": 27017,
"name": "garbage_reminder"
}, },
"search": { "search": {
"radius": 0.1 "radius": 0.1
@@ -25,6 +32,8 @@
"disabled_plugins": [], "disabled_plugins": [],
"strings": { "strings": {
"url_repo": "https://git.end-play.xyz/GarbageReminder/TelegramBot", "url_repo": "https://git.end-play.xyz/GarbageReminder/TelegramBot",
"url_contact": "https://git.end-play.xyz/GarbageReminder/TelegramBot/issues" "url_contact": "https://git.end-play.xyz/GarbageReminder/TelegramBot/issues",
} "url_updater": "https://git.end-play.xyz/api/v1/repos/GarbageReminder/TelegramBot/releases/latest"
},
"update_checker": true
} }

View File

@@ -3,10 +3,14 @@
"flag": "🇩🇪", "flag": "🇩🇪",
"name": "Deutsch", "name": "Deutsch",
"codes": [ "codes": [
"de", "de"
"de-DE"
] ]
}, },
"bot": {
"name": "Garbage Reminder",
"about": "Nie wieder Müllabfuhrtermin verpassen. Mehr erfahren: https://garbagebot.eu",
"description": "Sie können Erinnerungen an die Müllabfuhr für Orte Ihrer Wahl erhalten.\n\nVerwenden Sie /help, um die Funktionsweise des Bots besser zu verstehen, oder verwenden Sie /setup, um Ihre Erinnerungen zu konfigurieren."
},
"formats": { "formats": {
"date": "%d.%m.%Y", "date": "%d.%m.%Y",
"time": "%H:%M" "time": "%H:%M"
@@ -60,21 +64,22 @@
"set_time_finished": "🔔 Die Benachrichtigungszeit wurde aktualisiert! Sie erhalten nun eine Benachrichtigung über die Abholung {offset} T. vor der Abholung um **{time}**. {toggle_notice}", "set_time_finished": "🔔 Die Benachrichtigungszeit wurde aktualisiert! Sie erhalten nun eine Benachrichtigung über die Abholung {offset} T. vor der Abholung um **{time}**. {toggle_notice}",
"set_time_invalid": "Bitte geben Sie eine gültige Uhrzeit im Format SS:MM an. {cancel_notice}", "set_time_invalid": "Bitte geben Sie eine gültige Uhrzeit im Format SS:MM an. {cancel_notice}",
"set_time": "Okay. Bitte senden Sie die gewünschte Zeit im Format SS:MM.", "set_time": "Okay. Bitte senden Sie die gewünschte Zeit im Format SS:MM.",
"setup_finished": "✅ Fertig! Ihr Standort ist jetzt **{name}**. Sie werden die Benachrichtigungen über die Müllabfuhr {offset} T. im Voraus um {time} erhalten.", "setup_finished": "✅ Fertig! Sie werden die Erinnerungen an die Müllabfuhr {offset} T. im Voraus um {time} für **{name}** erhalten.",
"setup_retry": " Wenn Sie versuchen möchten, den Speicherort erneut auszuwählen, verwenden Sie den Kommando /setup.", "setup_retry": " Wenn Sie versuchen möchten, den Speicherort erneut auszuwählen, verwenden Sie den Kommando /setup.",
"setup": "⚙️ Beginnen wir die Konfiguration mit der Suche nach Ihrem Standort.\n\nBitte wählen Sie aus, ob Sie unter den Standorten in Ihrer Nähe suchen möchten oder direkt zur Suche nach dem Standortnamen übergehen wollen.\n\nBeachten Sie, dass der von Ihnen gesendete Standort **NICHT** irgendwo gespeichert wird und nur für die Standortsuche in der Datenbank verwendet wird.", "setup": "⚙️ Beginnen wir die Konfiguration mit der Suche nach Ihrem Standort.\n\nBitte wählen Sie aus, ob Sie unter den Standorten in Ihrer Nähe suchen möchten oder direkt zur Suche nach dem Standortnamen übergehen wollen.\n\nBeachten Sie, dass der von Ihnen gesendete Standort **NICHT** irgendwo gespeichert wird und nur für die Standortsuche in der Datenbank verwendet wird.",
"start_code_invalid": "🚫 Sie haben den Bot über den Link gestartet, der einen Ort enthält, aber es scheint kein gültiger zu sein. Bitte verwenden Sie den Kommando /setup, um den Standort manuell zu konfigurieren.", "start_code_invalid": "🚫 Sie haben den Bot über den Link gestartet, der einen Ort enthält, aber es scheint kein gültiger zu sein. Bitte verwenden Sie den Kommando /setup, um den Standort manuell zu konfigurieren.",
"start_code": " Sie haben den Bot über den Link gestartet, der einen Ort **{name}** enthält.\n\nBitte bestätigen Sie, ob Sie diesen als Ihren Standort verwenden möchten.", "start_code": " Sie haben den Bot über den Link gestartet, der einen Ort **{name}** enthält.\n\nBitte bestätigen Sie, ob Sie diesen als Ihren Standort verwenden möchten.",
"start_configure": "📍 Lassen Sie uns Ihren Standort konfigurieren. Drücken Sie die Taste auf der Pop-up-Tastatur, um den Vorgang zu starten.", "start_configure": "📍 Lassen Sie uns Ihren Standort konfigurieren. Drücken Sie die Taste auf der Pop-up-Tastatur, um den Vorgang zu starten.",
"start_selection_no": "Gut, Sie sind jetzt auf sich allein gestellt. Bitte verwenden Sie den Kommando /setup, um Ihren Standort zu konfigurieren und Erinnerungen zu erhalten.", "start_selection_no": "Gut, Sie sind jetzt auf sich allein gestellt. Bitte verwenden Sie den Kommando /setup, um Ihren Standort zu konfigurieren und Erinnerungen zu erhalten.",
"start_selection_yes": "✅ Fertig! Ihr Standort ist jetzt **{name}**. Sie erhalten Erinnerungen an die Müllabfuhr {offset} T. im Voraus um {time}.\n\nBitte besuchen Sie /help Menü, wenn Sie wissen möchten, wie Sie die Zeit der Benachrichtigungen ändern oder sie deaktivieren können.", "start_selection_yes": "✅ Fertig! Sie werden die Erinnerungen an die Müllabfuhr {offset} T. im Voraus um {time} für **{name}** erhalten.\n\nBitte besuchen Sie /help Menü, wenn Sie wissen möchten, wie Sie die Zeit der Benachrichtigungen ändern oder sie deaktivieren können.",
"start": "👋 Herzlich willkommen!\n\nDieser kleine Open-Source-Bot soll Ihnen das Leben etwas erleichtern, indem er Sie über die nächste Müllabfuhr in Ihrer Nähe informiert.\n\nDurch die Nutzung dieses Bots akzeptieren Sie die [Datenschutzbestimmungen]({privacy_policy}), andernfalls blockieren und entfernen Sie diesen Bot bitte vor weiterer Interaktion.\n\nNun ist der offizielle Teil vorbei und Sie können sich mit dem Bot beschäftigen.", "start": "👋 Herzlich willkommen!\n\nDieser kleine Open-Source-Bot soll Ihnen das Leben etwas erleichtern, indem er Sie über die nächste Müllabfuhr in Ihrer Nähe informiert.\n\nDurch die Nutzung dieses Bots akzeptieren Sie die [Nutzungsbedingungen]({terms_of_service}) und [Datenschutzbestimmungen]({privacy_policy}), andernfalls blockieren und entfernen Sie diesen Bot bitte vor weiterer Interaktion.\n\nNun ist der offizielle Teil vorbei und Sie können sich mit dem Bot beschäftigen.",
"toggle_disabled": "🔕 Die Benachrichtigungen wurden deaktiviert.", "toggle_disabled": "🔕 Die Benachrichtigungen wurden deaktiviert.",
"toggle_enabled_location": "🔔 Benachrichtigungen wurden aktiviert {offset} T. vor der Sammlung um {time} am **{name}**.", "toggle_enabled_location": "🔔 Benachrichtigungen wurden aktiviert {offset} T. vor der Sammlung um {time} am **{name}**.",
"toggle_enabled": "🔔 Benachrichtigungen wurden aktiviert {offset} T. vor der Sammlung um {time}. Verwenden Sie /setup, um Ihren Standort auszuwählen.", "toggle_enabled": "🔔 Benachrichtigungen wurden aktiviert {offset} T. vor der Sammlung um {time}. Verwenden Sie /setup, um Ihren Standort auszuwählen.",
"toggle": "Führen Sie /toggle aus, um Benachrichtigungen zu aktivieren.", "toggle": "Führen Sie /toggle aus, um Benachrichtigungen zu aktivieren.",
"upcoming_empty": "Keine Müllabfuhr-Einträge für die nächsten 30 Tage bei **{name}** gefunden", "upcoming_empty": "Keine Müllabfuhr-Einträge für die nächsten 30 Tage bei **{name}** gefunden",
"upcoming": "Bevorstehende Müllabfuhr:\n\n{entries}" "upcoming": "Bevorstehende Müllabfuhr:\n\n{entries}",
"update_available": "Es gibt eine neue Version von GarbageBot!\n\nVersion: `{version_current}` -> `{version_new}`\n\n[Release-Seite]({release_url}) | [Update-Anleitung](https://garbagebot.eu/bot_telegram/upgrading)"
}, },
"force_replies": { "force_replies": {
"import": "JSON mit Abfalltermine", "import": "JSON mit Abfalltermine",
@@ -95,4 +100,4 @@
"callbacks": { "callbacks": {
"locale_set": "Ihre Sprache ist jetzt: {locale}" "locale_set": "Ihre Sprache ist jetzt: {locale}"
} }
} }

View File

@@ -3,10 +3,14 @@
"flag": "🇬🇧", "flag": "🇬🇧",
"name": "English", "name": "English",
"codes": [ "codes": [
"en", "en"
"en-GB"
] ]
}, },
"bot": {
"name": "Garbage Reminder",
"about": "Never forget about garbage collection again. Learn more: https://garbagebot.eu",
"description": "You can receive reminders about garbage collection for locations of your choice.\n\nUse /help to better understand how the bot works or use /setup to configure your reminders."
},
"formats": { "formats": {
"date": "%d/%m/%Y", "date": "%d/%m/%Y",
"time": "%H:%M" "time": "%H:%M"
@@ -60,21 +64,22 @@
"set_time_finished": "🔔 Notifications time has been updated! You will now receive notification about collection {offset} d. before the collection at **{time}**. {toggle_notice}", "set_time_finished": "🔔 Notifications time has been updated! You will now receive notification about collection {offset} d. before the collection at **{time}**. {toggle_notice}",
"set_time_invalid": "Please, provide a valid time in HH:MM format. {cancel_notice}", "set_time_invalid": "Please, provide a valid time in HH:MM format. {cancel_notice}",
"set_time": "Alright. Please, send your desired time in HH:MM format.", "set_time": "Alright. Please, send your desired time in HH:MM format.",
"setup_finished": "✅ Finished! Your location is now **{name}**. You will receive the notifications about garbage collection {offset} d. in advance at {time}.", "setup_finished": "✅ Finished! You will receive reminders about garbage collection {offset} d. in advance at {time} for **{name}**.",
"setup_retry": " If you want try selecting the location again, use the /setup command.", "setup_retry": " If you want try selecting the location again, use the /setup command.",
"setup": "⚙️ Let's begin configuration with the search for your location.\n\nPlease, select whether you want to search among the locations near you or go straight to the search by location name.\n\nNote that the location you send will **NOT** be saved anywhere and is only used for location lookup in the database.", "setup": "⚙️ Let's begin configuration with the search for your location.\n\nPlease, select whether you want to search among the locations near you or go straight to the search by location name.\n\nNote that the location you send will **NOT** be saved anywhere and is only used for location lookup in the database.",
"start_code_invalid": "🚫 You have started the bot by the link containing a location, but it does not seem to be a valid one. Please, use the command /setup to manually configure the location.", "start_code_invalid": "🚫 You have started the bot by the link containing a location, but it does not seem to be a valid one. Please, use the command /setup to manually configure the location.",
"start_code": " You have started the bot by the link containing a location **{name}**.\n\nPlease, confirm whether you want to use it as your location.", "start_code": " You have started the bot by the link containing a location **{name}**.\n\nPlease, confirm whether you want to use it as your location.",
"start_configure": "📍 Let's configure your location. Press the button on pop-up keyboard to start the process.", "start_configure": "📍 Let's configure your location. Press the button on pop-up keyboard to start the process.",
"start_selection_no": "Alright, you're on your own now. Please, use the command /setup to configure your location and start receiving reminders.", "start_selection_no": "Alright, you're on your own now. Please, use the command /setup to configure your location and start receiving reminders.",
"start_selection_yes": "✅ Finished! Your location is now **{name}**. You will receive reminders about garbage collection {offset} d. in advance at {time}.\n\nPlease, visit /help if you want to know how to change notifications time or disable them.", "start_selection_yes": "✅ Finished! You will receive reminders about garbage collection {offset} d. in advance at {time} for **{name}**.\n\nPlease, visit /help if you want to know how to change notifications time or disable them.",
"start": "👋 Welcome!\n\nThis small open-source bot is made to simplify your life a bit easier by sending you notifications about upcoming garbage collection in your location.\n\nBy using this bot you accept [Privacy Policy]({privacy_policy}), otherwise please block and remove this bot before further interaction.\n\nNow the official part is over so you can dive into the bot.", "start": "👋 Welcome!\n\nThis small open-source bot is made to simplify your life a bit easier by sending you notifications about upcoming garbage collection in your location.\n\nBy using this bot you accept [Terms of service]({terms_of_service}) and [Privacy policy]({privacy_policy}), otherwise please block and remove this bot before further interaction.\n\nNow the official part is over so you can dive into the bot.",
"toggle_disabled": "🔕 Notifications have been disabled.", "toggle_disabled": "🔕 Notifications have been disabled.",
"toggle_enabled_location": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time} at the **{name}**.", "toggle_enabled_location": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time} at the **{name}**.",
"toggle_enabled": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time}. Use /setup to select your location.", "toggle_enabled": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time}. Use /setup to select your location.",
"toggle": "Execute /toggle to enable notifications.", "toggle": "Execute /toggle to enable notifications.",
"upcoming_empty": "No garbage collection entries found for the next 30 days at **{name}**", "upcoming_empty": "No garbage collection entries found for the next 30 days at **{name}**",
"upcoming": "Upcoming garbage collection:\n\n{entries}" "upcoming": "Upcoming garbage collection:\n\n{entries}",
"update_available": "There is a new version of GarbageBot available!\n\nVersion: `{version_current}` -> `{version_new}`\n\n[Release page]({release_url}) | [Update instructions](https://garbagebot.eu/bot_telegram/upgrading)"
}, },
"buttons": { "buttons": {
"delete_confirm": "I agree and want to proceed", "delete_confirm": "I agree and want to proceed",

View File

@@ -1,55 +1,15 @@
{ {
"metadata": { "metadata": {
"codes": [
"ru",
"ru-RU"
],
"flag": "🇺🇦", "flag": "🇺🇦",
"name": "Російська" "name": "Російська",
"codes": [
"ru"
]
}, },
"messages": { "bot": {
"cancelled": "Операцію скасовано.", "name": "Garbage Reminder 🇺🇦",
"setup": "⚙️ Почнемо налаштування з пошуку Вашого місцезнаходження.\n\nБудь ласка, виберіть, чи хочете Ви шукати серед найближчих до Вас локацій, чи одразу перейти до пошуку за назвою.\n\nЗверніть увагу, що надіслане Вами місцезнаходження **НЕ** зберігається ніде і використовується лише для пошуку місць поряд в базі даних.", "about": "Більше ніколи не забувайте про вивезення сміття. Дізнатись більше: https://garbagebot.eu",
"cancel": "Якщо Ви хочете скасувати цю операцію, використовуйте /cancel.", "description": "Ви можете отримувати нагадування про вивезення сміття для обраних вами місць.\n\nВикористовуйте /help, щоб краще зрозуміти, як працює бот, або /setup, щоб налаштувати нагадування."
"checkout_deleted": "🗑️ Ваші дані було видалено. Якщо Ви хочете знову почати користуватися цим ботом, скористайтеся командою /setup. В іншому випадку видаліть/заблокуйте бота і більше не взаємодійте з ним.",
"checkout_deletion": "Гаразд. Будь ласка, підтвердіть, що Ви хочете видалити свої дані з бота.\n\nНаступні дані будуть видалені:\n• Вибране місце\n• Бажана мова всіх повідомлень\n• Час сповіщень\n• Зсув сповіщень\n\nВикористовуйте клавіатуру, щоб підтвердити й продовжити або /cancel, щоб перервати цю операцію.",
"checkout": "Це фактично всі дані, які має бот. Будь ласка, використовуйте ці кнопки, щоб вибрати, чи хочете Ви видалити свої дані з бота.",
"commands_removed": "✅ Всі зареєстровані на цю мить команди було видалено. Команди будуть зареєстровані знову при запуску бота.",
"help": "🔔 Цей бот надсилає сповіщення про вивезення сміття згідно з Вашим місцевим графіком.\n\n**Доступні команди**\n/help - Показати це повідомлення\n/setup - Вибрати місце розташування\n/toggle - Увімкнути/вимкнути нагадування\n/set_time - Встановити час нагадувань\n/set_offset - Встановити зсув між нагадуваннями та вивозом\n/upcoming - Показати майбутні вивезення\n/language - Обрати мову бота\n/checkout - Експортувати або видалити дані\n\nВи також можете запропонувати додати своє місто/район до бота, зв'язавшись з адміністраторами за [цим посиланням]({url_contact}) та вказавши свій розклад.\n\n⚙ Бажаєте розмістити цього бота самостійно або внести деякі зміни? Бот має відкритий вихідний код, тож ви можете форкнути його. Ознайомтесь із [репозиторієм бота]({url_repo}) щоб дізнатись деталі.\n\nПриємного користування! 🤗",
"import_finished": "Ви успішно вставили {count} записів.",
"import_invalid_date": "Записи містять невірні формати дат. Використовуйте формат дати **ISO 8601**.",
"import_invalid_filetype": "Неправильний ввід. Будь ласка, надішліть мені JSON або CSV файл із записами. {cancel_notice}",
"import_invalid": "Це недійсний файл з даними про збір сміття.",
"import": "Гаразд. Надішліть мені правильний файл. Він може бути у форматі JSON або CSV. Дізнайтеся більше про підтримувані формати в документації",
"locale_choice": "Гаразд. Будь ласка, оберіть мову за допомогою клавіатури нижче.",
"location_empty": "У Вас не встановлено локацію вивозу. Оберіть свою локацію за допомогою /setup.",
"location_name_empty": "Не вдалося знайти жодного населеного пункту з такою назвою. Спробуйте перефразувати назву або переконайтеся, що Ви використовуєте ту саму мову та назву, що й місцева влада у графіку вивезення сміття.\n\n{cancel_notice}",
"location_name_invalid": "Будь ласка, надішліть назву місця у вигляді тексту. {cancel_notice}",
"location_name": "Будь ласка, надішліть мені назву населеного пункту. Це має бути назва, яка використовується у графіку вивезення сміття Вашою місцевою владою. Зазвичай це назва району або міста.",
"location_select": "Виберіть місце за допомогою показаної клавіатури.",
"reminder": "**Вивіз сміття**\n\nТип: {type}\nДата: {date}\n\nНе забудьте підготувати свій контейнер до збору!",
"search_nearby_empty": "Не вдалося знайти жодної локації поблизу. Спробуємо скористатися пошуком за назвою.",
"selection_invalid": "Будь ласка, виберіть правильний варіант за допомогою клавіатури. {cancel_notice}",
"set_offset_finished": "🔔 Час сповіщень було оновлено! Тепер Ви будете отримувати сповіщення про вивіз сміття за **{offset} д.** до вивозу о {time}. {toggle_notice}",
"set_offset_invalid": "Будь ласка, вкажіть дійсну цілу кількість днів у діапазоні від 0 до 7 (включно). {cancel_notice}",
"set_offset": "Гаразд. Будь ласка, напишіть, за скільки днів до збору Ви хочете отримати сповіщення про збір.",
"set_time_finished": "🔔 Час сповіщень було оновлено! Тепер Ви будете отримувати сповіщення про вивіз сміття за {offset} д. до вивозу о **{time}**. {toggle_notice}",
"set_time_invalid": "Будь ласка, вкажіть дійсний час у форматі ГГ:ХХ. {cancel_notice}",
"set_time": "Гаразд. Будь ласка, надішліть бажаний час у форматі ГГ:ХХ.",
"setup_finished": "✅ Готово! Ваше місцезнаходження тепер **{name}**. Ви будете отримувати сповіщення про вивезення сміття за {offset} д. заздалегідь о {time}.",
"setup_retry": " Якщо Ви захочете вибрати місце розташування, скористайтеся командою /setup.",
"start_code_invalid": "🚫 Ви запустили бота за посиланням, що містить локацію, але, схоже, вона не є дійсною. Будь ласка, скористайтеся командою /setup, щоб налаштувати локацію вручну.",
"start_code": " Ви запустили бота за посиланням, що містить локацію **{name}**.\n\nБудь ласка, підтвердіть, чи хочете Ви використовувати її як свою локацію для сповіщень.",
"start_configure": "📍 Налаштуймо Вашу локацію. Натисніть кнопку на показаній клавіатурі, щоб почати процес.",
"start_selection_no": "Гаразд, тепер Ви самі по собі. Будь ласка, скористайтеся командою /setup, щоб налаштувати своє місцезнаходження і почати отримувати нагадування.",
"start_selection_yes": "✅ Готово! Ваша локація тепер **{name}**. Ви будете отримувати нагадування про вивезення сміття за {offset} д. заздалегідь о {time}.\n\nБудь ласка, скористайтесь /help, якщо Ви хочете дізнатися, як змінити час сповіщень або вимкнути їх.",
"start": "👋 Вітання!\n\nЦей невеличкий бот з відкритим вихідним кодом створений для того, щоб трохи спростити Вам життя, надсилаючи сповіщення про вивіз сміття у вашому регіоні.\n\nКористуючись цим ботом, Ви приймаєте [Політику конфіденційності]({privacy_policy}), в іншому випадку, будь ласка, заблокуйте та видаліть цього бота перед подальшою взаємодією.\n\nТепер офіційна частина закінчена, тож Ви можете зануритися в бота.",
"toggle_disabled": "🔕 Сповіщення було вимкнено.",
"toggle_enabled_location": "🔔 Сповіщення увімкнено за {offset} д. до вивезення сміття о {time} для локації **{name}**.",
"toggle_enabled": "🔔 Сповіщення було увімкнено за {offset} д. до вивезення сміття о {time}. Оберіть своє розташування за допомогою /setup.",
"toggle": "Використовуйте /toggle, щоб увімкнути сповіщення.",
"upcoming_empty": "Не знайдено записів про вивезення сміття на найближчі 30 днів для **{name}**",
"upcoming": "Найближчі вивози сміття:\n\n{entries}"
}, },
"formats": { "formats": {
"date": "%d.%m.%Y", "date": "%d.%m.%Y",
@@ -76,6 +36,51 @@
"shutdown": "Вимкнути бота", "shutdown": "Вимкнути бота",
"remove_commands": "Видалити всі команди" "remove_commands": "Видалити всі команди"
}, },
"messages": {
"cancel": "Якщо Ви хочете скасувати цю операцію, використовуйте /cancel.",
"cancelled": "Операцію скасовано.",
"checkout_deleted": "🗑️ Ваші дані було видалено. Якщо Ви хочете знову почати користуватися цим ботом, скористайтеся командою /setup. В іншому випадку видаліть/заблокуйте бота і більше не взаємодійте з ним.",
"checkout_deletion": "Гаразд. Будь ласка, підтвердіть, що Ви хочете видалити свої дані з бота.\n\nНаступні дані будуть видалені:\n• Вибране місце\n• Бажана мова всіх повідомлень\n• Час сповіщень\n• Зсув сповіщень\n\nВикористовуйте клавіатуру, щоб підтвердити й продовжити або /cancel, щоб перервати цю операцію.",
"checkout": "Це фактично всі дані, які має бот. Будь ласка, використовуйте ці кнопки, щоб вибрати, чи хочете Ви видалити свої дані з бота.",
"commands_removed": "✅ Всі зареєстровані на цю мить команди було видалено. Команди будуть зареєстровані знову при запуску бота.",
"help": "🔔 Цей бот надсилає сповіщення про вивезення сміття згідно з Вашим місцевим графіком.\n\n**Доступні команди**\n/help - Показати це повідомлення\n/setup - Вибрати місце розташування\n/toggle - Увімкнути/вимкнути нагадування\n/set_time - Встановити час нагадувань\n/set_offset - Встановити зсув між нагадуваннями та вивозом\n/upcoming - Показати майбутні вивезення\n/language - Обрати мову бота\n/checkout - Експортувати або видалити дані\n\nВи також можете запропонувати додати своє місто/район до бота, зв'язавшись з адміністраторами за [цим посиланням]({url_contact}) та вказавши свій розклад.\n\n⚙ Бажаєте розмістити цього бота самостійно або внести деякі зміни? Бот має відкритий вихідний код, тож ви можете форкнути його. Ознайомтесь із [репозиторієм бота]({url_repo}) щоб дізнатись деталі.\n\nПриємного користування! 🤗",
"import_finished": "Ви успішно вставили {count} записів.",
"import_invalid_date": "Записи містять невірні формати дат. Використовуйте формат дати **ISO 8601**.",
"import_invalid_filetype": "Неправильний ввід. Будь ласка, надішліть мені JSON або CSV файл із записами. {cancel_notice}",
"import_invalid": "Це недійсний файл з даними про збір сміття.",
"import": "Гаразд. Надішліть мені правильний файл. Він може бути у форматі JSON або CSV. Дізнайтеся більше про підтримувані формати в документації",
"locale_choice": "Гаразд. Будь ласка, оберіть мову за допомогою клавіатури нижче.",
"location_empty": "У Вас не встановлено локацію вивозу. Оберіть свою локацію за допомогою /setup.",
"location_name_empty": "Не вдалося знайти жодного населеного пункту з такою назвою. Спробуйте перефразувати назву або переконайтеся, що Ви використовуєте ту саму мову та назву, що й місцева влада у графіку вивезення сміття.\n\n{cancel_notice}",
"location_name_invalid": "Будь ласка, надішліть назву місця у вигляді тексту. {cancel_notice}",
"location_name": "Будь ласка, надішліть мені назву населеного пункту. Це має бути назва, яка використовується у графіку вивезення сміття Вашою місцевою владою. Зазвичай це назва району або міста.",
"location_select": "Виберіть місце за допомогою показаної клавіатури.",
"reminder": "**Вивіз сміття**\n\nТип: {type}\nДата: {date}\n\nНе забудьте підготувати свій контейнер до збору!",
"search_nearby_empty": "Не вдалося знайти жодної локації поблизу. Спробуємо скористатися пошуком за назвою.",
"selection_invalid": "Будь ласка, виберіть правильний варіант за допомогою клавіатури. {cancel_notice}",
"set_offset_finished": "🔔 Час сповіщень було оновлено! Тепер Ви будете отримувати сповіщення про вивіз сміття за **{offset} д.** до вивозу о {time}. {toggle_notice}",
"set_offset_invalid": "Будь ласка, вкажіть дійсну цілу кількість днів у діапазоні від 0 до 7 (включно). {cancel_notice}",
"set_offset": "Гаразд. Будь ласка, напишіть, за скільки днів до збору Ви хочете отримати сповіщення про збір.",
"set_time_finished": "🔔 Час сповіщень було оновлено! Тепер Ви будете отримувати сповіщення про вивіз сміття за {offset} д. до вивозу о **{time}**. {toggle_notice}",
"set_time_invalid": "Будь ласка, вкажіть дійсний час у форматі ГГ:ХХ. {cancel_notice}",
"set_time": "Гаразд. Будь ласка, надішліть бажаний час у форматі ГГ:ХХ.",
"setup_finished": "✅ Готово! Ви будете отримувати нагадування про вивезення сміття для **{name}** за {offset} д. заздалегідь о {time}.",
"setup_retry": " Якщо Ви захочете вибрати місце розташування, скористайтеся командою /setup.",
"setup": "⚙️ Почнемо налаштування з пошуку Вашого місцезнаходження.\n\nБудь ласка, виберіть, чи хочете Ви шукати серед найближчих до Вас локацій, чи одразу перейти до пошуку за назвою.\n\nЗверніть увагу, що надіслане Вами місцезнаходження **НЕ** зберігається ніде і використовується лише для пошуку місць поряд в базі даних.",
"start_code_invalid": "🚫 Ви запустили бота за посиланням, що містить локацію, але, схоже, вона не є дійсною. Будь ласка, скористайтеся командою /setup, щоб налаштувати локацію вручну.",
"start_code": " Ви запустили бота за посиланням, що містить локацію **{name}**.\n\nБудь ласка, підтвердіть, чи хочете Ви використовувати її як свою локацію для сповіщень.",
"start_configure": "📍 Налаштуймо Вашу локацію. Натисніть кнопку на показаній клавіатурі, щоб почати процес.",
"start_selection_no": "Гаразд, тепер Ви самі по собі. Будь ласка, скористайтеся командою /setup, щоб налаштувати своє місцезнаходження і почати отримувати нагадування.",
"start_selection_yes": "✅ Готово! Ви будете отримувати нагадування про вивезення сміття для **{name}** за {offset} д. заздалегідь о {time}.\n\nБудь ласка, скористайтесь /help, якщо Ви хочете дізнатися, як змінити час сповіщень або вимкнути їх.",
"start": "👋 Вітання!\n\nЦей невеличкий бот з відкритим вихідним кодом створений для того, щоб трохи спростити Вам життя, надсилаючи сповіщення про вивіз сміття у вашому регіоні.\n\nКористуючись цим ботом, Ви приймаєте [Умови надання послуг]({terms_of_service}) та [Політику конфіденційності]({privacy_policy}), в іншому випадку, будь ласка, заблокуйте та видаліть цього бота перед подальшою взаємодією.\n\nТепер офіційна частина закінчена, тож Ви можете зануритися в бота.",
"toggle_disabled": "🔕 Сповіщення було вимкнено.",
"toggle_enabled_location": "🔔 Сповіщення увімкнено за {offset} д. до вивезення сміття о {time} для локації **{name}**.",
"toggle_enabled": "🔔 Сповіщення було увімкнено за {offset} д. до вивезення сміття о {time}. Оберіть своє розташування за допомогою /setup.",
"toggle": "Використовуйте /toggle, щоб увімкнути сповіщення.",
"upcoming_empty": "Не знайдено записів про вивезення сміття на найближчі 30 днів для **{name}**",
"upcoming": "Найближчі вивози сміття:\n\n{entries}",
"update_available": "Доступна нова версія GarbageBot!\n\nВерсія: `{version_current}` -> `{version_new}`\n\n[Сторінка релізу]({release_url}) | [Інструкція з оновлення](https://garbagebot.eu/bot_telegram/upgrading)"
},
"buttons": { "buttons": {
"delete_confirm": "Я погоджуюсь і хочу продовжити", "delete_confirm": "Я погоджуюсь і хочу продовжити",
"delete_no": "❌ Ні, я не хочу видаляти їх", "delete_no": "❌ Ні, я не хочу видаляти їх",
@@ -95,4 +100,4 @@
"set_offset": "Кількість днів", "set_offset": "Кількість днів",
"set_time": "Час у вигляді ГГ:ХХ" "set_time": "Час у вигляді ГГ:ХХ"
} }
} }

View File

@@ -3,10 +3,14 @@
"flag": "🇺🇦", "flag": "🇺🇦",
"name": "Українська", "name": "Українська",
"codes": [ "codes": [
"uk", "uk"
"uk-UA"
] ]
}, },
"bot": {
"name": "Garbage Reminder 🇺🇦",
"about": "Більше ніколи не забувайте про вивезення сміття. Дізнатись більше: https://garbagebot.eu",
"description": "Ви можете отримувати нагадування про вивезення сміття для обраних вами місць.\n\nВикористовуйте /help, щоб краще зрозуміти, як працює бот, або /setup, щоб налаштувати нагадування."
},
"formats": { "formats": {
"date": "%d.%m.%Y", "date": "%d.%m.%Y",
"time": "%H:%M" "time": "%H:%M"
@@ -60,21 +64,22 @@
"set_time_finished": "🔔 Час сповіщень було оновлено! Тепер Ви будете отримувати сповіщення про вивіз сміття за {offset} д. до вивозу о **{time}**. {toggle_notice}", "set_time_finished": "🔔 Час сповіщень було оновлено! Тепер Ви будете отримувати сповіщення про вивіз сміття за {offset} д. до вивозу о **{time}**. {toggle_notice}",
"set_time_invalid": "Будь ласка, вкажіть дійсний час у форматі ГГ:ХХ. {cancel_notice}", "set_time_invalid": "Будь ласка, вкажіть дійсний час у форматі ГГ:ХХ. {cancel_notice}",
"set_time": "Гаразд. Будь ласка, надішліть бажаний час у форматі ГГ:ХХ.", "set_time": "Гаразд. Будь ласка, надішліть бажаний час у форматі ГГ:ХХ.",
"setup_finished": "✅ Готово! Ваше місцезнаходження тепер **{name}**. Ви будете отримувати сповіщення про вивезення сміття за {offset} д. заздалегідь о {time}.", "setup_finished": "✅ Готово! Ви будете отримувати нагадування про вивезення сміття для **{name}** за {offset} д. заздалегідь о {time}.",
"setup_retry": " Якщо Ви захочете вибрати місце розташування, скористайтеся командою /setup.", "setup_retry": " Якщо Ви захочете вибрати місце розташування, скористайтеся командою /setup.",
"setup": "⚙️ Почнемо налаштування з пошуку Вашого місцезнаходження.\n\nБудь ласка, виберіть, чи хочете Ви шукати серед найближчих до Вас локацій, чи одразу перейти до пошуку за назвою.\n\nЗверніть увагу, що надіслане Вами місцезнаходження **НЕ** зберігається ніде і використовується лише для пошуку місць поряд в базі даних.", "setup": "⚙️ Почнемо налаштування з пошуку Вашого місцезнаходження.\n\nБудь ласка, виберіть, чи хочете Ви шукати серед найближчих до Вас локацій, чи одразу перейти до пошуку за назвою.\n\nЗверніть увагу, що надіслане Вами місцезнаходження **НЕ** зберігається ніде і використовується лише для пошуку місць поряд в базі даних.",
"start_code_invalid": "🚫 Ви запустили бота за посиланням, що містить локацію, але, схоже, вона не є дійсною. Будь ласка, скористайтеся командою /setup, щоб налаштувати локацію вручну.", "start_code_invalid": "🚫 Ви запустили бота за посиланням, що містить локацію, але, схоже, вона не є дійсною. Будь ласка, скористайтеся командою /setup, щоб налаштувати локацію вручну.",
"start_code": " Ви запустили бота за посиланням, що містить локацію **{name}**.\n\nБудь ласка, підтвердіть, чи хочете Ви використовувати її як свою локацію для сповіщень.", "start_code": " Ви запустили бота за посиланням, що містить локацію **{name}**.\n\nБудь ласка, підтвердіть, чи хочете Ви використовувати її як свою локацію для сповіщень.",
"start_configure": "📍 Налаштуймо Вашу локацію. Натисніть кнопку на показаній клавіатурі, щоб почати процес.", "start_configure": "📍 Налаштуймо Вашу локацію. Натисніть кнопку на показаній клавіатурі, щоб почати процес.",
"start_selection_no": "Гаразд, тепер Ви самі по собі. Будь ласка, скористайтеся командою /setup, щоб налаштувати своє місцезнаходження і почати отримувати нагадування.", "start_selection_no": "Гаразд, тепер Ви самі по собі. Будь ласка, скористайтеся командою /setup, щоб налаштувати своє місцезнаходження і почати отримувати нагадування.",
"start_selection_yes": "✅ Готово! Ваша локація тепер **{name}**. Ви будете отримувати нагадування про вивезення сміття за {offset} д. заздалегідь о {time}.\n\nБудь ласка, скористайтесь /help, якщо Ви хочете дізнатися, як змінити час сповіщень або вимкнути їх.", "start_selection_yes": "✅ Готово! Ви будете отримувати нагадування про вивезення сміття для **{name}** за {offset} д. заздалегідь о {time}.\n\nБудь ласка, скористайтесь /help, якщо Ви хочете дізнатися, як змінити час сповіщень або вимкнути їх.",
"start": "👋 Вітання!\n\nЦей невеличкий бот з відкритим вихідним кодом створений для того, щоб трохи спростити Вам життя, надсилаючи сповіщення про вивіз сміття у вашому регіоні.\n\nКористуючись цим ботом, Ви приймаєте [Політику конфіденційності]({privacy_policy}), в іншому випадку, будь ласка, заблокуйте та видаліть цього бота перед подальшою взаємодією.\n\nТепер офіційна частина закінчена, тож Ви можете зануритися в бота.", "start": "👋 Вітання!\n\nЦей невеличкий бот з відкритим вихідним кодом створений для того, щоб трохи спростити Вам життя, надсилаючи сповіщення про вивіз сміття у вашому регіоні.\n\nКористуючись цим ботом, Ви приймаєте [Умови надання послуг]({terms_of_service}) та [Політику конфіденційності]({privacy_policy}), в іншому випадку, будь ласка, заблокуйте та видаліть цього бота перед подальшою взаємодією.\n\nТепер офіційна частина закінчена, тож Ви можете зануритися в бота.",
"toggle_disabled": "🔕 Сповіщення було вимкнено.", "toggle_disabled": "🔕 Сповіщення було вимкнено.",
"toggle_enabled_location": "🔔 Сповіщення увімкнено за {offset} д. до вивезення сміття о {time} для локації **{name}**.", "toggle_enabled_location": "🔔 Сповіщення увімкнено за {offset} д. до вивезення сміття о {time} для локації **{name}**.",
"toggle_enabled": "🔔 Сповіщення було увімкнено за {offset} д. до вивезення сміття о {time}. Оберіть своє розташування за допомогою /setup.", "toggle_enabled": "🔔 Сповіщення було увімкнено за {offset} д. до вивезення сміття о {time}. Оберіть своє розташування за допомогою /setup.",
"toggle": "Використовуйте /toggle, щоб увімкнути сповіщення.", "toggle": "Використовуйте /toggle, щоб увімкнути сповіщення.",
"upcoming_empty": "Не знайдено записів про вивезення сміття на найближчі 30 днів для **{name}**", "upcoming_empty": "Не знайдено записів про вивезення сміття на найближчі 30 днів для **{name}**",
"upcoming": "Найближчі вивози сміття:\n\n{entries}" "upcoming": "Найближчі вивози сміття:\n\n{entries}",
"update_available": "Доступна нова версія GarbageBot!\n\nВерсія: `{version_current}` -> `{version_new}`\n\n[Сторінка релізу]({release_url}) | [Інструкція з оновлення](https://garbagebot.eu/bot_telegram/upgrading)"
}, },
"buttons": { "buttons": {
"delete_confirm": "Я погоджуюсь і хочу продовжити", "delete_confirm": "Я погоджуюсь і хочу продовжити",
@@ -95,4 +100,4 @@
"set_offset": "Кількість днів", "set_offset": "Кількість днів",
"set_time": "Час у вигляді ГГ:ХХ" "set_time": "Час у вигляді ГГ:ХХ"
} }
} }

14
main.py
View File

@@ -4,6 +4,7 @@ from argparse import ArgumentParser
from os import getpid from os import getpid
from pathlib import Path from pathlib import Path
from aiohttp import ClientSession
from convopyro import Conversation from convopyro import Conversation
from libbot import sync from libbot import sync
@@ -35,13 +36,16 @@ with contextlib.suppress(ImportError):
def main(): def main():
client = PyroClient(
scheduler=scheduler, commands_source=sync.json_read(Path("commands.json"))
)
Conversation(client)
if args.migrate: if args.migrate:
migrate_database() migrate_database()
logger.info("Migration finished. Exiting...")
exit()
client = PyroClient(
scheduler=scheduler,
commands_source=sync.json_read(Path("commands.json")),
)
Conversation(client)
try: try:
client.run() client.run()

View File

@@ -0,0 +1,16 @@
from libbot import sync
from mongodb_migrations.base import BaseMigration
class Migration(BaseMigration):
def upgrade(self):
sync.config_set("update_checker", True)
sync.config_set(
"url_updater",
"https://git.end-play.xyz/api/v1/repos/GarbageReminder/TelegramBot/releases/latest",
"strings",
)
def downgrade(self):
sync.config_delete("update_checker", missing_ok=True)
sync.config_delete("url_updater", "strings", missing_ok=True)

View File

@@ -1,4 +1,4 @@
"""Module that provides all database collections""" """Module that provides bot's database collections."""
from typing import Any, Mapping from typing import Any, Mapping
@@ -24,5 +24,3 @@ db_client = AsyncClient(con_string)
db: AsyncDatabase = db_client.get_database(name=db_config["name"]) db: AsyncDatabase = db_client.get_database(name=db_config["name"])
col_users: AsyncCollection = db.get_collection("users") col_users: AsyncCollection = db.get_collection("users")
col_entries: AsyncCollection = db.get_collection("entries")
col_locations: AsyncCollection = db.get_collection("locations")

29
modules/database_api.py Normal file
View File

@@ -0,0 +1,29 @@
"""Module that provides API database collections.
It's possible to use REST API client instead, but
using MongoDB directly is MUCH faster this way."""
from typing import Any, Mapping
from async_pymongo import AsyncClient, AsyncCollection, AsyncDatabase
from libbot.sync import config_get
db_config: Mapping[str, Any] = config_get("database_api")
if db_config["user"] is not None and db_config["password"] is not None:
con_string = "mongodb://{0}:{1}@{2}:{3}/{4}".format(
db_config["user"],
db_config["password"],
db_config["host"],
db_config["port"],
db_config["name"],
)
else:
con_string = "mongodb://{0}:{1}/{2}".format(
db_config["host"], db_config["port"], db_config["name"]
)
db_client = AsyncClient(con_string)
db: AsyncDatabase = db_client.get_database(name=db_config["name"])
col_entries: AsyncCollection = db.get_collection("entries")
col_locations: AsyncCollection = db.get_collection("locations")

View File

@@ -1,20 +1,21 @@
import logging import logging
from datetime import datetime, timedelta from datetime import datetime
import pytz
from bson import json_util from bson import json_util
from libbot.pyrogram.classes import PyroClient from libbot.pyrogram.classes import PyroClient
from classes.enums import GarbageType from classes.enums import GarbageType
from classes.location import Location from classes.location import Location
from classes.pyrouser import PyroUser from classes.pyrouser import PyroUser
from modules.database import col_entries, col_users from modules.database import col_users
from modules.utils import from_utc from modules.database_api import col_entries
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def remind(app: PyroClient) -> None: async def remind(app: PyroClient) -> None:
utcnow = datetime.utcnow() utcnow = datetime.now(pytz.utc)
logger.debug("Performing reminder lookup for %s (UTCNOW)", utcnow) logger.debug("Performing reminder lookup for %s (UTCNOW)", utcnow)
@@ -39,12 +40,10 @@ async def remind(app: PyroClient) -> None:
try: try:
location: Location = await app.get_location(user.location.id) # type: ignore location: Location = await app.get_location(user.location.id) # type: ignore
except ValueError: except ValueError:
logger.warning("Skipping reminder for %s due to invalid location", user.id)
continue continue
user_date = from_utc( user_date = user.get_reminder_date().replace(tzinfo=None)
datetime.utcnow() + timedelta(days=user.offset),
user.location.timezone.zone,
).replace(hour=0, minute=0, second=0, microsecond=0)
entries = await col_entries.find( entries = await col_entries.find(
{ {

View File

@@ -6,7 +6,7 @@ from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove
from classes.location import Location from classes.location import Location
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from modules.database import col_locations from modules.database_api import col_locations
async def search_name(app: PyroClient, message: Message) -> Union[Location, None]: async def search_name(app: PyroClient, message: Message) -> Union[Location, None]:

View File

@@ -6,7 +6,7 @@ from pyrogram.types import Message, ReplyKeyboardRemove
from classes.location import Location from classes.location import Location
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from modules.database import col_locations from modules.database_api import col_locations
from modules.search_name import search_name from modules.search_name import search_name

View File

@@ -1,12 +1,13 @@
from datetime import datetime from datetime import datetime
from typing import Union from typing import Union
from pytz import UTC import pytz
from pytz import timezone as pytz_timezone
def to_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: def to_utc(date: datetime, timezone: Union[str, None] = None) -> datetime:
"""Move timezone unaware datetime object to UTC timezone and return it. """*DEPRECATED AND WILL BE REMOVED IN FUTURE RELEASES*
Move timezone unaware datetime object to UTC timezone and return it.
### Args: ### Args:
* date (`datetime`): Datetime to be converted. * date (`datetime`): Datetime to be converted.
@@ -16,11 +17,15 @@ def to_utc(date: datetime, timezone: Union[str, None] = None) -> datetime:
* `datetime`: Timezone unaware datetime in UTC with timezone's offset applied to it. * `datetime`: Timezone unaware datetime in UTC with timezone's offset applied to it.
""" """
timezone = "UTC" if timezone is None else timezone timezone = "UTC" if timezone is None else timezone
return pytz_timezone(timezone).localize(date).astimezone(UTC).replace(tzinfo=None) return (
pytz.timezone(timezone).localize(date).astimezone(pytz.utc).replace(tzinfo=None)
)
def from_utc(date: datetime, timezone: Union[str, None] = None) -> datetime: def from_utc(date: datetime, timezone: Union[str, None] = None) -> datetime:
"""Move timezone unaware datetime object to the timezone specified and return it. """*DEPRECATED AND WILL BE REMOVED IN FUTURE RELEASES*
Move timezone unaware datetime object to the timezone specified and return it.
### Args: ### Args:
* date (`datetime`): Datetime to be converted. * date (`datetime`): Datetime to be converted.
@@ -31,8 +36,5 @@ def from_utc(date: datetime, timezone: Union[str, None] = None) -> datetime:
""" """
timezone = "UTC" if timezone is None else timezone timezone = "UTC" if timezone is None else timezone
return ( return (
pytz_timezone("UTC") pytz.utc.localize(date).astimezone(pytz.timezone(timezone)).replace(tzinfo=None)
.localize(date)
.astimezone(pytz_timezone(timezone))
.replace(tzinfo=None)
) )

View File

@@ -1,13 +1,13 @@
import logging import logging
from datetime import datetime from datetime import datetime
import pytz
from convopyro import listen_message from convopyro import listen_message
from pyrogram import filters from pyrogram import filters
from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from modules import custom_filters from modules import custom_filters
from modules.utils import from_utc
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -65,18 +65,19 @@ async def command_set_offset(app: PyroClient, message: Message):
logger.info("User %s has set offset to %s", user.id, offset) logger.info("User %s has set offset to %s", user.id, offset)
garbage_time = from_utc( garbage_time = (
datetime(1970, 1, 1, user.time_hour, user.time_minute), datetime.now(pytz.utc)
None if user.location is None else user.location.timezone.zone, .replace(hour=user.time_hour, minute=user.time_minute)
).strftime(app._("time", "formats")) .astimezone(user.location.timezone or pytz.utc)
)
await answer.reply_text( await answer.reply_text(
app._("set_offset_finished", "messages", locale=user.locale).format( app._("set_offset_finished", "messages", locale=user.locale).format(
offset=offset, offset=offset,
time=garbage_time, time=garbage_time.strftime(app._("time", "formats", locale=user.locale)),
toggle_notice="" toggle_notice=(
if user.enabled "" if user.enabled else app._("toggle", "messages", locale=user.locale)
else app._("toggle", "messages", locale=user.locale), ),
), ),
reply_markup=ReplyKeyboardRemove(), reply_markup=ReplyKeyboardRemove(),
) )

View File

@@ -1,13 +1,13 @@
import logging import logging
from datetime import datetime from datetime import datetime
import pytz
from convopyro import listen_message from convopyro import listen_message
from pyrogram import filters from pyrogram import filters
from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from modules import custom_filters from modules import custom_filters
from modules.utils import to_utc
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -55,31 +55,33 @@ async def command_set_time(app: PyroClient, message: Message):
break break
now = datetime.now() # Time we got from the user
parsed_time = datetime.strptime(answer.text, "%H:%M")
parsed_time = datetime.strptime(answer.text, "%H:%M").replace( # Datetime user means in their timezone
year=now.year, month=now.month, day=now.day, second=0, microsecond=0 user_time = datetime.now(user.location.timezone).replace(
hour=parsed_time.hour, minute=parsed_time.minute, second=0, microsecond=0
) )
user_time = to_utc(parsed_time, user.location.timezone.zone) # Datetime in user's timezone moved to UTC timezone
utc_time = user_time.astimezone(pytz.utc)
await user.update_time(hour=user_time.hour, minute=user_time.minute) await user.update_time(hour=utc_time.hour, minute=utc_time.minute)
logger.info( logger.info(
"User %s has selected notification time of %s", "User %s has selected notification time of %s (%s UTC)",
user.id, user.id,
user_time.strftime("%H:%M"), user_time.strftime("%H:%M"),
utc_time.strftime("%H:%M"),
) )
garbage_time = parsed_time.strftime(app._("time", "formats"))
await answer.reply_text( await answer.reply_text(
app._("set_time_finished", "messages", locale=user.locale).format( app._("set_time_finished", "messages", locale=user.locale).format(
offset=user.offset, offset=user.offset,
time=garbage_time, time=user_time.strftime(app._("time", "formats", locale=user.locale)),
toggle_notice="" toggle_notice=(
if user.enabled "" if user.enabled else app._("toggle", "messages", locale=user.locale)
else app._("toggle", "messages", locale=user.locale), ),
), ),
reply_markup=ReplyKeyboardRemove(), reply_markup=ReplyKeyboardRemove(),
) )

View File

@@ -1,5 +1,4 @@
import logging import logging
from datetime import datetime
from convopyro import listen_message from convopyro import listen_message
from libbot import i18n from libbot import i18n
@@ -11,7 +10,6 @@ from classes.pyroclient import PyroClient
from modules import custom_filters from modules import custom_filters
from modules.search_name import search_name from modules.search_name import search_name
from modules.search_nearby import search_nearby from modules.search_nearby import search_nearby
from modules.utils import from_utc
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -74,10 +72,9 @@ async def command_setup(app: PyroClient, message: Message):
await user.update_location(location.id) await user.update_location(location.id)
user_time = from_utc( user_time = user.get_reminder_time().strftime(
datetime(1970, 1, 1, user.time_hour, user.time_minute), app._("time", "formats", locale=user.locale)
None if user.location is None else user.location.timezone.zone, )
).strftime(app._("time", "formats", locale=user.locale))
await message.reply_text( await message.reply_text(
app._("setup_finished", "messages", locale=user.locale).format( app._("setup_finished", "messages", locale=user.locale).format(

View File

@@ -1,5 +1,3 @@
from datetime import datetime
from convopyro import listen_message from convopyro import listen_message
from pyrogram import filters from pyrogram import filters
from pyrogram.types import ( from pyrogram.types import (
@@ -11,7 +9,6 @@ from pyrogram.types import (
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from modules import custom_filters from modules import custom_filters
from modules.utils import from_utc
@PyroClient.on_message( @PyroClient.on_message(
@@ -20,7 +17,12 @@ from modules.utils import from_utc
async def command_start(app: PyroClient, message: Message): async def command_start(app: PyroClient, message: Message):
user = await app.find_user(message.from_user) user = await app.find_user(message.from_user)
await message.reply_text(app._("start", "messages", locale=user.locale)) await message.reply_text(
app._("start", "messages", locale=user.locale).format(
terms_of_service="https://garbagebot.eu/community/public/terms-of-service",
privacy_policy="https://garbagebot.eu/community/public/privacy-policy",
)
)
join_code = None if len(message.command) == 1 else message.command[1] join_code = None if len(message.command) == 1 else message.command[1]
@@ -86,13 +88,15 @@ async def command_start(app: PyroClient, message: Message):
await user.update_location(location.id) await user.update_location(location.id)
user_time = from_utc( user_time = user.get_reminder_time().strftime(
datetime(1970, 1, 1, user.time_hour, user.time_minute), app._("time", "formats", locale=user.locale)
None if user.location is None else user.location.timezone.zone, )
).strftime(app._("time", "formats", locale=user.locale))
await answer.reply_text( await answer.reply_text(
app._("start_selection_yes", "messages", locale=user.locale).format( app._("start_selection_yes", "messages", locale=user.locale).format(
name=location.name, offset=user.offset, time=user_time name=location.name,
offset=user.offset,
time=user_time,
), ),
reply_markup=ReplyKeyboardRemove(), reply_markup=ReplyKeyboardRemove(),
) )

View File

@@ -1,11 +1,8 @@
from datetime import datetime
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message from pyrogram.types import Message
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from modules import custom_filters from modules import custom_filters
from modules.utils import from_utc
@PyroClient.on_message( @PyroClient.on_message(
@@ -16,16 +13,18 @@ async def command_toggle(app: PyroClient, message: Message):
await user.update_state(not user.enabled) await user.update_state(not user.enabled)
if user.enabled: if not user.enabled:
await message.reply_text( await message.reply_text(
app._("toggle_disabled", "messages", locale=user.locale) app._("toggle_disabled", "messages", locale=user.locale)
) )
return return
user_time = from_utc( try:
datetime(1970, 1, 1, user.time_hour, user.time_minute), user_time = user.get_reminder_time().strftime(
None if user.location is None else user.location.timezone.zone, app._("time", "formats", locale=user.locale)
).strftime(app._("time", "formats")) )
except AttributeError:
user_time = "N/A"
if user.location is None: if user.location is None:
await message.reply_text( await message.reply_text(

View File

@@ -1,12 +1,13 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta
import pytz
from pyrogram import filters from pyrogram import filters
from pyrogram.types import Message from pyrogram.types import Message
from classes.garbage_entry import GarbageEntry from classes.garbage_entry import GarbageEntry
from classes.pyroclient import PyroClient from classes.pyroclient import PyroClient
from modules import custom_filters from modules import custom_filters
from modules.database import col_entries from modules.database_api import col_entries
@PyroClient.on_message( @PyroClient.on_message(
@@ -23,11 +24,11 @@ async def command_upcoming(app: PyroClient, message: Message):
date_min = ( date_min = (
datetime.now(user.location.timezone).replace(second=0, microsecond=0) datetime.now(user.location.timezone).replace(second=0, microsecond=0)
).replace(tzinfo=timezone.utc) ).replace(tzinfo=pytz.utc)
date_max = ( date_max = (
datetime.now(user.location.timezone).replace(second=0, microsecond=0) datetime.now(user.location.timezone).replace(second=0, microsecond=0)
+ timedelta(days=30) + timedelta(days=30)
).replace(tzinfo=timezone.utc) ).replace(tzinfo=pytz.utc)
entries = [ entries = [
await GarbageEntry.from_record(entry) await GarbageEntry.from_record(entry)

View File

@@ -1,11 +1,12 @@
aiohttp~=3.8.5 aiohttp~=3.10.2
apscheduler~=3.10.3 apscheduler~=3.10.4
async_pymongo==0.1.9
convopyro==0.5 convopyro==0.5
mongodb-migrations==1.3.0 mongodb-migrations==1.3.1
pykeyboard==0.1.5 pytz>=2024.1
tgcrypto==1.2.5 tgcrypto==1.2.5
ujson>=5.0.0 ujson>=5.0.0
uvloop==0.19.0 uvloop==0.20.0
--extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple --extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple
async_pymongo==0.1.4 libbot[speed,pyrogram]==3.2.3
libbot[speed,pyrogram]==2.0.1 pykeyboard==0.1.7

View File

@@ -4,6 +4,6 @@
"enabled": true, "enabled": true,
"location": 1, "location": 1,
"offset": 1, "offset": 1,
"time_hour": 18, "time_hour": 16,
"time_minute": 0 "time_minute": 0
} }