218 Commits

Author SHA1 Message Date
450476c8a8 Resolves #87 2024-08-10 14:19:40 +02:00
0b0ca6b832 revert 7f786d59ca
revert Update dependency aiohttp to ~=3.10.0 (#84)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [aiohttp](https://github.com/aio-libs/aiohttp) | minor | `~=3.9.1` -> `~=3.10.0` |

---

### Release Notes

<details>
<summary>aio-libs/aiohttp (aiohttp)</summary>

### [`v3.10.0`](https://github.com/aio-libs/aiohttp/blob/HEAD/CHANGES.rst#3100-2024-07-30)

[Compare Source](https://github.com/aio-libs/aiohttp/compare/v3.9.5...v3.10.0)

\========================

## Bug fixes

-   Fixed server response headers for `Content-Type` and `Content-Encoding` for
    static compressed files -- by :user:`steverep`.

    Server will now respond with a `Content-Type` appropriate for the compressed
    file (e.g. `"application/gzip"`), and omit the `Content-Encoding` header.
    Users should expect that most clients will no longer decompress such responses
    by default.

    *Related issues and pull requests on GitHub:*
    :issue:`4462`.

-   Fixed duplicate cookie expiration calls in the CookieJar implementation

    *Related issues and pull requests on GitHub:*
    :issue:`7784`.

-   Adjusted `FileResponse` to check file existence and access when preparing the response -- by :user:`steverep`.

    The :py:class:`~aiohttp.web.FileResponse` class was modified to respond with
    403 Forbidden or 404 Not Found as appropriate.  Previously, it would cause a
    server error if the path did not exist or could not be accessed.  Checks for
    existence, non-regular files, and permissions were expected to be done in the
    route handler.  For static routes, this now permits a compressed file to exist
    without its uncompressed variant and still be served.  In addition, this
    changes the response status for files without read permission to 403, and for
    non-regular files from 404 to 403 for consistency.

    *Related issues and pull requests on GitHub:*
    :issue:`8182`.

-   Fixed `AsyncResolver` to match `ThreadedResolver` behavior
    \-- by :user:`bdraco`.

    On system with IPv6 support, the :py:class:`~aiohttp.resolver.AsyncResolver` would not fallback
    to providing A records when AAAA records were not available.
    Additionally, unlike the :py:class:`~aiohttp.resolver.ThreadedResolver`, the :py:class:`~aiohttp.resolver.AsyncResolver`
    did not handle link-local addresses correctly.

    This change makes the behavior consistent with the :py:class:`~aiohttp.resolver.ThreadedResolver`.

    *Related issues and pull requests on GitHub:*
    :issue:`8270`.

-   Fixed `ws_connect` not respecting `receive_timeout`` on WS(S) connection. -- by :user:`arcivanov\`.

    *Related issues and pull requests on GitHub:*
    :issue:`8444`.

-   Removed blocking I/O in the event loop for static resources and refactored
    exception handling -- by :user:`steverep`.

    File system calls when handling requests for static routes were moved to a
    separate thread to potentially improve performance. Exception handling
    was tightened in order to only return 403 Forbidden or 404 Not Found responses
    for expected scenarios; 500 Internal Server Error would be returned for any
    unknown errors.

    *Related issues and pull requests on GitHub:*
    :issue:`8507`.

## Features

-   Added a Request.wait_for_disconnection() method, as means of allowing request handlers to be notified of premature client disconnections.

    *Related issues and pull requests on GitHub:*
    :issue:`2492`.

-   Added 5 new exceptions: :py:exc:`~aiohttp.InvalidUrlClientError`, :py:exc:`~aiohttp.RedirectClientError`,
    :py:exc:`~aiohttp.NonHttpUrlClientError`, :py:exc:`~aiohttp.InvalidUrlRedirectClientError`,
    :py:exc:`~aiohttp.NonHttpUrlRedirectClientError`

    :py:exc:`~aiohttp.InvalidUrlRedirectClientError`, :py:exc:`~aiohttp.NonHttpUrlRedirectClientError`
    are raised instead of :py:exc:`ValueError` or :py:exc:`~aiohttp.InvalidURL` when the redirect URL is invalid. Classes
    :py:exc:`~aiohttp.InvalidUrlClientError`, :py:exc:`~aiohttp.RedirectClientError`,
    :py:exc:`~aiohttp.NonHttpUrlClientError` are base for them.

    The :py:exc:`~aiohttp.InvalidURL` now exposes a `description` property with the text explanation of the error details.

    \-- by :user:`setla`, :user:`AraHaan`, and :user:`bdraco`

    *Related issues and pull requests on GitHub:*
    :issue:`2507`, :issue:`3315`, :issue:`6722`, :issue:`8481`, :issue:`8482`.

-   Added a feature to retry closed connections automatically for idempotent methods. -- by :user:`Dreamsorcerer`

    *Related issues and pull requests on GitHub:*
    :issue:`7297`.

-   Implemented filter_cookies() with domain-matching and path-matching on the keys, instead of testing every single cookie.
    This may break existing cookies that have been saved with `CookieJar.save()`. Cookies can be migrated with this script::

        import pickle
        with file_path.open("rb") as f:
            cookies = pickle.load(f)

        morsels = [(name, m) for c in cookies.values() for name, m in c.items()]
        cookies.clear()
        for name, m in morsels:
            cookies[(m["domain"], m["path"].rstrip("/"))][name] = m

        with file_path.open("wb") as f:
            pickle.dump(cookies, f, pickle.HIGHEST_PROTOCOL)

    *Related issues and pull requests on GitHub:*
    :issue:`7583`, :issue:`8535`.

-   Separated connection and socket timeout errors, from ServerTimeoutError.

    *Related issues and pull requests on GitHub:*
    :issue:`7801`.

-   Implemented happy eyeballs

    *Related issues and pull requests on GitHub:*
    :issue:`7954`.

-   Added server capability to check for static files with Brotli compression via a `.br` extension -- by :user:`steverep`.

    *Related issues and pull requests on GitHub:*
    :issue:`8062`.

## Removals and backward incompatible breaking changes

-   The shutdown logic in 3.9 waited on all tasks, which caused issues with some libraries.
    In 3.10 we've changed this logic to only wait on request handlers. This means that it's
    important for developers to correctly handle the lifecycle of background tasks using a
    library such as `aiojobs`. If an application is using `handler_cancellation=True` then
    it is also a good idea to ensure that any :func:`asyncio.shield` calls are replaced with
    :func:`aiojobs.aiohttp.shield`.

    Please read the updated documentation on these points: \
    https://docs.aiohttp.org/en/stable/web_advanced.html#graceful-shutdown \
    https://docs.aiohttp.org/en/stable/web_advanced.html#web-handler-cancellation

    \-- by :user:`Dreamsorcerer`

    *Related issues and pull requests on GitHub:*
    :issue:`8495`.

## Improved documentation

-   Added documentation for `aiohttp.web.FileResponse`.

    *Related issues and pull requests on GitHub:*
    :issue:`3958`.

-   Improved the docs for the `ssl` params.

    *Related issues and pull requests on GitHub:*
    :issue:`8403`.

## Contributor-facing changes

-   Enabled HTTP parser tests originally intended for 3.9.2 release -- by :user:`pajod`.

    *Related issues and pull requests on GitHub:*
    :issue:`8088`.

## Miscellaneous internal changes

-   Improved URL handler resolution time by indexing resources in the UrlDispatcher.
    For applications with a large number of handlers, this should increase performance significantly.
    \-- by :user:`bdraco`

    *Related issues and pull requests on GitHub:*
    :issue:`7829`.

-   Added `nacl_middleware <https://github.com/CosmicDNA/nacl_middleware>`\_ to the list of middlewares in the third party section of the documentation.

    *Related issues and pull requests on GitHub:*
    :issue:`8346`.

-   Minor improvements to static typing -- by :user:`Dreamsorcerer`.

    *Related issues and pull requests on GitHub:*
    :issue:`8364`.

-   Added a 3.11-specific overloads to `ClientSession`  -- by :user:`max-muoto`.

    *Related issues and pull requests on GitHub:*
    :issue:`8463`.

-   Simplified path checks for `UrlDispatcher.add_static()` method -- by :user:`steverep`.

    *Related issues and pull requests on GitHub:*
    :issue:`8491`.

-   Avoided creating a future on every websocket receive -- by :user:`bdraco`.

    *Related issues and pull requests on GitHub:*
    :issue:`8498`.

-   Updated identity checks for all `WSMsgType` type compares -- by :user:`bdraco`.

    *Related issues and pull requests on GitHub:*
    :issue:`8501`.

-   When using Python 3.12 or later, the writer is no longer scheduled on the event loop if it can finish synchronously. Avoiding event loop scheduling reduces latency and improves performance. -- by :user:`bdraco`.

    *Related issues and pull requests on GitHub:*
    :issue:`8510`.

-   Restored :py:class:`~aiohttp.resolver.AsyncResolver` to be the default resolver. -- by :user:`bdraco`.

    :py:class:`~aiohttp.resolver.AsyncResolver` was disabled by default because
    of IPv6 compatibility issues. These issues have been resolved and
    :py:class:`~aiohttp.resolver.AsyncResolver` is again now the default resolver.

    *Related issues and pull requests on GitHub:*
    :issue:`8522`.

***

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #84
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-08-01 00:44:21 +03:00
7f786d59ca Update dependency aiohttp to ~=3.10.0 (#84)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [aiohttp](https://github.com/aio-libs/aiohttp) | minor | `~=3.9.1` -> `~=3.10.0` |

---

### Release Notes

<details>
<summary>aio-libs/aiohttp (aiohttp)</summary>

### [`v3.10.0`](https://github.com/aio-libs/aiohttp/blob/HEAD/CHANGES.rst#3100-2024-07-30)

[Compare Source](https://github.com/aio-libs/aiohttp/compare/v3.9.5...v3.10.0)

\========================

## Bug fixes

-   Fixed server response headers for `Content-Type` and `Content-Encoding` for
    static compressed files -- by :user:`steverep`.

    Server will now respond with a `Content-Type` appropriate for the compressed
    file (e.g. `"application/gzip"`), and omit the `Content-Encoding` header.
    Users should expect that most clients will no longer decompress such responses
    by default.

    *Related issues and pull requests on GitHub:*
    :issue:`4462`.

-   Fixed duplicate cookie expiration calls in the CookieJar implementation

    *Related issues and pull requests on GitHub:*
    :issue:`7784`.

-   Adjusted `FileResponse` to check file existence and access when preparing the response -- by :user:`steverep`.

    The :py:class:`~aiohttp.web.FileResponse` class was modified to respond with
    403 Forbidden or 404 Not Found as appropriate.  Previously, it would cause a
    server error if the path did not exist or could not be accessed.  Checks for
    existence, non-regular files, and permissions were expected to be done in the
    route handler.  For static routes, this now permits a compressed file to exist
    without its uncompressed variant and still be served.  In addition, this
    changes the response status for files without read permission to 403, and for
    non-regular files from 404 to 403 for consistency.

    *Related issues and pull requests on GitHub:*
    :issue:`8182`.

-   Fixed `AsyncResolver` to match `ThreadedResolver` behavior
    \-- by :user:`bdraco`.

    On system with IPv6 support, the :py:class:`~aiohttp.resolver.AsyncResolver` would not fallback
    to providing A records when AAAA records were not available.
    Additionally, unlike the :py:class:`~aiohttp.resolver.ThreadedResolver`, the :py:class:`~aiohttp.resolver.AsyncResolver`
    did not handle link-local addresses correctly.

    This change makes the behavior consistent with the :py:class:`~aiohttp.resolver.ThreadedResolver`.

    *Related issues and pull requests on GitHub:*
    :issue:`8270`.

-   Fixed `ws_connect` not respecting `receive_timeout`` on WS(S) connection. -- by :user:`arcivanov\`.

    *Related issues and pull requests on GitHub:*
    :issue:`8444`.

-   Removed blocking I/O in the event loop for static resources and refactored
    exception handling -- by :user:`steverep`.

    File system calls when handling requests for static routes were moved to a
    separate thread to potentially improve performance. Exception handling
    was tightened in order to only return 403 Forbidden or 404 Not Found responses
    for expected scenarios; 500 Internal Server Error would be returned for any
    unknown errors.

    *Related issues and pull requests on GitHub:*
    :issue:`8507`.

## Features

-   Added a Request.wait_for_disconnection() method, as means of allowing request handlers to be notified of premature client disconnections.

    *Related issues and pull requests on GitHub:*
    :issue:`2492`.

-   Added 5 new exceptions: :py:exc:`~aiohttp.InvalidUrlClientError`, :py:exc:`~aiohttp.RedirectClientError`,
    :py:exc:`~aiohttp.NonHttpUrlClientError`, :py:exc:`~aiohttp.InvalidUrlRedirectClientError`,
    :py:exc:`~aiohttp.NonHttpUrlRedirectClientError`

    :py:exc:`~aiohttp.InvalidUrlRedirectClientError`, :py:exc:`~aiohttp.NonHttpUrlRedirectClientError`
    are raised instead of :py:exc:`ValueError` or :py:exc:`~aiohttp.InvalidURL` when the redirect URL is invalid. Classes
    :py:exc:`~aiohttp.InvalidUrlClientError`, :py:exc:`~aiohttp.RedirectClientError`,
    :py:exc:`~aiohttp.NonHttpUrlClientError` are base for them.

    The :py:exc:`~aiohttp.InvalidURL` now exposes a `description` property with the text explanation of the error details.

    \-- by :user:`setla`, :user:`AraHaan`, and :user:`bdraco`

    *Related issues and pull requests on GitHub:*
    :issue:`2507`, :issue:`3315`, :issue:`6722`, :issue:`8481`, :issue:`8482`.

-   Added a feature to retry closed connections automatically for idempotent methods. -- by :user:`Dreamsorcerer`

    *Related issues and pull requests on GitHub:*
    :issue:`7297`.

-   Implemented filter_cookies() with domain-matching and path-matching on the keys, instead of testing every single cookie.
    This may break existing cookies that have been saved with `CookieJar.save()`. Cookies can be migrated with this script::

        import pickle
        with file_path.open("rb") as f:
            cookies = pickle.load(f)

        morsels = [(name, m) for c in cookies.values() for name, m in c.items()]
        cookies.clear()
        for name, m in morsels:
            cookies[(m["domain"], m["path"].rstrip("/"))][name] = m

        with file_path.open("wb") as f:
            pickle.dump(cookies, f, pickle.HIGHEST_PROTOCOL)

    *Related issues and pull requests on GitHub:*
    :issue:`7583`, :issue:`8535`.

-   Separated connection and socket timeout errors, from ServerTimeoutError.

    *Related issues and pull requests on GitHub:*
    :issue:`7801`.

-   Implemented happy eyeballs

    *Related issues and pull requests on GitHub:*
    :issue:`7954`.

-   Added server capability to check for static files with Brotli compression via a `.br` extension -- by :user:`steverep`.

    *Related issues and pull requests on GitHub:*
    :issue:`8062`.

## Removals and backward incompatible breaking changes

-   The shutdown logic in 3.9 waited on all tasks, which caused issues with some libraries.
    In 3.10 we've changed this logic to only wait on request handlers. This means that it's
    important for developers to correctly handle the lifecycle of background tasks using a
    library such as `aiojobs`. If an application is using `handler_cancellation=True` then
    it is also a good idea to ensure that any :func:`asyncio.shield` calls are replaced with
    :func:`aiojobs.aiohttp.shield`.

    Please read the updated documentation on these points: \
    https://docs.aiohttp.org/en/stable/web_advanced.html#graceful-shutdown \
    https://docs.aiohttp.org/en/stable/web_advanced.html#web-handler-cancellation

    \-- by :user:`Dreamsorcerer`

    *Related issues and pull requests on GitHub:*
    :issue:`8495`.

## Improved documentation

-   Added documentation for `aiohttp.web.FileResponse`.

    *Related issues and pull requests on GitHub:*
    :issue:`3958`.

-   Improved the docs for the `ssl` params.

    *Related issues and pull requests on GitHub:*
    :issue:`8403`.

## Contributor-facing changes

-   Enabled HTTP parser tests originally intended for 3.9.2 release -- by :user:`pajod`.

    *Related issues and pull requests on GitHub:*
    :issue:`8088`.

## Miscellaneous internal changes

-   Improved URL handler resolution time by indexing resources in the UrlDispatcher.
    For applications with a large number of handlers, this should increase performance significantly.
    \-- by :user:`bdraco`

    *Related issues and pull requests on GitHub:*
    :issue:`7829`.

-   Added `nacl_middleware <https://github.com/CosmicDNA/nacl_middleware>`\_ to the list of middlewares in the third party section of the documentation.

    *Related issues and pull requests on GitHub:*
    :issue:`8346`.

-   Minor improvements to static typing -- by :user:`Dreamsorcerer`.

    *Related issues and pull requests on GitHub:*
    :issue:`8364`.

-   Added a 3.11-specific overloads to `ClientSession`  -- by :user:`max-muoto`.

    *Related issues and pull requests on GitHub:*
    :issue:`8463`.

-   Simplified path checks for `UrlDispatcher.add_static()` method -- by :user:`steverep`.

    *Related issues and pull requests on GitHub:*
    :issue:`8491`.

-   Avoided creating a future on every websocket receive -- by :user:`bdraco`.

    *Related issues and pull requests on GitHub:*
    :issue:`8498`.

-   Updated identity checks for all `WSMsgType` type compares -- by :user:`bdraco`.

    *Related issues and pull requests on GitHub:*
    :issue:`8501`.

-   When using Python 3.12 or later, the writer is no longer scheduled on the event loop if it can finish synchronously. Avoiding event loop scheduling reduces latency and improves performance. -- by :user:`bdraco`.

    *Related issues and pull requests on GitHub:*
    :issue:`8510`.

-   Restored :py:class:`~aiohttp.resolver.AsyncResolver` to be the default resolver. -- by :user:`bdraco`.

    :py:class:`~aiohttp.resolver.AsyncResolver` was disabled by default because
    of IPv6 compatibility issues. These issues have been resolved and
    :py:class:`~aiohttp.resolver.AsyncResolver` is again now the default resolver.

    *Related issues and pull requests on GitHub:*
    :issue:`8522`.

***

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #84
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-07-31 02:13:38 +03:00
e2093605bb Update dependency libbot to v3.2.3 (#83)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [libbot](https://git.end-play.xyz/profitroll/LibBotUniversal) | patch | `==3.2.2` -> `==3.2.3` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #83
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-07-10 08:11:54 +03:00
c38d79afe1 Update dependency pillow to ~=10.4.0 (#82)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pillow](https://github.com/python-pillow/Pillow) ([changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)) | minor | `~=10.3.0` -> `~=10.4.0` |

---

### Release Notes

<details>
<summary>python-pillow/Pillow (pillow)</summary>

### [`v10.4.0`](https://github.com/python-pillow/Pillow/blob/HEAD/CHANGES.rst#1040-2024-07-01)

[Compare Source](https://github.com/python-pillow/Pillow/compare/10.3.0...10.4.0)

-   Raise FileNotFoundError if show_file() path does not exist [#&#8203;8178](https://github.com/python-pillow/Pillow/issues/8178)
    \[radarhere]

-   Improved reading 16-bit TGA images with colour [#&#8203;7965](https://github.com/python-pillow/Pillow/issues/7965)
    \[Yay295, radarhere]

-   Deprecate non-image ImageCms modes [#&#8203;8031](https://github.com/python-pillow/Pillow/issues/8031)
    \[radarhere]

-   Fixed processing multiple JPEG EXIF markers [#&#8203;8127](https://github.com/python-pillow/Pillow/issues/8127)
    \[radarhere]

-   Do not preserve EXIFIFD tag by default when saving TIFF images [#&#8203;8110](https://github.com/python-pillow/Pillow/issues/8110)
    \[radarhere]

-   Added ImageFont.load_default_imagefont() [#&#8203;8086](https://github.com/python-pillow/Pillow/issues/8086)
    \[radarhere]

-   Added Image.WARN_POSSIBLE_FORMATS [#&#8203;8063](https://github.com/python-pillow/Pillow/issues/8063)
    \[radarhere]

-   Remove zero-byte end padding when parsing any XMP data [#&#8203;8171](https://github.com/python-pillow/Pillow/issues/8171)
    \[radarhere]

-   Do not detect Ultra HDR images as MPO [#&#8203;8056](https://github.com/python-pillow/Pillow/issues/8056)
    \[radarhere]

-   Raise SyntaxError specific to JP2 [#&#8203;8146](https://github.com/python-pillow/Pillow/issues/8146)
    \[Yay295, radarhere]

-   Do not use first frame duration for other frames when saving APNG images [#&#8203;8104](https://github.com/python-pillow/Pillow/issues/8104)
    \[radarhere]

-   Consider I;16 pixel size when using a 1 mode mask [#&#8203;8112](https://github.com/python-pillow/Pillow/issues/8112)
    \[radarhere]

-   When saving multiple PNG frames, convert to mode rather than raw mode [#&#8203;8087](https://github.com/python-pillow/Pillow/issues/8087)
    \[radarhere]

-   Added byte support to FreeTypeFont [#&#8203;8141](https://github.com/python-pillow/Pillow/issues/8141)
    \[radarhere]

-   Allow float center for rotate operations [#&#8203;8114](https://github.com/python-pillow/Pillow/issues/8114)
    \[radarhere]

-   Do not read layers immediately when opening PSD images [#&#8203;8039](https://github.com/python-pillow/Pillow/issues/8039)
    \[radarhere]

-   Restore original thread state [#&#8203;8065](https://github.com/python-pillow/Pillow/issues/8065)
    \[radarhere]

-   Read IM and TIFF images as RGB, rather than RGBX [#&#8203;7997](https://github.com/python-pillow/Pillow/issues/7997)
    \[radarhere]

-   Only preserve TIFF IPTC_NAA_CHUNK tag if type is BYTE or UNDEFINED [#&#8203;7948](https://github.com/python-pillow/Pillow/issues/7948)
    \[radarhere]

-   Clarify ImageDraw2 error message when size is missing [#&#8203;8165](https://github.com/python-pillow/Pillow/issues/8165)
    \[radarhere]

-   Support unpacking more rawmodes to RGBA palettes [#&#8203;7966](https://github.com/python-pillow/Pillow/issues/7966)
    \[radarhere]

-   Removed support for Qt 5 [#&#8203;8159](https://github.com/python-pillow/Pillow/issues/8159)
    \[radarhere]

-   Improve `ImageFont.freetype` support for XDG directories on Linux [#&#8203;8135](https://github.com/python-pillow/Pillow/issues/8135)
    \[mamg22, radarhere]

-   Improved consistency of XMP handling [#&#8203;8069](https://github.com/python-pillow/Pillow/issues/8069)
    \[radarhere]

-   Use pkg-config to help find libwebp and raqm [#&#8203;8142](https://github.com/python-pillow/Pillow/issues/8142)
    \[radarhere]

-   Accept 't' suffix for libtiff version [#&#8203;8126](https://github.com/python-pillow/Pillow/issues/8126), [#&#8203;8129](https://github.com/python-pillow/Pillow/issues/8129)
    \[radarhere]

-   Deprecate ImageDraw.getdraw hints parameter [#&#8203;8124](https://github.com/python-pillow/Pillow/issues/8124)
    \[radarhere, hugovk]

-   Added ImageDraw circle() [#&#8203;8085](https://github.com/python-pillow/Pillow/issues/8085)
    \[void4, hugovk, radarhere]

-   Add mypy target to Makefile [#&#8203;8077](https://github.com/python-pillow/Pillow/issues/8077)
    \[Yay295]

-   Added more modes to Image.MODES [#&#8203;7984](https://github.com/python-pillow/Pillow/issues/7984)
    \[radarhere]

-   Deprecate BGR;15, BGR;16 and BGR;24 modes [#&#8203;7978](https://github.com/python-pillow/Pillow/issues/7978)
    \[radarhere, hugovk]

-   Fix ImagingAccess for I;16N on big-endian [#&#8203;7921](https://github.com/python-pillow/Pillow/issues/7921)
    \[Yay295, radarhere]

-   Support reading P mode TIFF images with padding [#&#8203;7996](https://github.com/python-pillow/Pillow/issues/7996)
    \[radarhere]

-   Deprecate support for libtiff < 4 [#&#8203;7998](https://github.com/python-pillow/Pillow/issues/7998)
    \[radarhere, hugovk]

-   Corrected ImageShow UnixViewer command [#&#8203;7987](https://github.com/python-pillow/Pillow/issues/7987)
    \[radarhere]

-   Use functools.cached_property in ImageStat [#&#8203;7952](https://github.com/python-pillow/Pillow/issues/7952)
    \[nulano, hugovk, radarhere]

-   Add support for reading BITMAPV2INFOHEADER and BITMAPV3INFOHEADER [#&#8203;7956](https://github.com/python-pillow/Pillow/issues/7956)
    \[Cirras, radarhere]

-   Support reading CMYK JPEG2000 images [#&#8203;7947](https://github.com/python-pillow/Pillow/issues/7947)
    \[radarhere]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/82
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-07-02 00:55:50 +03:00
81748a889d Update dependency async_pymongo to v0.1.6 (#81)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [async_pymongo](https://github.com/Mayuri-Chan/async_pymongo) | patch | `==0.1.5` -> `==0.1.6` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #81
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-06-23 14:34:11 +03:00
37479f69b3 Update dependency async_pymongo to v0.1.5 (#80)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [async_pymongo](https://github.com/Mayuri-Chan/async_pymongo) | patch | `==0.1.4` -> `==0.1.5` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Co-authored-by: Profitroll <profitroll@noreply.localhost>
Reviewed-on: #80
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-06-02 12:58:16 +03:00
6388bc1274 Update dependency libbot to v3.2.2 (#79)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [libbot](https://git.end-play.xyz/profitroll/LibBotUniversal) | patch | `==3.2.1` -> `==3.2.2` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #79
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-05-26 23:58:15 +03:00
b122a36f6c Update dependency libbot to v3.2.1 (#78)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [libbot](https://git.end-play.xyz/profitroll/LibBotUniversal) | minor | `==3.1.0` -> `==3.2.1` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #78
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-05-26 19:40:47 +03:00
533e177f64 Update dependency libbot to v3.1.0 (#77)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [libbot](https://git.end-play.xyz/profitroll/LibBotUniversal) | minor | `==3.0.0` -> `==3.1.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #77
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-05-24 22:46:24 +03:00
c9f37ea2b6 Fixed deletion not working properly 2024-05-06 12:47:09 +02:00
7f107b2306 Fixed raw_result absent on deleted object 2024-05-06 13:29:26 +03:00
818afa0b74 Fixed caption being added explicitly 2024-04-30 20:57:51 +02:00
c99f22b1b8 Update dependency pillow to ~=10.3.0 (#76)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pillow](https://github.com/python-pillow/Pillow) ([changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)) | minor | `~=10.2.0` -> `~=10.3.0` |

---

### Release Notes

<details>
<summary>python-pillow/Pillow (pillow)</summary>

### [`v10.3.0`](https://github.com/python-pillow/Pillow/blob/HEAD/CHANGES.rst#1030-2024-04-01)

[Compare Source](https://github.com/python-pillow/Pillow/compare/10.2.0...10.3.0)

-   CVE-2024-28219: Use `strncpy` to avoid buffer overflow [#&#8203;7928](https://github.com/python-pillow/Pillow/issues/7928)
    \[radarhere, hugovk]

-   Deprecate `eval()`, replacing it with `lambda_eval()` and `unsafe_eval()` [#&#8203;7927](https://github.com/python-pillow/Pillow/issues/7927)
    \[radarhere, hugovk]

-   Raise `ValueError` if seeking to greater than offset-sized integer in TIFF [#&#8203;7883](https://github.com/python-pillow/Pillow/issues/7883)
    \[radarhere]

-   Add `--report` argument to `__main__.py` to omit supported formats [#&#8203;7818](https://github.com/python-pillow/Pillow/issues/7818)
    \[nulano, radarhere, hugovk]

-   Added RGB to I;16, I;16L, I;16B and I;16N conversion [#&#8203;7918](https://github.com/python-pillow/Pillow/issues/7918), [#&#8203;7920](https://github.com/python-pillow/Pillow/issues/7920)
    \[radarhere]

-   Fix editable installation with custom build backend and configuration options [#&#8203;7658](https://github.com/python-pillow/Pillow/issues/7658)
    \[nulano, radarhere]

-   Fix putdata() for I;16N on big-endian [#&#8203;7209](https://github.com/python-pillow/Pillow/issues/7209)
    \[Yay295, hugovk, radarhere]

-   Determine MPO size from markers, not EXIF data [#&#8203;7884](https://github.com/python-pillow/Pillow/issues/7884)
    \[radarhere]

-   Improved conversion from RGB to RGBa, LA and La [#&#8203;7888](https://github.com/python-pillow/Pillow/issues/7888)
    \[radarhere]

-   Support FITS images with GZIP\_1 compression [#&#8203;7894](https://github.com/python-pillow/Pillow/issues/7894)
    \[radarhere]

-   Use I;16 mode for 9-bit JPEG 2000 images [#&#8203;7900](https://github.com/python-pillow/Pillow/issues/7900)
    \[scaramallion, radarhere]

-   Raise ValueError if kmeans is negative [#&#8203;7891](https://github.com/python-pillow/Pillow/issues/7891)
    \[radarhere]

-   Remove TIFF tag OSUBFILETYPE when saving using libtiff [#&#8203;7893](https://github.com/python-pillow/Pillow/issues/7893)
    \[radarhere]

-   Raise ValueError for negative values when loading P1-P3 PPM images [#&#8203;7882](https://github.com/python-pillow/Pillow/issues/7882)
    \[radarhere]

-   Added reading of JPEG2000 palettes [#&#8203;7870](https://github.com/python-pillow/Pillow/issues/7870)
    \[radarhere]

-   Added alpha_quality argument when saving WebP images [#&#8203;7872](https://github.com/python-pillow/Pillow/issues/7872)
    \[radarhere]

-   Fixed joined corners for ImageDraw rounded_rectangle() non-integer dimensions [#&#8203;7881](https://github.com/python-pillow/Pillow/issues/7881)
    \[radarhere]

-   Stop reading EPS image at EOF marker [#&#8203;7753](https://github.com/python-pillow/Pillow/issues/7753)
    \[radarhere]

-   PSD layer co-ordinates may be negative [#&#8203;7706](https://github.com/python-pillow/Pillow/issues/7706)
    \[radarhere]

-   Use subprocess with CREATE_NO_WINDOW flag in ImageShow WindowsViewer [#&#8203;7791](https://github.com/python-pillow/Pillow/issues/7791)
    \[radarhere]

-   When saving GIF frame that restores to background color, do not fill identical pixels [#&#8203;7788](https://github.com/python-pillow/Pillow/issues/7788)
    \[radarhere]

-   Fixed reading PNG iCCP compression method [#&#8203;7823](https://github.com/python-pillow/Pillow/issues/7823)
    \[radarhere]

-   Allow writing IFDRational to UNDEFINED tag [#&#8203;7840](https://github.com/python-pillow/Pillow/issues/7840)
    \[radarhere]

-   Fix logged tag name when loading Exif data [#&#8203;7842](https://github.com/python-pillow/Pillow/issues/7842)
    \[radarhere]

-   Use maximum frame size in IHDR chunk when saving APNG images [#&#8203;7821](https://github.com/python-pillow/Pillow/issues/7821)
    \[radarhere]

-   Prevent opening P TGA images without a palette [#&#8203;7797](https://github.com/python-pillow/Pillow/issues/7797)
    \[radarhere]

-   Use palette when loading ICO images [#&#8203;7798](https://github.com/python-pillow/Pillow/issues/7798)
    \[radarhere]

-   Use consistent arguments for load_read and load_seek [#&#8203;7713](https://github.com/python-pillow/Pillow/issues/7713)
    \[radarhere]

-   Turn off nullability warnings for macOS SDK [#&#8203;7827](https://github.com/python-pillow/Pillow/issues/7827)
    \[radarhere]

-   Fix shift-sign issue in Convert.c [#&#8203;7838](https://github.com/python-pillow/Pillow/issues/7838)
    \[r-barnes, radarhere]

-   Open 16-bit grayscale PNGs as I;16 [#&#8203;7849](https://github.com/python-pillow/Pillow/issues/7849)
    \[radarhere]

-   Handle truncated chunks at the end of PNG images [#&#8203;7709](https://github.com/python-pillow/Pillow/issues/7709)
    \[lajiyuan, radarhere]

-   Match mask size to pasted image size in GifImagePlugin [#&#8203;7779](https://github.com/python-pillow/Pillow/issues/7779)
    \[radarhere]

-   Release GIL while calling `WebPAnimDecoderGetNext` [#&#8203;7782](https://github.com/python-pillow/Pillow/issues/7782)
    \[evanmiller, radarhere]

-   Fixed reading FLI/FLC images with a prefix chunk [#&#8203;7804](https://github.com/python-pillow/Pillow/issues/7804)
    \[twolife]

-   Update wl-paste handling and return None for some errors in grabclipboard() on Linux [#&#8203;7745](https://github.com/python-pillow/Pillow/issues/7745)
    \[nik012003, radarhere]

-   Remove execute bit from `setup.py` [#&#8203;7760](https://github.com/python-pillow/Pillow/issues/7760)
    \[hugovk]

-   Do not support using test-image-results to upload images after test failures [#&#8203;7739](https://github.com/python-pillow/Pillow/issues/7739)
    \[radarhere]

-   Changed ImageMath.ops to be static [#&#8203;7721](https://github.com/python-pillow/Pillow/issues/7721)
    \[radarhere]

-   Fix APNG info after seeking backwards more than twice [#&#8203;7701](https://github.com/python-pillow/Pillow/issues/7701)
    \[esoma, radarhere]

-   Deprecate ImageCms constants and versions() function [#&#8203;7702](https://github.com/python-pillow/Pillow/issues/7702)
    \[nulano, radarhere]

-   Added PerspectiveTransform [#&#8203;7699](https://github.com/python-pillow/Pillow/issues/7699)
    \[radarhere]

-   Add support for reading and writing grayscale PFM images [#&#8203;7696](https://github.com/python-pillow/Pillow/issues/7696)
    \[nulano, hugovk]

-   Add LCMS2 flags to ImageCms [#&#8203;7676](https://github.com/python-pillow/Pillow/issues/7676)
    \[nulano, radarhere, hugovk]

-   Rename x64 to AMD64 in winbuild [#&#8203;7693](https://github.com/python-pillow/Pillow/issues/7693)
    \[nulano]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/76
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-04-02 15:09:37 +03:00
1ac5abd7bf Update dependency photosapi_client to v0.6.0 (#75)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| photosapi_client | minor | `==0.5.0` -> `==0.6.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Co-authored-by: profitroll <vozhd.kk@gmail.com>
Reviewed-on: #75
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-03-19 23:25:03 +02:00
bd43ee15ae Replaced find_one_and* with proper methods 2024-03-11 21:37:01 +01:00
f8ec8f6335 Bump libbot to 3.0.0 2024-01-28 21:24:01 +02:00
e345f31c56 Update dependency pykeyboard to v0.1.7 (#74)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pykeyboard](https://github.com/pystorage/pykeyboard) | patch | `==0.1.5` -> `==0.1.7` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #74
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-01-28 21:23:30 +02:00
1c53476e37 Update dependency pillow to ~=10.2.0 (#73)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pillow](https://github.com/python-pillow/Pillow) ([changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)) | minor | `~=10.1.0` -> `~=10.2.0` |

---

### Release Notes

<details>
<summary>python-pillow/Pillow (pillow)</summary>

### [`v10.2.0`](https://github.com/python-pillow/Pillow/blob/HEAD/CHANGES.rst#1020-2024-01-02)

[Compare Source](https://github.com/python-pillow/Pillow/compare/10.1.0...10.2.0)

-   Add `keep_rgb` option when saving JPEG to prevent conversion of RGB colorspace [#&#8203;7553](https://github.com/python-pillow/Pillow/issues/7553)
    \[bgilbert, radarhere]

-   Trim glyph size in ImageFont.getmask() [#&#8203;7669](https://github.com/python-pillow/Pillow/issues/7669), [#&#8203;7672](https://github.com/python-pillow/Pillow/issues/7672)
    \[radarhere, nulano]

-   Deprecate IptcImagePlugin helpers [#&#8203;7664](https://github.com/python-pillow/Pillow/issues/7664)
    \[nulano, hugovk, radarhere]

-   Allow uncompressed TIFF images to be saved in chunks [#&#8203;7650](https://github.com/python-pillow/Pillow/issues/7650)
    \[radarhere]

-   Concatenate multiple JPEG EXIF markers [#&#8203;7496](https://github.com/python-pillow/Pillow/issues/7496)
    \[radarhere]

-   Changed IPTC tile tuple to match other plugins [#&#8203;7661](https://github.com/python-pillow/Pillow/issues/7661)
    \[radarhere]

-   Do not assign new fp attribute when exiting context manager [#&#8203;7566](https://github.com/python-pillow/Pillow/issues/7566)
    \[radarhere]

-   Support arbitrary masks for uncompressed RGB DDS images [#&#8203;7589](https://github.com/python-pillow/Pillow/issues/7589)
    \[radarhere, akx]

-   Support setting ROWSPERSTRIP tag [#&#8203;7654](https://github.com/python-pillow/Pillow/issues/7654)
    \[radarhere]

-   Apply ImageFont.MAX_STRING_LENGTH to ImageFont.getmask() [#&#8203;7662](https://github.com/python-pillow/Pillow/issues/7662)
    \[radarhere]

-   Optimise `ImageColor` using `functools.lru_cache` [#&#8203;7657](https://github.com/python-pillow/Pillow/issues/7657)
    \[hugovk]

-   Restricted environment keys for ImageMath.eval() [#&#8203;7655](https://github.com/python-pillow/Pillow/issues/7655)
    \[wiredfool, radarhere]

-   Optimise `ImageMode.getmode` using `functools.lru_cache` [#&#8203;7641](https://github.com/python-pillow/Pillow/issues/7641)
    \[hugovk, radarhere]

-   Fix incorrect color blending for overlapping glyphs [#&#8203;7497](https://github.com/python-pillow/Pillow/issues/7497)
    \[ZachNagengast, nulano, radarhere]

-   Attempt memory mapping when tile args is a string [#&#8203;7565](https://github.com/python-pillow/Pillow/issues/7565)
    \[radarhere]

-   Fill identical pixels with transparency in subsequent frames when saving GIF [#&#8203;7568](https://github.com/python-pillow/Pillow/issues/7568)
    \[radarhere]

-   Corrected duration when combining multiple GIF frames into single frame [#&#8203;7521](https://github.com/python-pillow/Pillow/issues/7521)
    \[radarhere]

-   Handle disposing GIF background from outside palette [#&#8203;7515](https://github.com/python-pillow/Pillow/issues/7515)
    \[radarhere]

-   Seek past the data when skipping a PSD layer [#&#8203;7483](https://github.com/python-pillow/Pillow/issues/7483)
    \[radarhere]

-   Import plugins relative to the module [#&#8203;7576](https://github.com/python-pillow/Pillow/issues/7576)
    \[deliangyang, jaxx0n]

-   Translate encoder error codes to strings; deprecate `ImageFile.raise_oserror()` [#&#8203;7609](https://github.com/python-pillow/Pillow/issues/7609)
    \[bgilbert, radarhere]

-   Support reading BC4U and DX10 BC1 images [#&#8203;6486](https://github.com/python-pillow/Pillow/issues/6486)
    \[REDxEYE, radarhere, hugovk]

-   Optimize ImageStat.Stat.extrema [#&#8203;7593](https://github.com/python-pillow/Pillow/issues/7593)
    \[florath, radarhere]

-   Handle pathlib.Path in FreeTypeFont [#&#8203;7578](https://github.com/python-pillow/Pillow/issues/7578)
    \[radarhere, hugovk, nulano]

-   Added support for reading DX10 BC4 DDS images [#&#8203;7603](https://github.com/python-pillow/Pillow/issues/7603)
    \[sambvfx, radarhere]

-   Optimized ImageStat.Stat.count [#&#8203;7599](https://github.com/python-pillow/Pillow/issues/7599)
    \[florath]

-   Correct PDF palette size when saving [#&#8203;7555](https://github.com/python-pillow/Pillow/issues/7555)
    \[radarhere]

-   Fixed closing file pointer with olefile 0.47 [#&#8203;7594](https://github.com/python-pillow/Pillow/issues/7594)
    \[radarhere]

-   Raise ValueError when TrueType font size is not greater than zero [#&#8203;7584](https://github.com/python-pillow/Pillow/issues/7584), [#&#8203;7587](https://github.com/python-pillow/Pillow/issues/7587)
    \[akx, radarhere]

-   If absent, do not try to close fp when closing image [#&#8203;7557](https://github.com/python-pillow/Pillow/issues/7557)
    \[RaphaelVRossi, radarhere]

-   Allow configuring JPEG restart marker interval on save [#&#8203;7488](https://github.com/python-pillow/Pillow/issues/7488)
    \[bgilbert, radarhere]

-   Decrement reference count for PyObject [#&#8203;7549](https://github.com/python-pillow/Pillow/issues/7549)
    \[radarhere]

-   Implement `streamtype=1` option for tables-only JPEG encoding [#&#8203;7491](https://github.com/python-pillow/Pillow/issues/7491)
    \[bgilbert, radarhere]

-   If save_all PNG only has one frame, do not create animated image [#&#8203;7522](https://github.com/python-pillow/Pillow/issues/7522)
    \[radarhere]

-   Fixed frombytes() for images with a zero dimension [#&#8203;7493](https://github.com/python-pillow/Pillow/issues/7493)
    \[radarhere]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/73
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2024-01-02 13:12:31 +02:00
898a63012f Update dependency libbot to v2.1.0 (#71)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [libbot](https://github.com/botlibx/libbot) | minor | `==2.0.1` -> `==2.1.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #71
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-12-27 16:21:23 +02:00
854e3d2832 Update dependency aiohttp to ~=3.9.1 (#68)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [aiohttp](https://github.com/aio-libs/aiohttp) | minor | `~=3.8.4` -> `~=3.9.1` |

---

### Release Notes

<details>
<summary>aio-libs/aiohttp (aiohttp)</summary>

### [`v3.9.1`](https://github.com/aio-libs/aiohttp/blob/HEAD/CHANGES.rst#391-2023-11-26)

[Compare Source](https://github.com/aio-libs/aiohttp/compare/v3.9.0...v3.9.1)

\==================

## Bugfixes

-   Fixed importing aiohttp under PyPy on Windows.

    `#&#8203;7848 <https://github.com/aio-libs/aiohttp/issues/7848>`\_

-   Fixed async concurrency safety in websocket compressor.

    `#&#8203;7865 <https://github.com/aio-libs/aiohttp/issues/7865>`\_

-   Fixed `ClientResponse.close()` releasing the connection instead of closing.

    `#&#8203;7869 <https://github.com/aio-libs/aiohttp/issues/7869>`\_

-   Fixed a regression where connection may get closed during upgrade. -- by :user:`Dreamsorcerer`

    `#&#8203;7879 <https://github.com/aio-libs/aiohttp/issues/7879>`\_

-   Fixed messages being reported as upgraded without an Upgrade header in Python parser. -- by :user:`Dreamsorcerer`

    `#&#8203;7895 <https://github.com/aio-libs/aiohttp/issues/7895>`\_

***

### [`v3.9.0`](https://github.com/aio-libs/aiohttp/blob/HEAD/CHANGES.rst#390-2023-11-18)

[Compare Source](https://github.com/aio-libs/aiohttp/compare/v3.8.6...v3.9.0)

\==================

## Features

-   Introduced `AppKey` for static typing support of `Application` storage.
    See https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config

    `#&#8203;5864 <https://github.com/aio-libs/aiohttp/issues/5864>`\_

-   Added a graceful shutdown period which allows pending tasks to complete before the application's cleanup is called.
    The period can be adjusted with the `shutdown_timeout` parameter. -- by :user:`Dreamsorcerer`.
    See https://docs.aiohttp.org/en/latest/web_advanced.html#graceful-shutdown

    `#&#8203;7188 <https://github.com/aio-libs/aiohttp/issues/7188>`\_

-   Added `handler_cancellation <https://docs.aiohttp.org/en/stable/web_advanced.html#web-handler-cancellation>`\_ parameter to cancel web handler on client disconnection. -- by :user:`mosquito`
    This (optionally) reintroduces a feature removed in a previous release.
    Recommended for those looking for an extra level of protection against denial-of-service attacks.

    `#&#8203;7056 <https://github.com/aio-libs/aiohttp/issues/7056>`\_

-   Added support for setting response header parameters `max_line_size` and `max_field_size`.

    `#&#8203;2304 <https://github.com/aio-libs/aiohttp/issues/2304>`\_

-   Added `auto_decompress` parameter to `ClientSession.request` to override `ClientSession._auto_decompress`. -- by :user:`Daste745`

    `#&#8203;3751 <https://github.com/aio-libs/aiohttp/issues/3751>`\_

-   Changed `raise_for_status` to allow a coroutine.

    `#&#8203;3892 <https://github.com/aio-libs/aiohttp/issues/3892>`\_

-   Added client brotli compression support (optional with runtime check).

    `#&#8203;5219 <https://github.com/aio-libs/aiohttp/issues/5219>`\_

-   Added `client_max_size` to `BaseRequest.clone()` to allow overriding the request body size. -- :user:`anesabml`.

    `#&#8203;5704 <https://github.com/aio-libs/aiohttp/issues/5704>`\_

-   Added a middleware type alias `aiohttp.typedefs.Middleware`.

    `#&#8203;5898 <https://github.com/aio-libs/aiohttp/issues/5898>`\_

-   Exported `HTTPMove` which can be used to catch any redirection request
    that has a location -- :user:`dreamsorcerer`.

    `#&#8203;6594 <https://github.com/aio-libs/aiohttp/issues/6594>`\_

-   Changed the `path` parameter in `web.run_app()` to accept a `pathlib.Path` object.

    `#&#8203;6839 <https://github.com/aio-libs/aiohttp/issues/6839>`\_

-   Performance: Skipped filtering `CookieJar` when the jar is empty or all cookies have expired.

    `#&#8203;7819 <https://github.com/aio-libs/aiohttp/issues/7819>`\_

-   Performance: Only check origin if insecure scheme and there are origins to treat as secure, in `CookieJar.filter_cookies()`.

    `#&#8203;7821 <https://github.com/aio-libs/aiohttp/issues/7821>`\_

-   Performance: Used timestamp instead of `datetime` to achieve faster cookie expiration in `CookieJar`.

    `#&#8203;7824 <https://github.com/aio-libs/aiohttp/issues/7824>`\_

-   Added support for passing a custom server name parameter to HTTPS connection.

    `#&#8203;7114 <https://github.com/aio-libs/aiohttp/issues/7114>`\_

-   Added support for using Basic Auth credentials from :file:`.netrc` file when making HTTP requests with the
    :py:class:`~aiohttp.ClientSession` `trust_env` argument is set to `True`. -- by :user:`yuvipanda`.

    `#&#8203;7131 <https://github.com/aio-libs/aiohttp/issues/7131>`\_

-   Turned access log into no-op when the logger is disabled.

    `#&#8203;7240 <https://github.com/aio-libs/aiohttp/issues/7240>`\_

-   Added typing information to `RawResponseMessage`. -- by :user:`Gobot1234`

    `#&#8203;7365 <https://github.com/aio-libs/aiohttp/issues/7365>`\_

-   Removed `async-timeout` for Python 3.11+ (replaced with `asyncio.timeout()` on newer releases).

    `#&#8203;7502 <https://github.com/aio-libs/aiohttp/issues/7502>`\_

-   Added support for `brotlicffi` as an alternative to `brotli` (fixing Brotli support on PyPy).

    `#&#8203;7611 <https://github.com/aio-libs/aiohttp/issues/7611>`\_

-   Added `WebSocketResponse.get_extra_info()` to access a protocol transport's extra info.

    `#&#8203;7078 <https://github.com/aio-libs/aiohttp/issues/7078>`\_

-   Allow `link` argument to be set to None/empty in HTTP 451 exception.

    `#&#8203;7689 <https://github.com/aio-libs/aiohttp/issues/7689>`\_

## Bugfixes

-   Implemented stripping the trailing dots from fully-qualified domain names in `Host` headers and TLS context when acting as an HTTP client.
    This allows the client to connect to URLs with FQDN host name like `https://example.com./`.
    \-- by :user:`martin-sucha`.

    `#&#8203;3636 <https://github.com/aio-libs/aiohttp/issues/3636>`\_

-   Fixed client timeout not working when incoming data is always available without waiting. -- by :user:`Dreamsorcerer`.

    `#&#8203;5854 <https://github.com/aio-libs/aiohttp/issues/5854>`\_

-   Fixed `readuntil` to work with a delimiter of more than one character.

    `#&#8203;6701 <https://github.com/aio-libs/aiohttp/issues/6701>`\_

-   Added `__repr__` to `EmptyStreamReader` to avoid `AttributeError`.

    `#&#8203;6916 <https://github.com/aio-libs/aiohttp/issues/6916>`\_

-   Fixed bug when using `TCPConnector` with `ttl_dns_cache=0`.

    `#&#8203;7014 <https://github.com/aio-libs/aiohttp/issues/7014>`\_

-   Fixed response returned from expect handler being thrown away. -- by :user:`Dreamsorcerer`

    `#&#8203;7025 <https://github.com/aio-libs/aiohttp/issues/7025>`\_

-   Avoided raising `UnicodeDecodeError` in multipart and in HTTP headers parsing.

    `#&#8203;7044 <https://github.com/aio-libs/aiohttp/issues/7044>`\_

-   Changed `sock_read` timeout to start after writing has finished, avoiding read timeouts caused by an unfinished write. -- by :user:`dtrifiro`

    `#&#8203;7149 <https://github.com/aio-libs/aiohttp/issues/7149>`\_

-   Fixed missing query in tracing method URLs when using `yarl` 1.9+.

    `#&#8203;7259 <https://github.com/aio-libs/aiohttp/issues/7259>`\_

-   Changed max 32-bit timestamp to an aware datetime object, for consistency with the non-32-bit one, and to avoid a `DeprecationWarning` on Python 3.12.

    `#&#8203;7302 <https://github.com/aio-libs/aiohttp/issues/7302>`\_

-   Fixed `EmptyStreamReader.iter_chunks()` never ending. -- by :user:`mind1m`

    `#&#8203;7616 <https://github.com/aio-libs/aiohttp/issues/7616>`\_

-   Fixed a rare `RuntimeError: await wasn't used with future` exception. -- by :user:`stalkerg`

    `#&#8203;7785 <https://github.com/aio-libs/aiohttp/issues/7785>`\_

-   Fixed issue with insufficient HTTP method and version validation.

    `#&#8203;7700 <https://github.com/aio-libs/aiohttp/issues/7700>`\_

-   Added check to validate that absolute URIs have schemes.

    `#&#8203;7712 <https://github.com/aio-libs/aiohttp/issues/7712>`\_

-   Fixed unhandled exception when Python HTTP parser encounters unpaired Unicode surrogates.

    `#&#8203;7715 <https://github.com/aio-libs/aiohttp/issues/7715>`\_

-   Updated parser to disallow invalid characters in header field names and stop accepting LF as a request line separator.

    `#&#8203;7719 <https://github.com/aio-libs/aiohttp/issues/7719>`\_

-   Fixed Python HTTP parser not treating 204/304/1xx as an empty body.

    `#&#8203;7755 <https://github.com/aio-libs/aiohttp/issues/7755>`\_

-   Ensure empty body response for 1xx/204/304 per RFC 9112 sec 6.3.

    `#&#8203;7756 <https://github.com/aio-libs/aiohttp/issues/7756>`\_

-   Fixed an issue when a client request is closed before completing a chunked payload. -- by :user:`Dreamsorcerer`

    `#&#8203;7764 <https://github.com/aio-libs/aiohttp/issues/7764>`\_

-   Edge Case Handling for ResponseParser for missing reason value.

    `#&#8203;7776 <https://github.com/aio-libs/aiohttp/issues/7776>`\_

-   Fixed `ClientWebSocketResponse.close_code` being erroneously set to `None` when there are concurrent async tasks receiving data and closing the connection.

    `#&#8203;7306 <https://github.com/aio-libs/aiohttp/issues/7306>`\_

-   Added HTTP method validation.

    `#&#8203;6533 <https://github.com/aio-libs/aiohttp/issues/6533>`\_

-   Fixed arbitrary sequence types being allowed to inject values via version parameter. -- by :user:`Dreamsorcerer`

    `#&#8203;7835 <https://github.com/aio-libs/aiohttp/issues/7835>`\_

-   Performance: Fixed increase in latency with small messages from websocket compression changes.

    `#&#8203;7797 <https://github.com/aio-libs/aiohttp/issues/7797>`\_

## Improved Documentation

-   Fixed the `ClientResponse.release`'s type in the doc. Changed from `comethod` to `method`.

    `#&#8203;5836 <https://github.com/aio-libs/aiohttp/issues/5836>`\_

-   Added information on behavior of base_url parameter in `ClientSession`.

    `#&#8203;6647 <https://github.com/aio-libs/aiohttp/issues/6647>`\_

-   Fixed `ClientResponseError` docs.

    `#&#8203;6700 <https://github.com/aio-libs/aiohttp/issues/6700>`\_

-   Updated Redis code examples to follow the latest API.

    `#&#8203;6907 <https://github.com/aio-libs/aiohttp/issues/6907>`\_

-   Added a note about possibly needing to update headers when using `on_response_prepare`. -- by :user:`Dreamsorcerer`

    `#&#8203;7283 <https://github.com/aio-libs/aiohttp/issues/7283>`\_

-   Completed `trust_env` parameter description to honor `wss_proxy`, `ws_proxy` or `no_proxy` env.

    `#&#8203;7325 <https://github.com/aio-libs/aiohttp/issues/7325>`\_

-   Expanded SSL documentation with more examples (e.g. how to use certifi). -- by :user:`Dreamsorcerer`

    `#&#8203;7334 <https://github.com/aio-libs/aiohttp/issues/7334>`\_

-   Fix, update, and improve client exceptions documentation.

    `#&#8203;7733 <https://github.com/aio-libs/aiohttp/issues/7733>`\_

## Deprecations and Removals

-   Added `shutdown_timeout` parameter to `BaseRunner`, while
    deprecating `shutdown_timeout` parameter from `BaseSite`. -- by :user:`Dreamsorcerer`

    `#&#8203;7718 <https://github.com/aio-libs/aiohttp/issues/7718>`\_

-   Dropped Python 3.6 support.

    `#&#8203;6378 <https://github.com/aio-libs/aiohttp/issues/6378>`\_

-   Dropped Python 3.7 support. -- by :user:`Dreamsorcerer`

    `#&#8203;7336 <https://github.com/aio-libs/aiohttp/issues/7336>`\_

-   Removed support for abandoned `tokio` event loop. -- by :user:`Dreamsorcerer`

    `#&#8203;7281 <https://github.com/aio-libs/aiohttp/issues/7281>`\_

## Misc

-   Made `print` argument in `run_app()` optional.

    `#&#8203;3690 <https://github.com/aio-libs/aiohttp/issues/3690>`\_

-   Improved performance of `ceil_timeout` in some cases.

    `#&#8203;6316 <https://github.com/aio-libs/aiohttp/issues/6316>`\_

-   Changed importing Gunicorn to happen on-demand, decreasing import time by ~53%. -- :user:`Dreamsorcerer`

    `#&#8203;6591 <https://github.com/aio-libs/aiohttp/issues/6591>`\_

-   Improved import time by replacing `http.server` with `http.HTTPStatus`.

    `#&#8203;6903 <https://github.com/aio-libs/aiohttp/issues/6903>`\_

-   Fixed annotation of `ssl` parameter to disallow `True`. -- by :user:`Dreamsorcerer`.

    `#&#8203;7335 <https://github.com/aio-libs/aiohttp/issues/7335>`\_

***

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/68
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-11-26 21:37:23 +02:00
8d57f8e1c6 Update dependency uvloop to v0.19.0 (#53)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [uvloop](https://github.com/MagicStack/uvloop) | minor | `==0.18.0` -> `==0.19.0` |

---

### Release Notes

<details>
<summary>MagicStack/uvloop (uvloop)</summary>

### [`v0.19.0`](https://github.com/MagicStack/uvloop/releases/tag/v0.19.0)

[Compare Source](https://github.com/MagicStack/uvloop/compare/v0.18.0...v0.19.0)

# Changes

-   Drop support of Python 3.7 and update CI ([#&#8203;578](https://github.com/MagicStack/uvloop/issues/578))
    (by [@&#8203;fantix](https://github.com/fantix) in [`ee5ad26`](https://github.com/MagicStack/uvloop/commit/ee5ad26a) for [#&#8203;578](https://github.com/MagicStack/uvloop/issues/578))

# Fixes

-   Restore uvloop.new_event_loop and other missing uvloop members to typing ([#&#8203;573](https://github.com/MagicStack/uvloop/issues/573))
    (by [@&#8203;graingert](https://github.com/graingert) in [`5c500ee`](https://github.com/MagicStack/uvloop/commit/5c500ee2) for [#&#8203;573](https://github.com/MagicStack/uvloop/issues/573))

-   Fix docstring of loop.shutdown_default_executor ([#&#8203;535](https://github.com/MagicStack/uvloop/issues/535))
    (by [@&#8203;Gelbpunkt](https://github.com/Gelbpunkt) in [`919da56`](https://github.com/MagicStack/uvloop/commit/919da567) for [#&#8203;535](https://github.com/MagicStack/uvloop/issues/535))

-   Fix CI status badge ([#&#8203;522](https://github.com/MagicStack/uvloop/issues/522))
    (by [@&#8203;shuuji3](https://github.com/shuuji3) in [`0e9ff6c`](https://github.com/MagicStack/uvloop/commit/0e9ff6cd) for [#&#8203;522](https://github.com/MagicStack/uvloop/issues/522))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/53
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-10-29 19:33:07 +02:00
894840ef95 Constant change due to #29 2023-10-16 05:50:32 +00:00
a0616ff285 Improved naming 2023-10-15 18:14:22 +02:00
c4d31c955f Fixed context handler 2023-10-15 17:56:20 +02:00
6bd1234d3d This commit closes #45 2023-10-15 17:40:56 +02:00
154db69f20 Update dependency pillow to ~=10.1.0 (#48)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pillow](https://python-pillow.org) ([source](https://github.com/python-pillow/Pillow), [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)) | minor | `~=10.0.0` -> `~=10.1.0` |

---

### Release Notes

<details>
<summary>python-pillow/Pillow (pillow)</summary>

### [`v10.1.0`](https://github.com/python-pillow/Pillow/blob/HEAD/CHANGES.rst#1010-2023-10-15)

[Compare Source](https://github.com/python-pillow/Pillow/compare/10.0.1...10.1.0)

-   Added TrueType default font to allow for different sizes [#&#8203;7354](https://github.com/python-pillow/Pillow/issues/7354)
    \[radarhere]

-   Fixed invalid argument warning [#&#8203;7442](https://github.com/python-pillow/Pillow/issues/7442)
    \[radarhere]

-   Added ImageOps cover method [#&#8203;7412](https://github.com/python-pillow/Pillow/issues/7412)
    \[radarhere, hugovk]

-   Catch struct.error from truncated EXIF when reading JPEG DPI [#&#8203;7458](https://github.com/python-pillow/Pillow/issues/7458)
    \[radarhere]

-   Consider default image when selecting mode for PNG save_all [#&#8203;7437](https://github.com/python-pillow/Pillow/issues/7437)
    \[radarhere]

-   Support BGR;15, BGR;16 and BGR;24 access, unpacking and putdata [#&#8203;7303](https://github.com/python-pillow/Pillow/issues/7303)
    \[radarhere]

-   Added CMYK to RGB unpacker [#&#8203;7310](https://github.com/python-pillow/Pillow/issues/7310)
    \[radarhere]

-   Improved flexibility of XMP parsing [#&#8203;7274](https://github.com/python-pillow/Pillow/issues/7274)
    \[radarhere]

-   Support reading 8-bit YCbCr TIFF images [#&#8203;7415](https://github.com/python-pillow/Pillow/issues/7415)
    \[radarhere]

-   Allow saving I;16B images as PNG [#&#8203;7302](https://github.com/python-pillow/Pillow/issues/7302)
    \[radarhere]

-   Corrected drawing I;16 points and writing I;16 text [#&#8203;7257](https://github.com/python-pillow/Pillow/issues/7257)
    \[radarhere]

-   Set blue channel to 128 for BC5S [#&#8203;7413](https://github.com/python-pillow/Pillow/issues/7413)
    \[radarhere]

-   Increase flexibility when reading IPTC fields [#&#8203;7319](https://github.com/python-pillow/Pillow/issues/7319)
    \[radarhere]

-   Set C palette to be empty by default [#&#8203;7289](https://github.com/python-pillow/Pillow/issues/7289)
    \[radarhere]

-   Added gs_binary to control Ghostscript use on all platforms [#&#8203;7392](https://github.com/python-pillow/Pillow/issues/7392)
    \[radarhere]

-   Read bounding box information from the trailer of EPS files if specified [#&#8203;7382](https://github.com/python-pillow/Pillow/issues/7382)
    \[nopperl, radarhere]

-   Added reading 8-bit color DDS images [#&#8203;7426](https://github.com/python-pillow/Pillow/issues/7426)
    \[radarhere]

-   Added has_transparency_data [#&#8203;7420](https://github.com/python-pillow/Pillow/issues/7420)
    \[radarhere, hugovk]

-   Fixed bug when reading BC5S DDS images [#&#8203;7401](https://github.com/python-pillow/Pillow/issues/7401)
    \[radarhere]

-   Prevent TIFF orientation from being applied more than once [#&#8203;7383](https://github.com/python-pillow/Pillow/issues/7383)
    \[radarhere]

-   Use previous pixel alpha for QOI_OP_RGB [#&#8203;7357](https://github.com/python-pillow/Pillow/issues/7357)
    \[radarhere]

-   Added BC5U reading [#&#8203;7358](https://github.com/python-pillow/Pillow/issues/7358)
    \[radarhere]

-   Allow getpixel() to accept a list [#&#8203;7355](https://github.com/python-pillow/Pillow/issues/7355)
    \[radarhere, homm]

-   Allow GaussianBlur and BoxBlur to accept a sequence of x and y radii [#&#8203;7336](https://github.com/python-pillow/Pillow/issues/7336)
    \[radarhere]

-   Expand JPEG buffer size when saving optimized or progressive [#&#8203;7345](https://github.com/python-pillow/Pillow/issues/7345)
    \[radarhere]

-   Added session type check for Linux in ImageGrab.grabclipboard() [#&#8203;7332](https://github.com/python-pillow/Pillow/issues/7332)
    \[TheNooB2706, radarhere, hugovk]

-   Allow "loop=None" when saving GIF images [#&#8203;7329](https://github.com/python-pillow/Pillow/issues/7329)
    \[radarhere]

-   Fixed transparency when saving P mode images to PDF [#&#8203;7323](https://github.com/python-pillow/Pillow/issues/7323)
    \[radarhere]

-   Added saving LA images as PDFs [#&#8203;7299](https://github.com/python-pillow/Pillow/issues/7299)
    \[radarhere]

-   Set SMaskInData to 1 for PDFs with alpha [#&#8203;7316](https://github.com/python-pillow/Pillow/issues/7316), [#&#8203;7317](https://github.com/python-pillow/Pillow/issues/7317)
    \[radarhere]

-   Changed Image mode property to be read-only by default [#&#8203;7307](https://github.com/python-pillow/Pillow/issues/7307)
    \[radarhere]

-   Silence exceptions in *repr_jpeg* and *repr_png* [#&#8203;7266](https://github.com/python-pillow/Pillow/issues/7266)
    \[mtreinish, radarhere]

-   Do not use transparency when saving GIF if it has been removed when normalizing mode [#&#8203;7284](https://github.com/python-pillow/Pillow/issues/7284)
    \[radarhere]

-   Fix missing symbols when libtiff depends on libjpeg [#&#8203;7270](https://github.com/python-pillow/Pillow/issues/7270)
    \[heitbaum]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/48
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-10-15 17:47:05 +03:00
e719da7750 Update dependency uvloop to v0.18.0 (#46)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [uvloop](https://github.com/MagicStack/uvloop) | minor | `==0.17.0` -> `==0.18.0` |

---

### Release Notes

<details>
<summary>MagicStack/uvloop (uvloop)</summary>

### [`v0.18.0`](https://github.com/MagicStack/uvloop/releases/tag/v0.18.0)

[Compare Source](https://github.com/MagicStack/uvloop/compare/v0.17.0...v0.18.0)

# Fixes

-   CI fixes ([#&#8203;520](https://github.com/MagicStack/uvloop/issues/520), [#&#8203;553](https://github.com/MagicStack/uvloop/issues/553))
    (by [@&#8203;altendky](https://github.com/altendky) in [`7783f1c`](https://github.com/MagicStack/uvloop/commit/7783f1c5), [@&#8203;dulmandakh](https://github.com/dulmandakh) in [`1dd40f1`](https://github.com/MagicStack/uvloop/commit/1dd40f17))

-   Make extract_stack resilient to lacking frames. ([#&#8203;563](https://github.com/MagicStack/uvloop/issues/563))
    (by [@&#8203;jhance](https://github.com/jhance) in [`0687643`](https://github.com/MagicStack/uvloop/commit/06876434) for [#&#8203;563](https://github.com/MagicStack/uvloop/issues/563))

-   Port uvloop to Python 3.12 ([#&#8203;570](https://github.com/MagicStack/uvloop/issues/570))
    (by [@&#8203;1st1](https://github.com/1st1), [@&#8203;fantix](https://github.com/fantix) in [`9f82bd7`](https://github.com/MagicStack/uvloop/commit/9f82bd74) for [#&#8203;569](https://github.com/MagicStack/uvloop/issues/569))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/46
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-10-15 17:46:55 +03:00
1d88076285 exp is now exc 2023-08-16 13:27:23 +02:00
5e8506cc12 Attempt to work around #40 2023-08-16 13:20:25 +02:00
235fa37252 Small config reading fix 2023-08-14 15:20:48 +02:00
176f5d35c3 Dependencies cleanup 2023-08-14 14:58:11 +02:00
cd26990b7e Migrate to async_pymongo 2023-08-14 14:52:02 +02:00
5b56919b80 Update dependency libbot to v2.0.1 (#39)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| libbot | patch | `==2.0.0` -> `==2.0.1` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi4zNS4wIiwidXBkYXRlZEluVmVyIjoiMzYuMzUuMCIsInRhcmdldEJyYW5jaCI6ImRldiJ9-->

Reviewed-on: #39
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-08-11 11:31:19 +03:00
0e9bed1277 Update dependency libbot to v2 (#38)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| libbot | major | `==0.2.2` -> `==2.0.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Reviewed-on: #38
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-08-07 13:07:58 +03:00
fb37da4195 Bump libbot to 0.2.2 2023-08-06 22:20:27 +02:00
065f704923 Update dependency libbot to v1.9 (#35)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| libbot | minor | `==1.8` -> `==1.9` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Co-authored-by: Renovate <renovate@git.end-play.xyz>
Reviewed-on: #35
Co-authored-by: Renovate <renovate@noreply.localhost>
Co-committed-by: Renovate <renovate@noreply.localhost>
2023-07-26 15:28:42 +03:00
a45f6b620f Update dependency black to ~=23.7.0 (#34)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [black](https://github.com/psf/black) ([changelog](https://github.com/psf/black/blob/main/CHANGES.md)) | minor | `~=23.3.0` -> `~=23.7.0` |

---

### Release Notes

<details>
<summary>psf/black</summary>

### [`v23.7.0`](https://github.com/psf/black/blob/HEAD/CHANGES.md#&#8203;2370)

[Compare Source](https://github.com/psf/black/compare/23.3.0...23.7.0)

##### Highlights

-   Runtime support for Python 3.7 has been removed. Formatting 3.7 code will still be
    supported until further notice ([#&#8203;3765](https://github.com/psf/black/issues/3765))

##### Stable style

-   Fix a bug where an illegal trailing comma was added to return type annotations using
    PEP 604 unions ([#&#8203;3735](https://github.com/psf/black/issues/3735))
-   Fix several bugs and crashes where comments in stub files were removed or mishandled
    under some circumstances ([#&#8203;3745](https://github.com/psf/black/issues/3745))
-   Fix a crash with multi-line magic comments like `type: ignore` within parentheses
    ([#&#8203;3740](https://github.com/psf/black/issues/3740))
-   Fix error in AST validation when *Black* removes trailing whitespace in a type comment
    ([#&#8203;3773](https://github.com/psf/black/issues/3773))

##### Preview style

-   Implicitly concatenated strings used as function args are no longer wrapped inside
    parentheses ([#&#8203;3640](https://github.com/psf/black/issues/3640))
-   Remove blank lines between a class definition and its docstring ([#&#8203;3692](https://github.com/psf/black/issues/3692))

##### Configuration

-   The `--workers` argument to *Black* can now be specified via the `BLACK_NUM_WORKERS`
    environment variable ([#&#8203;3743](https://github.com/psf/black/issues/3743))
-   `.pytest_cache`, `.ruff_cache` and `.vscode` are now excluded by default ([#&#8203;3691](https://github.com/psf/black/issues/3691))
-   Fix *Black* not honouring `pyproject.toml` settings when running `--stdin-filename`
    and the `pyproject.toml` found isn't in the current working directory ([#&#8203;3719](https://github.com/psf/black/issues/3719))
-   *Black* will now error if `exclude` and `extend-exclude` have invalid data types in
    `pyproject.toml`, instead of silently doing the wrong thing ([#&#8203;3764](https://github.com/psf/black/issues/3764))

##### Packaging

-   Upgrade mypyc from 0.991 to 1.3 ([#&#8203;3697](https://github.com/psf/black/issues/3697))
-   Remove patching of Click that mitigated errors on Python 3.6 with `LANG=C` ([#&#8203;3768](https://github.com/psf/black/issues/3768))

##### Parser

-   Add support for the new PEP 695 syntax in Python 3.12 ([#&#8203;3703](https://github.com/psf/black/issues/3703))

##### Performance

-   Speed up *Black* significantly when the cache is full ([#&#8203;3751](https://github.com/psf/black/issues/3751))
-   Avoid importing `IPython` in a case where we wouldn't need it ([#&#8203;3748](https://github.com/psf/black/issues/3748))

##### Output

-   Use aware UTC datetimes internally, avoids deprecation warning on Python 3.12 ([#&#8203;3728](https://github.com/psf/black/issues/3728))
-   Change verbose logging to exactly mirror *Black*'s logic for source discovery ([#&#8203;3749](https://github.com/psf/black/issues/3749))

##### *Blackd*

-   The `blackd` argument parser now shows the default values for options in their help
    text ([#&#8203;3712](https://github.com/psf/black/issues/3712))

##### Integrations

-   Black is now tested with
    [`PYTHONWARNDEFAULTENCODING = 1`](https://docs.python.org/3/library/io.html#io-encoding-warning)
    ([#&#8203;3763](https://github.com/psf/black/issues/3763))
-   Update GitHub Action to display black output in the job summary ([#&#8203;3688](https://github.com/psf/black/issues/3688))

##### Documentation

-   Add a CITATION.cff file to the root of the repository, containing metadata on how to
    cite this software ([#&#8203;3723](https://github.com/psf/black/issues/3723))
-   Update the *classes* and *exceptions* documentation in Developer reference to match
    the latest code base ([#&#8203;3755](https://github.com/psf/black/issues/3755))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Co-authored-by: Renovate <renovate@git.end-play.xyz>
Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/34
Co-authored-by: Renovate <renovate@noreply.localhost>
Co-committed-by: Renovate <renovate@noreply.localhost>
2023-07-11 11:20:23 +03:00
fe8b562a7e master (#33)
Reviewed-on: #33
2023-07-06 17:35:12 +03:00
63164d2169 Merge branch 'master' into dev 2023-07-06 15:21:15 +03:00
be288776d9 max_concurrent_transmissions reduced to 1 2023-07-06 14:19:56 +02:00
0c73c51936 unauthorized_client will be created when needed 2023-07-03 14:42:55 +02:00
82467518da Merge branch 'master' of https://git.end-play.xyz/profitroll/TelegramPoster 2023-07-03 12:47:29 +02:00
c0085b8000 Merge branch 'dev' 2023-07-03 12:43:43 +02:00
a7e79eb254 Updated README 2023-07-03 12:34:55 +02:00
dc774262f8 Locale for console is gone for good 2023-07-03 11:42:28 +02:00
987f642578 CLI is back and updated 2023-07-03 11:27:15 +02:00
f7df4d8ddc Bump libbot to 1.8 2023-07-03 11:04:39 +02:00
c2619a1370 Update dependency pillow to v10 (#29)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pillow](https://python-pillow.org) ([source](https://github.com/python-pillow/Pillow), [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)) | major | `~=9.4.0` -> `~=10.0.0` |

---

### Release Notes

<details>
<summary>python-pillow/Pillow</summary>

### [`v10.0.0`](https://github.com/python-pillow/Pillow/blob/HEAD/CHANGES.rst#&#8203;1000-2023-07-01)

[Compare Source](https://github.com/python-pillow/Pillow/compare/9.5.0...10.0.0)

-   Fixed deallocating mask images [#&#8203;7246](https://github.com/python-pillow/Pillow/issues/7246)
    \[radarhere]

-   Added ImageFont.MAX_STRING_LENGTH [#&#8203;7244](https://github.com/python-pillow/Pillow/issues/7244)
    \[radarhere, hugovk]

-   Fix Windows build with pyproject.toml [#&#8203;7230](https://github.com/python-pillow/Pillow/issues/7230)
    \[hugovk, nulano, radarhere]

-   Do not close provided file handles with libtiff [#&#8203;7199](https://github.com/python-pillow/Pillow/issues/7199)
    \[radarhere]

-   Convert to HSV if mode is HSV in getcolor() [#&#8203;7226](https://github.com/python-pillow/Pillow/issues/7226)
    \[radarhere]

-   Added alpha_only argument to getbbox() [#&#8203;7123](https://github.com/python-pillow/Pillow/issues/7123)
    \[radarhere. hugovk]

-   Prioritise speed in *repr_png* [#&#8203;7242](https://github.com/python-pillow/Pillow/issues/7242)
    \[radarhere]

-   Do not use CFFI access by default on PyPy [#&#8203;7236](https://github.com/python-pillow/Pillow/issues/7236)
    \[radarhere]

-   Limit size even if one dimension is zero in decompression bomb check [#&#8203;7235](https://github.com/python-pillow/Pillow/issues/7235)
    \[radarhere]

-   Use --config-settings instead of deprecated --global-option [#&#8203;7171](https://github.com/python-pillow/Pillow/issues/7171)
    \[radarhere]

-   Better C integer definitions [#&#8203;6645](https://github.com/python-pillow/Pillow/issues/6645)
    \[Yay295, hugovk]

-   Fixed finding dependencies on Cygwin [#&#8203;7175](https://github.com/python-pillow/Pillow/issues/7175)
    \[radarhere]

-   Changed grabclipboard() to use PNG instead of JPG compression on macOS [#&#8203;7219](https://github.com/python-pillow/Pillow/issues/7219)
    \[abey79, radarhere]

-   Added in_place argument to ImageOps.exif_transpose() [#&#8203;7092](https://github.com/python-pillow/Pillow/issues/7092)
    \[radarhere]

-   Fixed calling putpalette() on L and LA images before load() [#&#8203;7187](https://github.com/python-pillow/Pillow/issues/7187)
    \[radarhere]

-   Fixed saving TIFF multiframe images with LONG8 tag types [#&#8203;7078](https://github.com/python-pillow/Pillow/issues/7078)
    \[radarhere]

-   Fixed combining single duration across duplicate APNG frames [#&#8203;7146](https://github.com/python-pillow/Pillow/issues/7146)
    \[radarhere]

-   Remove temporary file when error is raised [#&#8203;7148](https://github.com/python-pillow/Pillow/issues/7148)
    \[radarhere]

-   Do not use temporary file when grabbing clipboard on Linux [#&#8203;7200](https://github.com/python-pillow/Pillow/issues/7200)
    \[radarhere]

-   If the clipboard fails to open on Windows, wait and try again [#&#8203;7141](https://github.com/python-pillow/Pillow/issues/7141)
    \[radarhere]

-   Fixed saving multiple 1 mode frames to GIF [#&#8203;7181](https://github.com/python-pillow/Pillow/issues/7181)
    \[radarhere]

-   Replaced absolute PIL import with relative import [#&#8203;7173](https://github.com/python-pillow/Pillow/issues/7173)
    \[radarhere]

-   Replaced deprecated Py_FileSystemDefaultEncoding for Python >= 3.12 [#&#8203;7192](https://github.com/python-pillow/Pillow/issues/7192)
    \[radarhere]

-   Improved wl-paste mimetype handling in ImageGrab [#&#8203;7094](https://github.com/python-pillow/Pillow/issues/7094)
    \[rrcgat, radarhere]

-   Added *repr_jpeg*() for IPython display_jpeg [#&#8203;7135](https://github.com/python-pillow/Pillow/issues/7135)
    \[n3011, radarhere, nulano]

-   Use "/sbin/ldconfig" if ldconfig is not found [#&#8203;7068](https://github.com/python-pillow/Pillow/issues/7068)
    \[radarhere]

-   Prefer screenshots using XCB over gnome-screenshot [#&#8203;7143](https://github.com/python-pillow/Pillow/issues/7143)
    \[nulano, radarhere]

-   Fixed joined corners for ImageDraw rounded_rectangle() odd dimensions [#&#8203;7151](https://github.com/python-pillow/Pillow/issues/7151)
    \[radarhere]

-   Support reading signed 8-bit TIFF images [#&#8203;7111](https://github.com/python-pillow/Pillow/issues/7111)
    \[radarhere]

-   Added width argument to ImageDraw regular_polygon [#&#8203;7132](https://github.com/python-pillow/Pillow/issues/7132)
    \[radarhere]

-   Support I mode for ImageFilter.BuiltinFilter [#&#8203;7108](https://github.com/python-pillow/Pillow/issues/7108)
    \[radarhere]

-   Raise error from stderr of Linux ImageGrab.grabclipboard() command [#&#8203;7112](https://github.com/python-pillow/Pillow/issues/7112)
    \[radarhere]

-   Added unpacker from I;16B to I;16 [#&#8203;7125](https://github.com/python-pillow/Pillow/issues/7125)
    \[radarhere]

-   Support float font sizes [#&#8203;7107](https://github.com/python-pillow/Pillow/issues/7107)
    \[radarhere]

-   Use later value for duplicate xref entries in PdfParser [#&#8203;7102](https://github.com/python-pillow/Pillow/issues/7102)
    \[radarhere]

-   Load before getting size in **getstate** [#&#8203;7105](https://github.com/python-pillow/Pillow/issues/7105)
    \[bigcat88, radarhere]

-   Fixed type handling for include and lib directories [#&#8203;7069](https://github.com/python-pillow/Pillow/issues/7069)
    \[adisbladis, radarhere]

-   Remove deprecations for Pillow 10.0.0 [#&#8203;7059](https://github.com/python-pillow/Pillow/issues/7059), [#&#8203;7080](https://github.com/python-pillow/Pillow/issues/7080)
    \[hugovk, radarhere]

-   Drop support for soon-EOL Python 3.7 [#&#8203;7058](https://github.com/python-pillow/Pillow/issues/7058)
    \[hugovk, radarhere]

### [`v9.5.0`](https://github.com/python-pillow/Pillow/blob/HEAD/CHANGES.rst#&#8203;950-2023-04-01)

[Compare Source](https://github.com/python-pillow/Pillow/compare/9.4.0...9.5.0)

-   Added ImageSourceData to TAGS_V2 [#&#8203;7053](https://github.com/python-pillow/Pillow/issues/7053)
    \[radarhere]

-   Clear PPM half token after use [#&#8203;7052](https://github.com/python-pillow/Pillow/issues/7052)
    \[radarhere]

-   Removed absolute path to ldconfig [#&#8203;7044](https://github.com/python-pillow/Pillow/issues/7044)
    \[radarhere]

-   Support custom comments and PLT markers when saving JPEG2000 images [#&#8203;6903](https://github.com/python-pillow/Pillow/issues/6903)
    \[joshware, radarhere, hugovk]

-   Load before getting size in **array_interface** [#&#8203;7034](https://github.com/python-pillow/Pillow/issues/7034)
    \[radarhere]

-   Support creating BGR;15, BGR;16 and BGR;24 images, but drop support for BGR;32 [#&#8203;7010](https://github.com/python-pillow/Pillow/issues/7010)
    \[radarhere]

-   Consider transparency when applying APNG blend mask [#&#8203;7018](https://github.com/python-pillow/Pillow/issues/7018)
    \[radarhere]

-   Round duration when saving animated WebP images [#&#8203;6996](https://github.com/python-pillow/Pillow/issues/6996)
    \[radarhere]

-   Added reading of JPEG2000 comments [#&#8203;6909](https://github.com/python-pillow/Pillow/issues/6909)
    \[radarhere]

-   Decrement reference count [#&#8203;7003](https://github.com/python-pillow/Pillow/issues/7003)
    \[radarhere, nulano]

-   Allow libtiff_support_custom_tags to be missing [#&#8203;7020](https://github.com/python-pillow/Pillow/issues/7020)
    \[radarhere]

-   Improved I;16N support [#&#8203;6834](https://github.com/python-pillow/Pillow/issues/6834)
    \[radarhere]

-   Added QOI reading [#&#8203;6852](https://github.com/python-pillow/Pillow/issues/6852)
    \[radarhere, hugovk]

-   Added saving RGBA images as PDFs [#&#8203;6925](https://github.com/python-pillow/Pillow/issues/6925)
    \[radarhere]

-   Do not raise an error if os.environ does not contain PATH [#&#8203;6935](https://github.com/python-pillow/Pillow/issues/6935)
    \[radarhere, hugovk]

-   Close OleFileIO instance when closing or exiting FPX or MIC [#&#8203;7005](https://github.com/python-pillow/Pillow/issues/7005)
    \[radarhere]

-   Added **int** to IFDRational for Python >= 3.11 [#&#8203;6998](https://github.com/python-pillow/Pillow/issues/6998)
    \[radarhere]

-   Added memoryview support to Dib.frombytes() [#&#8203;6988](https://github.com/python-pillow/Pillow/issues/6988)
    \[radarhere, nulano]

-   Close file pointer copy in the libtiff encoder if still open [#&#8203;6986](https://github.com/python-pillow/Pillow/issues/6986)
    \[fcarron, radarhere]

-   Raise an error if ImageDraw co-ordinates are incorrectly ordered [#&#8203;6978](https://github.com/python-pillow/Pillow/issues/6978)
    \[radarhere]

-   Added "corners" argument to ImageDraw rounded_rectangle() [#&#8203;6954](https://github.com/python-pillow/Pillow/issues/6954)
    \[radarhere]

-   Added memoryview support to frombytes() [#&#8203;6974](https://github.com/python-pillow/Pillow/issues/6974)
    \[radarhere]

-   Allow comments in FITS images [#&#8203;6973](https://github.com/python-pillow/Pillow/issues/6973)
    \[radarhere]

-   Support saving PDF with different X and Y resolutions [#&#8203;6961](https://github.com/python-pillow/Pillow/issues/6961)
    \[jvanderneutstulen, radarhere, hugovk]

-   Fixed writing int as UNDEFINED tag [#&#8203;6950](https://github.com/python-pillow/Pillow/issues/6950)
    \[radarhere]

-   Raise an error if EXIF data is too long when saving JPEG [#&#8203;6939](https://github.com/python-pillow/Pillow/issues/6939)
    \[radarhere]

-   Handle more than one directory returned by pkg-config [#&#8203;6896](https://github.com/python-pillow/Pillow/issues/6896)
    \[sebastic, radarhere]

-   Do not retry past formats when loading all formats for the first time [#&#8203;6902](https://github.com/python-pillow/Pillow/issues/6902)
    \[radarhere]

-   Do not retry specified formats if they failed when opening [#&#8203;6893](https://github.com/python-pillow/Pillow/issues/6893)
    \[radarhere]

-   Do not unintentionally load TIFF format at first [#&#8203;6892](https://github.com/python-pillow/Pillow/issues/6892)
    \[radarhere]

-   Stop reading when EPS line becomes too long [#&#8203;6897](https://github.com/python-pillow/Pillow/issues/6897)
    \[radarhere]

-   Allow writing IFDRational to BYTE tag [#&#8203;6890](https://github.com/python-pillow/Pillow/issues/6890)
    \[radarhere]

-   Raise ValueError for BoxBlur filter with negative radius [#&#8203;6874](https://github.com/python-pillow/Pillow/issues/6874)
    \[hugovk, radarhere]

-   Support arbitrary number of loaded modules on Windows [#&#8203;6761](https://github.com/python-pillow/Pillow/issues/6761)
    \[javidcf, radarhere, nulano]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Co-authored-by: Renovate <renovate@git.end-play.xyz>
Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/29
Co-authored-by: Renovate <renovate@noreply.localhost>
Co-committed-by: Renovate <renovate@noreply.localhost>
2023-07-03 11:37:48 +03:00
15b272ae35 max_concurrent_transmissions is now 3 by default 2023-07-01 15:43:07 +02:00
28b5449f2a Improved /shutdown 2023-06-30 11:34:15 +02:00
bfec702bef Config cleanup 2023-06-30 11:34:06 +02:00
fd0c4c0545 Bump libbot to 1.7 2023-06-30 11:33:34 +02:00
11dbf3239d Removed deprecated collection 2023-06-28 10:52:00 +02:00
3d87f035e7 Added /language for owner 2023-06-28 10:48:14 +02:00
d8245934e2 Fixed wrong db record 2023-06-28 10:45:23 +02:00
420a4cb7eb Fixed locale strings and commands 2023-06-28 10:43:13 +02:00
b747dde664 Added missing requirement 2023-06-28 10:39:39 +02:00
10c60ae932 WIP: /language system 2023-06-28 10:37:18 +02:00
6f8b560acc WIP: New User system 2023-06-28 10:15:45 +02:00
93f3439a11 Fixed response type check 2023-06-28 08:59:40 +02:00
9e0a815062 Fixed markup 2023-06-28 08:57:09 +02:00
51da210817 Small fix 2023-06-28 08:56:21 +02:00
e06cb4b377 /remove command fixed 2023-06-28 08:53:15 +02:00
97b3aa1505 Starting scripts and README were updated 2023-06-28 08:22:18 +02:00
5adb004a2a API usage overhaul (#27)
* `/report` command added
* Updated to libbot 1.5
* Moved to [PhotosAPI_Client](https://git.end-play.xyz/profitroll/PhotosAPI_Client) v0.5.0 from using self-made API client
* Video support (almost stable)
* Bug fixes and improvements

Co-authored-by: profitroll <vozhd.kk@gmail.com>
Reviewed-on: #27
2023-06-28 00:57:30 +03:00
f003638128 Update 'requirements.txt' 2023-06-28 00:57:13 +03:00
e9e68cb6b3 Update dependency pymongo to v4.4.0 (#25)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pymongo](https://github.com/mongodb/mongo-python-driver) | minor | `==4.3.3` -> `==4.4.0` |

---

### Release Notes

<details>
<summary>mongodb/mongo-python-driver</summary>

### [`v4.4.0`](https://github.com/mongodb/mongo-python-driver/releases/tag/4.4.0): PyMongo 4.4.0

[Compare Source](https://github.com/mongodb/mongo-python-driver/compare/4.3.3...4.4.0)

Release notes: https://www.mongodb.com/community/forums/t/pymongo-4-4-released/232211

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Co-authored-by: Renovate <renovate@git.end-play.xyz>
Reviewed-on: #25
Co-authored-by: Renovate <renovate@noreply.localhost>
Co-committed-by: Renovate <renovate@noreply.localhost>
2023-06-21 22:45:23 +03:00
bd62149a2c Update dependency ujson to v5.8.0 (#24)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ujson](https://github.com/ultrajson/ultrajson) | minor | `==5.7.0` -> `==5.8.0` |

---

### Release Notes

<details>
<summary>ultrajson/ultrajson</summary>

### [`v5.8.0`](https://github.com/ultrajson/ultrajson/releases/tag/5.8.0)

[Compare Source](https://github.com/ultrajson/ultrajson/compare/5.7.0...5.8.0)

#### Added

-   Build wheel for Python 3.12 beta (built against 3.12.0b2) ([#&#8203;594](https://github.com/ultrajson/ultrajson/issues/594)) [@&#8203;hugovk](https://github.com/hugovk)

#### Changed

-   Drop support for Python 3.7 ([#&#8203;595](https://github.com/ultrajson/ultrajson/issues/595)) [@&#8203;hugovk](https://github.com/hugovk)

#### Fixed

-   Include BSD-3-Clause and TCL license text ([#&#8203;584](https://github.com/ultrajson/ultrajson/issues/584)) [@&#8203;musicinmybrain](https://github.com/musicinmybrain)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Co-authored-by: Renovate <renovate@git.end-play.xyz>
Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/24
Co-authored-by: Renovate <renovate@noreply.localhost>
Co-committed-by: Renovate <renovate@noreply.localhost>
2023-06-11 12:29:02 +03:00
e5b2584d4c Bug fixes and Pyrogram bump (#23)
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-authored-by: profitroll <vozhd.kk@gmail.com>
Reviewed-on: #23
2023-05-16 15:45:23 +03:00
76fc4981cc Merge branch 'master' into dev 2023-05-16 15:45:11 +03:00
337a7b28aa Fixed coroutines not being awaited 2023-05-16 12:30:56 +02:00
93804345df Update dependency pyrogram to v2.0.106 (#22)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pyrogram](https://github.com/pyrogram) ([source](https://github.com/pyrogram/pyrogram)) | patch | `==2.0.104` -> `==2.0.106` |

---

### Release Notes

<details>
<summary>pyrogram/pyrogram</summary>

### [`v2.0.106`](https://github.com/pyrogram/pyrogram/compare/v2.0.105...v2.0.106)

[Compare Source](https://github.com/pyrogram/pyrogram/compare/v2.0.105...v2.0.106)

### [`v2.0.105`](https://github.com/pyrogram/pyrogram/compare/v2.0.104...v2.0.105)

[Compare Source](https://github.com/pyrogram/pyrogram/compare/v2.0.104...v2.0.105)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Reviewed-on: #22
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-05-08 11:51:15 +03:00
f5b3335af0 master (#21)
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-authored-by: profitroll <vozhd.kk@gmail.com>
Reviewed-on: #21
2023-04-24 14:38:03 +03:00
fc8f49b487 Commented random_pic_response output 2023-04-24 13:36:58 +02:00
c6cba6de2f Merge branch 'dev' 2023-04-24 13:33:33 +02:00
adef3b3afc Changed versioning prefix in code 2023-04-24 12:53:22 +02:00
853c3c7cea dev (#19)
* Config file changes
* Commands `/remove`, `/import` and `/export`

Co-authored-by: profitroll <vozhd.kk@gmail.com>
Co-authored-by: Profitroll <47523801+profitrollgame@users.noreply.github.com>
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Reviewed-on: #19
2023-04-24 13:48:22 +03:00
fe60b3f8a5 Changed album config path 2023-04-24 12:44:10 +02:00
aa82a8f382 Fixed SyntaxError 2023-04-24 12:40:06 +02:00
826e031a39 Update dependency pyrogram to v2.0.104 (#18)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pyrogram](https://github.com/pyrogram) ([source](https://github.com/pyrogram/pyrogram)) | patch | `==2.0.102` -> `==2.0.104` |

---

### Release Notes

<details>
<summary>pyrogram/pyrogram</summary>

### [`v2.0.104`](https://github.com/pyrogram/pyrogram/compare/v2.0.103...v2.0.104)

[Compare Source](https://github.com/pyrogram/pyrogram/compare/v2.0.103...v2.0.104)

### [`v2.0.103`](https://github.com/pyrogram/pyrogram/compare/v2.0.102...v2.0.103)

[Compare Source](https://github.com/pyrogram/pyrogram/compare/v2.0.102...v2.0.103)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Reviewed-on: #18
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-04-24 13:36:39 +03:00
0681338970 Update dependency psutil to v5.9.5 (#17)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [psutil](https://github.com/giampaolo/psutil) | patch | `==5.9.4` -> `==5.9.5` |

---

### Release Notes

<details>
<summary>giampaolo/psutil</summary>

### [`v5.9.5`](https://github.com/giampaolo/psutil/blob/HEAD/HISTORY.rst#&#8203;595)

[Compare Source](https://github.com/giampaolo/psutil/compare/release-5.9.4...release-5.9.5)

\=====

2023-04-17

**Enhancements**

-   2196\_: in case of exception, display a cleaner error traceback by hiding the
    `KeyError` bit deriving from a missed cache hit.
-   2217\_: print the full traceback when a `DeprecationWarning` or `UserWarning`
    is raised.
-   2230\_, \[OpenBSD]: `psutil.net_connections`\_ implementation was rewritten from
    scratch:
    -   We're now able to retrieve the path of AF_UNIX sockets (before it was an
        empty string)
    -   The function is faster since it no longer iterates over all processes.
    -   No longer produces duplicate connection entries.
-   2238\_: there are cases where `Process.cwd()`\_ cannot be determined
    (e.g. directory no longer exists), in which case we returned either `None`
    or an empty string. This was consolidated and we now return `""` on all
    platforms.
-   2239\_, \[UNIX]: if process is a zombie, and we can only determine part of the
    its truncated `Process.name()`\_ (15 chars), don't fail with `ZombieProcess`\_
    when we try to guess the full name from the `Process.cmdline()`\_. Just
    return the truncated name.
-   2240\_, \[NetBSD], \[OpenBSD]: add CI testing on every commit for NetBSD and
    OpenBSD platforms (python 3 only).

**Bug fixes**

-   1043\_, \[OpenBSD] `psutil.net_connections`\_ returns duplicate entries.
-   1915\_, \[Linux]: on certain kernels, `"MemAvailable"` field from
    `/proc/meminfo` returns `0` (possibly a kernel bug), in which case we
    calculate an approximation for `available` memory which matches "free"
    CLI utility.
-   2164\_, \[Linux]: compilation fails on kernels < 2.6.27 (e.g. CentOS 5).
-   2186\_, \[FreeBSD]: compilation fails with Clang 15.  (patch by Po-Chuan Hsieh)
-   2191\_, \[Linux]: `disk_partitions()`*: do not unnecessarily read
    /proc/filesystems and raise `AccessDenied`* unless user specified `all=False`
    argument.
-   2216\_, \[Windows]: fix tests when running in a virtual environment (patch by
    Matthieu Darbois)
-   2225\_, \[POSIX]: `users()`\_ loses precision for `started` attribute (off by
    1 minute).
-   2229\_, \[OpenBSD]: unable to properly recognize zombie processes.
    `NoSuchProcess`\_ may be raised instead of `ZombieProcess`\_.
-   2231\_, \[NetBSD]: *available*  `virtual_memory()`\_ is higher than *total*.
-   2234\_, \[NetBSD]: `virtual_memory()`\_ metrics are wrong: *available* and
    *used* are too high. We now match values shown by *htop* CLI utility.
-   2236\_, \[NetBSD]: `Process.num_threads()`\_ and `Process.threads()`\_ return
    threads that are already terminated.
-   2237\_, \[OpenBSD], \[NetBSD]: `Process.cwd()`\_ may raise `FileNotFoundError`
    if cwd no longer exists. Return an empty string instead.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Reviewed-on: #17
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-04-24 13:36:12 +03:00
7c8e07bbc9 Made some dependencies strict 2023-04-24 12:13:14 +02:00
ad653146e1 Update dependency pillow to ~=9.5.0 (#15)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [pillow](https://python-pillow.org) ([source](https://github.com/python-pillow/Pillow), [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)) | minor | `~=9.4.0` -> `~=9.5.0` |

---

### Release Notes

<details>
<summary>python-pillow/Pillow</summary>

### [`v9.5.0`](https://github.com/python-pillow/Pillow/blob/HEAD/CHANGES.rst#&#8203;950-2023-04-01)

[Compare Source](https://github.com/python-pillow/Pillow/compare/9.4.0...9.5.0)

-   Added ImageSourceData to TAGS_V2 [#&#8203;7053](https://github.com/python-pillow/Pillow/issues/7053)
    \[radarhere]

-   Clear PPM half token after use [#&#8203;7052](https://github.com/python-pillow/Pillow/issues/7052)
    \[radarhere]

-   Removed absolute path to ldconfig [#&#8203;7044](https://github.com/python-pillow/Pillow/issues/7044)
    \[radarhere]

-   Support custom comments and PLT markers when saving JPEG2000 images [#&#8203;6903](https://github.com/python-pillow/Pillow/issues/6903)
    \[joshware, radarhere, hugovk]

-   Load before getting size in **array_interface** [#&#8203;7034](https://github.com/python-pillow/Pillow/issues/7034)
    \[radarhere]

-   Support creating BGR;15, BGR;16 and BGR;24 images, but drop support for BGR;32 [#&#8203;7010](https://github.com/python-pillow/Pillow/issues/7010)
    \[radarhere]

-   Consider transparency when applying APNG blend mask [#&#8203;7018](https://github.com/python-pillow/Pillow/issues/7018)
    \[radarhere]

-   Round duration when saving animated WebP images [#&#8203;6996](https://github.com/python-pillow/Pillow/issues/6996)
    \[radarhere]

-   Added reading of JPEG2000 comments [#&#8203;6909](https://github.com/python-pillow/Pillow/issues/6909)
    \[radarhere]

-   Decrement reference count [#&#8203;7003](https://github.com/python-pillow/Pillow/issues/7003)
    \[radarhere, nulano]

-   Allow libtiff_support_custom_tags to be missing [#&#8203;7020](https://github.com/python-pillow/Pillow/issues/7020)
    \[radarhere]

-   Improved I;16N support [#&#8203;6834](https://github.com/python-pillow/Pillow/issues/6834)
    \[radarhere]

-   Added QOI reading [#&#8203;6852](https://github.com/python-pillow/Pillow/issues/6852)
    \[radarhere, hugovk]

-   Added saving RGBA images as PDFs [#&#8203;6925](https://github.com/python-pillow/Pillow/issues/6925)
    \[radarhere]

-   Do not raise an error if os.environ does not contain PATH [#&#8203;6935](https://github.com/python-pillow/Pillow/issues/6935)
    \[radarhere, hugovk]

-   Close OleFileIO instance when closing or exiting FPX or MIC [#&#8203;7005](https://github.com/python-pillow/Pillow/issues/7005)
    \[radarhere]

-   Added **int** to IFDRational for Python >= 3.11 [#&#8203;6998](https://github.com/python-pillow/Pillow/issues/6998)
    \[radarhere]

-   Added memoryview support to Dib.frombytes() [#&#8203;6988](https://github.com/python-pillow/Pillow/issues/6988)
    \[radarhere, nulano]

-   Close file pointer copy in the libtiff encoder if still open [#&#8203;6986](https://github.com/python-pillow/Pillow/issues/6986)
    \[fcarron, radarhere]

-   Raise an error if ImageDraw co-ordinates are incorrectly ordered [#&#8203;6978](https://github.com/python-pillow/Pillow/issues/6978)
    \[radarhere]

-   Added "corners" argument to ImageDraw rounded_rectangle() [#&#8203;6954](https://github.com/python-pillow/Pillow/issues/6954)
    \[radarhere]

-   Added memoryview support to frombytes() [#&#8203;6974](https://github.com/python-pillow/Pillow/issues/6974)
    \[radarhere]

-   Allow comments in FITS images [#&#8203;6973](https://github.com/python-pillow/Pillow/issues/6973)
    \[radarhere]

-   Support saving PDF with different X and Y resolutions [#&#8203;6961](https://github.com/python-pillow/Pillow/issues/6961)
    \[jvanderneutstulen, radarhere, hugovk]

-   Fixed writing int as UNDEFINED tag [#&#8203;6950](https://github.com/python-pillow/Pillow/issues/6950)
    \[radarhere]

-   Raise an error if EXIF data is too long when saving JPEG [#&#8203;6939](https://github.com/python-pillow/Pillow/issues/6939)
    \[radarhere]

-   Handle more than one directory returned by pkg-config [#&#8203;6896](https://github.com/python-pillow/Pillow/issues/6896)
    \[sebastic, radarhere]

-   Do not retry past formats when loading all formats for the first time [#&#8203;6902](https://github.com/python-pillow/Pillow/issues/6902)
    \[radarhere]

-   Do not retry specified formats if they failed when opening [#&#8203;6893](https://github.com/python-pillow/Pillow/issues/6893)
    \[radarhere]

-   Do not unintentionally load TIFF format at first [#&#8203;6892](https://github.com/python-pillow/Pillow/issues/6892)
    \[radarhere]

-   Stop reading when EPS line becomes too long [#&#8203;6897](https://github.com/python-pillow/Pillow/issues/6897)
    \[radarhere]

-   Allow writing IFDRational to BYTE tag [#&#8203;6890](https://github.com/python-pillow/Pillow/issues/6890)
    \[radarhere]

-   Raise ValueError for BoxBlur filter with negative radius [#&#8203;6874](https://github.com/python-pillow/Pillow/issues/6874)
    \[hugovk, radarhere]

-   Support arbitrary number of loaded modules on Windows [#&#8203;6761](https://github.com/python-pillow/Pillow/issues/6761)
    \[javidcf, radarhere, nulano]

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS41NC4wIiwidXBkYXRlZEluVmVyIjoiMzUuNTQuMCJ9-->

Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/15
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-04-20 16:04:39 +03:00
e5f80d9702 Updated Renovate config 2023-04-20 13:41:14 +02:00
6f03d5c18f Configure Renovate (#14)
Welcome to [Renovate](https://github.com/renovatebot/renovate)! This is an onboarding PR to help you understand and configure settings before regular Pull Requests begin.

🚦 To activate Renovate, merge this Pull Request. To disable Renovate, simply close this Pull Request unmerged.

---
### Detected Package Files

 * `requirements.txt` (pip_requirements)

### Configuration Summary

Based on the default config's presets, Renovate will:

  - Start dependency updates only once this onboarding PR is merged
  - Enable Renovate Dependency Dashboard creation.
  - Use semantic commit type `fix` for dependencies and `chore` for all others if semantic commits are in use.
  - Ignore `node_modules`, `bower_components`, `vendor` and various test/tests directories.
  - Group known monorepo packages together.
  - Use curated list of recommended non-monorepo package groupings.
  - Apply crowd-sourced package replacement rules.
  - Apply crowd-sourced workarounds for known problems with packages.

🔡 Would you like to change the way Renovate is upgrading your dependencies? Simply edit the `.renovaterc` in this branch with your custom config and the list of Pull Requests in the "What to Expect" section below will be updated the next time Renovate runs.

---

### What to Expect

It looks like your repository dependencies are already up-to-date and no Pull Requests will be necessary right away.

---

 Got questions? Check out Renovate's [Docs](https://docs.renovatebot.com/), particularly the Getting Started section.
If you need any further assistance then you can also [request help here](https://github.com/renovatebot/renovate/discussions).

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).

Reviewed-on: #14
Co-authored-by: Renovate <renovate@git.end-play.xyz>
Co-committed-by: Renovate <renovate@git.end-play.xyz>
2023-04-20 14:40:36 +03:00
2b520760d3 Fixed branch name in Renovate config 2023-04-20 13:35:52 +02:00
9d340b32c1 Added Renovate config 2023-04-20 13:32:52 +02:00
4dd754055e Removed optional dependencies as a concept 2023-03-23 15:13:38 +01:00
ae654d686e TgCrypto is no longer optional dependency 2023-03-23 15:12:53 +01:00
af2bcd088f Album check returned 2023-03-22 15:52:34 +01:00
Profitroll
cc6523f604 Added updates checker 2023-03-22 11:03:03 +01:00
Profitroll
5ad52aa3f8 Changed aiohttp module location 2023-03-22 11:02:55 +01:00
Profitroll
45789ad013 Updates wiki init 2023-03-22 11:02:13 +01:00
Profitroll
4422a13ba9 Improved async context 2023-03-22 10:33:21 +01:00
f82c7b309f Added media removal 2023-03-21 14:34:25 +01:00
399fc5050d Updated user messages 2023-03-20 14:50:57 +01:00
56adb16a3e Replaced strings 2023-03-20 13:59:22 +01:00
1684c5177c Replaced /reboot with /shutdown 2023-03-20 13:56:18 +01:00
fba52bcfc4 Updated docs 2023-03-20 13:55:55 +01:00
20666fc0f8 Improved shutdown 2023-03-20 13:55:42 +01:00
03b6bbe039 Prototype: Files import 2023-03-20 12:03:03 +01:00
1749d49a52 Fixed enumerate error 2023-03-18 21:17:04 +01:00
a4323981fb WIP: Media import/export 2023-03-18 20:53:26 +01:00
06b6b49f43 This commit closes #13 2023-03-17 14:51:41 +01:00
1f345e87f7 Improved logging 2023-03-17 14:44:37 +01:00
717586a9f0 Improved logging 2023-03-17 14:13:28 +01:00
95351f247c This commit closed #12 2023-03-17 13:52:16 +01:00
b5e3abd4ad WIP: default config replacement for (#12) 2023-03-16 16:32:26 +01:00
4ec69c2a05 This commit closes #9 2023-03-16 15:03:14 +01:00
Profitroll
62e0a4986c WIP: ID returns 2023-03-16 12:58:57 +01:00
Profitroll
be8f0262d0 This commit closes #11 2023-03-15 21:58:14 +01:00
Profitroll
ba5a0f116c Merge branch 'dev' of https://git.end-play.xyz/profitroll/TelegramPoster into dev 2023-03-15 21:52:48 +01:00
Profitroll
e85adeafd0 Added info about venv to the README 2023-03-15 21:50:06 +01:00
87d2a2c6d3 Improved README 2023-03-13 13:02:06 +01:00
bc6080e832 Added venv guide to README 2023-03-13 11:33:30 +01:00
9bf4663b9e Made execution order correct 2023-03-12 22:58:55 +01:00
b74ce9ec89 Small fixes 2023-03-12 22:58:41 +01:00
6ed3673682 Fixed a typo 2023-03-12 22:45:06 +01:00
c593be156d Rearranged content 2023-03-12 22:30:35 +01:00
c96f98560c WIP: New readme 2023-03-12 22:29:53 +01:00
e1c1b58ffb Added warning about readme not being ready yet 2023-03-12 22:26:16 +01:00
bd2a1f0b12 WIP: New readme 2023-03-12 22:23:02 +01:00
8cc7808afb Disabled slash-escape for .json 2023-03-12 22:14:41 +01:00
6684c4d750 Bump Pyrogram to 2.0.102 2023-03-12 22:14:23 +01:00
c670db72fa Added user-friendly CLI 2023-03-12 22:14:03 +01:00
48acab2d5e WIP: New readme 2023-03-12 19:57:22 +01:00
62f076148c Wiki pages init 2023-03-12 19:43:51 +01:00
59b504e6d9 Temp: removed strings about API based in config 2023-03-12 19:34:27 +01:00
ad281ba981 WIP: New readme 2023-03-12 19:25:35 +01:00
0073120bf2 Moved ujson to required dependencies 2023-03-12 14:54:06 +01:00
244173e556 Bumped Pyrogram to 2.0.101 2023-03-12 14:25:22 +01:00
98e9c5f5a2 Fixed line being displayed incorrectly 2023-03-09 16:17:35 +01:00
a8545dd097 Fixed project name align 2023-03-09 16:16:38 +01:00
3f22340852 Added license and code style badges 2023-03-09 11:48:50 +01:00
c2fb88ed65 Bumped APScheduler to 3.10.1 2023-03-09 11:35:02 +01:00
88692ebc85 Now using black for formatting 2023-03-09 11:33:02 +01:00
4331af415e Moved to aiohttp and aiofiles 2023-03-02 22:38:48 +01:00
a913ba19c6 WIP: aiohttp migration 2023-03-02 16:39:59 +01:00
2387823151 Added missing texts 2023-02-25 23:31:09 +01:00
88f8bb4a52 WIP: Photos and queue removal 2023-02-25 23:19:08 +01:00
cf6c1f03d7 Disabled notification for accepted submissions 2023-02-25 23:12:33 +01:00
0a309a9f59 Added one more rule 2023-02-25 23:10:57 +01:00
7810f3b7b9 Fixed absence of None check 2023-02-24 21:06:18 +01:00
7b2534012d Fixed typo 2023-02-24 13:48:06 -05:00
6e8b47cf4b Improved submissions randomness 2023-02-24 13:33:34 -05:00
c27b1c5a5b Temporarily disabled album creation on start 2023-02-24 13:25:57 -05:00
c7228a006b Improved randomness 2023-02-24 13:25:36 -05:00
64ae3fb047 Added option to mention submissions 2023-02-24 12:39:33 -05:00
Profitroll
056fc52353 This commit closes #4 and closes #6 2023-02-19 20:54:58 +01:00
Profitroll
8bafd0cb35 This commit closes #3 2023-02-19 20:44:00 +01:00
Profitroll
fd47217bad This commit closes #5 2023-02-19 20:31:08 +01:00
Profitroll
6b7b5c22f2 Reverted web preview disable 2023-02-18 00:58:09 +01:00
Profitroll
b3698cfa70 Added support for duplicates access tokens 2023-02-18 00:55:58 +01:00
Profitroll
7607003f55 Added callback message for "nothing" 2023-02-17 23:45:55 +01:00
Profitroll
7918049f49 Fixed language code typo 2023-02-17 23:18:54 +01:00
Profitroll
fcd59b7aca Fixed caption display 2023-02-17 23:18:34 +01:00
Profitroll
8c478072c6 Fixed reply message not getting quoted 2023-02-17 23:18:19 +01:00
Profitroll
b766d0c52c Improved linting and removed unused imports 2023-02-17 22:59:03 +01:00
Profitroll
807e629ae7 Moved cooldowns to PosterUser class 2023-02-17 22:48:37 +01:00
Profitroll
87af9fd333 Fixed captions not getting detected 2023-02-17 22:26:07 +01:00
Profitroll
a54081a2ae Fixed some issues with locale 2023-02-17 21:58:46 +01:00
Profitroll
664284a6f8 Integrated previous commits 2023-02-17 21:55:38 +01:00
Profitroll
bd9917fb17 Replaced user block logic 2023-02-17 21:54:52 +01:00
Profitroll
68ea087963 Integrated interval-based schedule 2023-02-17 21:54:30 +01:00
Profitroll
d1813856d9 Imported callback "nothing" 2023-02-17 21:54:14 +01:00
Profitroll
cf204577e4 Added external address for links 2023-02-17 21:53:57 +01:00
Profitroll
68c887999e Divided admins and owner 2023-02-17 21:53:43 +01:00
Profitroll
642e17ee55 Image resize when too big 2023-02-17 16:46:44 +01:00
Profitroll
25af9b31f8 Changed Client to PosterClient 2023-02-17 16:46:33 +01:00
Profitroll
28fc359593 WIP: export and import 2023-02-17 16:46:13 +01:00
Profitroll
07203a9db9 Changed the way exceptions are handled 2023-02-17 16:45:51 +01:00
Profitroll
663a7b0db8 Submission without confirmation added 2023-02-17 16:44:56 +01:00
Profitroll
0d2e9fa6ec Using PosterClient instead of Client 2023-02-17 16:44:30 +01:00
Profitroll
c90e5eb697 Added Pillow to requirements 2023-02-17 16:44:03 +01:00
Profitroll
f4359aa6cd Added upload_pic method 2023-02-17 11:51:38 +01:00
4cd37be5dc WIP: New submission system 2023-02-16 16:41:01 +01:00
05042f01c6 Disabled WIP warning 2023-02-15 14:07:40 +01:00
5b1afe2c9e WIP: API usage as main 2023-02-15 14:06:06 +01:00
6eaa6019a3 Added tmp file size 2023-02-15 11:56:15 +01:00
493be818b8 Updated ignore 2023-02-15 11:56:05 +01:00
cf09017985 TMP: interval changes 2023-02-14 16:26:08 +01:00
7940cbe5a7 WIP: API usage as main 2023-02-14 16:25:56 +01:00
8e0fee4cb9 Added database support 2023-02-14 14:45:52 +01:00
08e03ba911 Moved handlers to another location 2023-02-14 11:38:54 +01:00
b589da5d8f Updated dependencies 2023-02-14 11:38:09 +01:00
a3a1be241c Small keyboard fix 2023-01-17 14:38:31 +01:00
9ce05589b0 Contextual keyboard [WIP] 2023-01-17 14:38:21 +01:00
c1ee988106 Added empty callback 2023-01-17 14:37:36 +01:00
aebe804b58 Optimized for API 2023-01-17 14:11:23 +01:00
9f0ed202da Updated ignore 2023-01-17 12:51:30 +01:00
b4a9595eb4 Renamed logging module 2023-01-17 11:43:10 +01:00
569fffd223 Updated requirements 2023-01-17 11:41:07 +01:00
f719befe40 Updated config 2023-01-17 11:40:59 +01:00
c4323f0b00 Moved to async 2023-01-10 13:06:24 +01:00
6facf428c5 Complete refactor 2023-01-10 12:52:44 +01:00
9261585f5f Added scheduler start 2023-01-09 19:01:07 +02:00
cddf1e211a Moved to apscheduler 2023-01-09 15:36:56 +01:00
cd5b73e7ff Small improvements 2022-11-03 12:13:02 +01:00
46ea9a2f74 Merge branch 'master' of https://git.profitroll.eu/profitroll/TelegramPoster 2022-11-03 12:12:41 +01:00
7a7f386891 Is now using argparse library 2022-11-03 12:10:59 +01:00
Profitroll
11255bbbd0 Pagination fixed 2022-10-05 21:37:37 +02:00
Profitroll
8ae50a4f97 Low number of files in queue reminder 2022-10-05 21:36:59 +02:00
Profitroll
46e3665749 Small fix 2022-09-24 17:03:20 +02:00
Profitroll
0e14ca600f Changed some imports' paths 2022-09-18 18:27:25 +02:00
Profitroll
c4177d1575 Optimized imports 2022-09-18 18:27:13 +02:00
Profitroll
f0c333968b "locale_log" explanation added 2022-09-18 18:13:40 +02:00
Profitroll
dfc1e36f07 Logging has been improved 2022-09-18 18:10:41 +02:00
Profitroll
fc21983305 Fixed absence of "block" & "unblock" buttons 2022-09-16 20:42:58 +02:00
58c5429e5c Imports were a bit optimized 2022-08-31 14:31:24 +02:00
6e1de38da0 Changed bot "mode" behavior 2022-08-30 14:10:05 +02:00
5848bca40a Changed a bit more due to "modes" update 2022-08-30 14:03:11 +02:00
da97606bb4 Commented on_raw_update event 2022-08-30 14:01:12 +02:00
91a1d7e349 Logging now has debug and bot has various modes 2022-08-30 13:53:17 +02:00
366a1d9201 Main executable name changed 2022-08-30 13:51:24 +02:00
Profitroll
aca61b8c1a AttributeError handling in get_submission 2022-08-22 11:51:40 +02:00
Profitroll
404a46ec15 Merge branch 'master' of https://git.end-play.xyz/profitroll/TelegramPoster 2022-08-14 18:27:39 +02:00
Profitroll
2591d26349 Admin now also gets user commands 2022-08-14 18:27:35 +02:00
44 changed files with 2821 additions and 794 deletions

23
.gitignore vendored
View File

@@ -152,18 +152,17 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
# ---> VisualStudioCode # Custom
.vscode/* cache/
!.vscode/settings.json config.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code *.session
.history/ *.session-wal
*.session-shm
*.session-journal
# Built Visual Studio Code Extensions venv
*.vsix venv_linux
venv_windows
# Project .vscode

20
.renovaterc Normal file
View File

@@ -0,0 +1,20 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"baseBranches": [
"dev"
],
"packageRules": [
{
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true
}
]
}

113
README.md
View File

@@ -1,59 +1,110 @@
# TelegramPoster <h1 align="center">TelegramPoster</h1>
<p align="center">
<a href="https://git.end-play.xyz/profitroll/TelegramPoster/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/profitroll/TelegramPoster"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
</p>
> Шукаєш інструкцію українською? А вона [ось тут](https://git.end-play.xyz/profitroll/TelegramPoster/src/branch/master/README_uk.md) знаходиться) > Шукаєш інструкцію українською? А вона [ось тут](https://git.end-play.xyz/profitroll/TelegramPoster/src/branch/master/README_uk.md) знаходиться)
This bot is used for one and only task - post pictures from my personal archive. Here's its source code so you can also host a bot and have fun with it. Just don't exepect it to be brilliant. It is not. But hey, you can always fork it ;) This bot is used for one and only task - post pictures from my personal archive. Here's its source code so you can also host a bot and have fun with it. Just don't exepect it to be brilliant. It is not. But hey, you can always fork it ;)
## Dependencies
* [Python 3.8+](https://www.python.org) (3.9+ recommended)
* [MongoDB](https://www.mongodb.com)
* [PhotosAPI](https://git.end-play.xyz/profitroll/PhotosAPI)
Use [MongoDB's installation manual](https://www.mongodb.com/docs/manual/installation) and [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md).
Please note that Photos API also requires MongoDB so it makes sense to install and configure Mongo first.
## Installation ## Installation
To make this bot run at first you need to have a Python interpreter and git. Google is your friend finding it. You can also ignore git and simply download source code, should also work fine. After that you're ready to go.
To make this bot run at first you need to have a Python interpreter, Photos API, 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 > 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 > 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. > is `python3` or for example `/home/user/.local/bin/python3.9` - use it instead.
> If it's non-standart executable path - you should also change > If it's non-standard executable path - you should also change
> it in scripts you will use (`loop.sh`, `loop.bat`, `start.sh` and `start.bat`). > it in scripts you will use (`loop.sh`, `loop.bat`, `start.sh` and `start.bat`).
1. Download the bot: 1. Install MongoDB and Photos API:
1. `git clone https://git.end-play.xyz/profitroll/TelegramSender.git` (if you want to use git)
2. `cd ./TelegramSender` 1. Install MongoDB by following [official installation manual](https://www.mongodb.com/docs/manual/installation)
2. Install Photos API by following [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md)
2. Download the bot:
1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramPoster.git` (if you're using git)
2. `cd TelegramPoster`
3. Create virtual environment [Optional]:
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:
2. Install dependencies:
`python -m pip install -r requirements.txt` `python -m pip install -r requirements.txt`
Without installing those - bot cannot work at all Without installing those - bot cannot work at all.
3. Install optional dependencies [Not required]: 5. Configure required keys with your favorite text editor:
`python -m pip install -r requirements-optional.txt`
These are not required but can make the bot run a bit faster
4. Configure your bot with a favorite text editor: 1. Copy config file: `cp config_example.json config.json`
`nano 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
You can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever. 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).
5. Add bot to the channel: 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).
To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel.
6. Fill your contents folder: 6. Configure database and API:
Of course bot cannot post something from nothing. Configure your `config.json` what media types bot should post (`"posting", "extensions"`), when to post them (`"posting", "time"`) and also where to find them (`"locations"`). You can also move them when sent by setting `"posting", "move_sent"` to `true`.
6. Good to go, run it! 1. Configure database:
`python ./main.py` 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).
2. Configure Photos API:
1. Change `"posting.api.address"` and `"posting.api.address_external"` to the ones your API server uses
2. Run your bot using `python main.py --create-user --create-album` to configure its new user and album. You can also use manual user and album creation described [in the wiki](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-API). You can also change username, password and album in`"posting.api"` to the user and album you have if you already have Photos API album and user set up. In that case you don't need to create a new one.
7. Add bot to the channel:
To use your bot of course you need to have a channel or group otherwise it makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. After that simply set `"posting.channel"` to your channel's ID and `"posting.comments"` to comments group's ID.
8. Configure posting time:
To make your bot post random content you need to configure `"posting.time"` with a list of "DD:MM" formatted strings or use `"posting.interval"` formatted as "XdXhXmXs". To use interval instead of selected time, set `"posting.use_interval"` to `true`.
9. Good to go, run it!
Make sure MongoDB and Photos API are running and use `python main.py` to start the bot.
Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux.
Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/shutdown` command.
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/profitroll/TelegramPoster/wiki) to get more detailed instructions.
## CLI arguments
## Command line arguments
Of course bot also has them. You can perform some actions with them. Of course bot also has them. You can perform some actions with them.
* `--move-sent` - allows you to move all sent files from queue to sent directories
* `--cleanup` - purge files in both `queue` and `sent` folders if they're sent. Requires `--confirm` argument * `--create-user` - create new API user. Requires config key `"posting.api.address"` to be set;
* `--cleanup-index` - purge all sent entries from index. Requires `--confirm` argument * `--create-album` - create new API album. Requires API address and user config (`"posting.api"`) to be complete.
* `--norun` - allows you to execute above arguments without triggering the bot start itself
Examples: Examples:
* `python3 ./main.py --move-sent --norun`
* `python3 ./main.py --cleanup --confirm` * `python main.py --create-user`
* `python main.py --create-user --create-album`
## Tips and improvements
* You may want to configure your bot to work as a systemd service instead. There's [a tutorial for that](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Service) in the wiki.
## Localization ## Localization
Bot is capable of using custom locales. There are some that are pre-installed (English and Ukrainian), however you can add your own locales too. Bot is capable of using custom locales. There are some that are pre-installed (English and Ukrainian), however you can add your own locales too.
All localization files are located in the `locale` folder, otherwise in folder specified in config file. 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 console output and messages, that cannot determine admin's locale - edit `"locale"` parameter in the `config.json`. If this locale is not available - `"locale_fallback"` will be used instead. If both are not available - error will be shown. 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. 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

@@ -1,57 +1,108 @@
# TelegramPoster <h1 align="center">TelegramPoster</h1>
Цей бот використовується для однієї-єдиної задачі - розміщувати фотографії з мого особистого архіву. Ось його код, тож Ви також можете захостити бота самостійно та розважитися з ним. Тільки не очікуйте, що він ідеальним. Не буде. Але гей, Ви завжди можете його доробити під себе ;)
## Установка <p align="center">
Для запуску цього бота спочатку потрібно мати інтерпретатор Python та встановлений git. Google — Ваш друг у пошуках. Ви також можете ігнорувати git і просто завантажити код, також має спрацювати добре. Після цього Ви готові до встановлення. <a href="https://git.end-play.xyz/profitroll/TelegramPoster/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/profitroll/TelegramPoster"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
</p>
> У цьому README я вважаю, що Ви використовуєте python за замовчуванням у своїй Цей бот використовується для однієї-єдиної задачі - публікувати фотографії з мого особистого архіву. Ось його код, тож ви також можете запустити бота і погратися з ним самостійно. Тільки не очікуйте, що він буде ідеальним. Це не так. Але ви завжди можете його форкнути ;)
> системі і PATH Вашої системи містить його. Якщо Ваш python за замовчуванням
> є `python3` або, наприклад, `/home/user/.local/bin/python3.9` - використовуйте його натомість.
> Якщо це нестандартний шлях до виконуваного файлу - Вам також слід змінити
> це у скриптах, які Ви використовуватимете (`loop.sh`, `loop.bat`, `start.sh` та `start.bat`).
1. Завантажте бота: ## Залежності
1. `git clone https://git.end-play.xyz/profitroll/TelegramSender.git` (якщо хочете використовувати git)
2. `cd ./TelegramSender`
2. Встановіть залежності: * [Python 3.8+](https://www.python.org) (рекомендується 3.9+)
`python -m pip install -r requirements.txt` * [MongoDB](https://www.mongodb.com)
Без їх установки бот не зможе працювати взагалі * [PhotosAPI](https://git.end-play.xyz/profitroll/PhotosAPI)
3. Встановіть додаткові залежності [Не обов'язково]: Користуйтесь [інструкцією зі встановлення MongoDB](https://www.mongodb.com/docs/manual/installation) та [README Photos API](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md).
`python -m pip install -r requirements-optional.txt`
Вони не є обов’язковими, але можуть прискорити роботу бота
4. Налаштуйте свого бота за допомогою текстового редактора: Зверніть увагу, що Photos API також потребує MongoDB, тому має сенс спочатку встановити й налаштувати Mongo.
`nano config.json`
Ви можете редагувати за допомогою vim, nano, у Windows це Notepad або Notepad++. На Ваш смак.
Якщо Ви не знаєте, де знайти bot_token і свій ідентифікатор, тут Ви можете знайти кілька підказок: [отримати токен бота](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [отримати свій ідентифікатор](https://www.alphr.com/telegram-find-user-id/), [отримати api_hash і api_id](https://core.telegram.org/api/obtaining_api_id ).
5. Додайте бота на канал: ## Встановлення
Звичайно, щоб використовувати свого бота, Вам потрібно мати канал або групу, інакше немає сенсу мати такого бота. [Тут](https://stackoverflow.com/a/33497769) Ви можете знайти короткий гайд, як додати свого бота до каналу.
6. Заповніть папку вмістом: Щоб запустити бота, вам потрібно мати інтерпретатор Python, Photos API, MongoDB і, за бажанням, git (якщо ви хочете оновлювати за допомогою `git pull`). Ви також можете проігнорувати git і просто завантажити вихідний код, це також повинно спрацювати. Після цього ви готові до роботи.
Звичайно, бот не може опублікувати щось із нічого. Налаштуйте свій `config.json`, які медіа-типи бот повинен публікувати (`"posting", "extensions"`), коли їх публікувати (`"posting", "time"`), а також де їх знайти (`"locations"`). Ви також можете переміщати їх після надсилання, встановивши для `"posting", "move_sent"` значення `true`.
6. Готово, запускайте! > У цьому README я припускаю, що ви використовуєте python за замовчуванням у вашій
`python ./main.py` > системі, і він міститься у вашому системному PATH. Якщо ваш python за замовчуванням
Або ви також можете використовувати `.\start.bat` на Windows і `bash ./start.sh` на Linux. > це `python3` або, наприклад, `/home/user/.local/bin/python3.9` - використовуйте його.
Крім того, доступні `loop.sh` і `loop.bat`, якщо ви хочете, щоб ваш бот запускався знову після зупинки або після використання команди `/reboot`. > Якщо це нестандартний шлях до виконуваного файлу - вам також слід змінити
> його у скриптах, які ви будете використовувати (`loop.sh`, `loop.bat`, `start.sh` та `start.bat`).
## Аргументи командного рядка 1. Встановіть MongoDB та Photos API:
Звичайно, у бота вони також є. З ними можна виконувати деякі дії.
* `--move-sent` - дозволяє перемістити всі надіслані файли з черги до папки надісланих 1. Встановіть MongoDB, дотримуючись [офіційного посібника зі встановлення](https://www.mongodb.com/docs/manual/installation)
* `--cleanup` - очистити файли в папках `queue` і `sent`, якщо вони вже надіслані. Потрібен аргумент `--confirm` 2. Встановіть Photos API, дотримуючись [README Photos API](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md)
* `--cleanup-index` - видалити всі надіслані записи з індексу. Потрібен аргумент `--confirm`
* `--norun` - дозволяє виконувати наведені вище аргументи, не запускаючи самого бота 2. Завантажте бота:
1. `git clone https://git.end-play.xyz/profitroll/TelegramPoster.git` (якщо ви використовуєте git)
2. `cd TelegramPoster`
3. Створіть віртуальне середовище [Необов'язково]:
1. Встановіть модуль virtualenv: `pip install virtualenv`
2. Створіть venv: `python -m venv .venv`
3. Активуйте його за допомогою `ource .venv/bin/activate` в Linux, `.venv\Scripts\activate.bat` в CMD або `.venv\Scripts\Activate.ps1` в PowerShell.
4. Встановіть залежності проекту:
`python -m pip install -r requirements.txt`.
Без їх встановлення бот не зможе працювати взагалі.
5. Налаштуйте необхідні ключі за допомогою вашого улюбленого текстового редактора:
1. Скопіюйте конфігураційний файл: `cp config_example.json config.json`
2. Відкрийте `config.json` за допомогою вашого улюбленого текстового редактора. Наприклад, `nano config.json`, але ви також можете відредагувати його за допомогою vim, mcedit або Notepad/Notepad++ на Windows
3. Змініть значення ключів `"bot.owner"`, `"bot.api_id"`, `"bot.api_hash"` і `"bot.bot_token"`.
Якщо ви не знаєте, де знайти bot_token і ваш id - тут ви можете знайти кілька підказок: [отримати токен бота](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [отримати свій id](https://www.alphr.com/telegram-find-user-id), [отримати api_hash та api_id](https://core.telegram.org/api/obtaining_api_id).
6. Налаштування бази даних та API:
1. Налаштуйте базу даних:
1. Змініть хост і порт бази даних у ключах `"database.host"` і `"database.port"`. Для локальної установки за замовчуванням це будуть `127.0.0.1` і `27017` відповідно
2. Змініть ім'я бази даних в `"database.name"`. Вона буде автоматично створена при запуску
3. Якщо ви змінили користувача та пароль для доступу до бази даних, вам також слід змінити ключі `"database.user"` та `"database.password"`, інакше залиште їх `null` (за замовчуванням).
2. Налаштуйте Photos API:
1. Змініть `"posting.api.address"` та `"posting.api.address_external"` на ті, що використовує ваш сервер API
2. Запустіть бота за допомогою `python main.py --create-user --create-album`, щоб налаштувати нового користувача та альбом. Ви також можете скористатися ручним створенням користувача і альбому, описаним [у вікі](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-API). Ви також можете змінити ім'я користувача, пароль і альбом у `"posting.api"` на користувача і альбом, які у вас є, якщо у вас вже налаштовані альбом і користувач Photos API. У цьому випадку вам не потрібно створювати нові.
7. Додайте бота до каналу:
Щоб використовувати бота, вам, звичайно, потрібно мати канал або групу, інакше немає сенсу мати такого бота. [Тут](https://stackoverflow.com/a/33497769) ви можете знайти короткий посібник, як додати бота до каналу. Після цього просто встановіть `"posting.channel"` на ID вашого каналу і `"posting.comments"` на ID групи коментарів.
8. Налаштуйте час публікації:
Щоб ваш бот публікував випадковий контент, вам потрібно налаштувати `"posting.time"` зі списком рядків у форматі "ДД:ММ" або використовувати `"posting.interval"` у форматі "XdXhXmXs". Щоб використовувати інтервал замість вибраного часу, встановіть `"posting.use_interval"` у значення `true`.
9. Готово, запускайте!
Переконайтеся, що MongoDB і Photos API запущені і використовуйте `python main.py` для запуску бота.
Або ви також можете використовувати `.\start.bat` в Windows і `bash ./start.sh` в Linux.
Додатково доступні `loop.sh` і `loop.bat`, якщо ви хочете, щоб ваш бот запустився знову після зупинки або після використання команди `/shutdown`.
Якщо вам потрібні додаткові інструкції щодо налаштування бота або у вас виникли труднощі - скористайтеся [вікі в цьому репозиторії](https://git.end-play.xyz/profitroll/TelegramPoster/wiki), щоб отримати детальніші інструкції.
## CLI аргументи
Звичайно, бот також має CLI аргументи. За допомогою них можна виконувати деякі дії.
* `--create-user` - створити нового користувача API. Потребує встановленого конфігураційного ключа `"posting.api.address"`;
* `--create-album` - створити новий альбом API. Вимагає заповнених адреси API та конфігурації користувача (`"posting.api"`).
Приклади: Приклади:
* `python3 ./main.py --move-sent --norun`
* `python3 ./main.py --cleanup --confirm` * `python main.py --create-user`
* `python main.py --create-user --create-album`
## Поради та покращення
* Можливо, ви захочете налаштувати бота для роботи як системну службу. У вікі є [сторінка з цього питання](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Service).
## Локалізація ## Локалізація
Бот може використовувати різні мови. Є деякі попередньо встановлені (Англійська та Українська), однак Ви можете додавати свої власні локалізації теж.
Всі файли локалізації знаходяться у папці `locale`, якщо в конфігураційному файлі не вказано іншу. Просто скопіюйте цікавлячий Вас файл, назвіть його відповідно до [тегів мови IETF](https://en.wikipedia.org/wiki/IETF_language_tag) (якщо Ви хочете, щоб переклад був сумісним з перекладами Telegram) або просто вкажіть свою власну назву. Збережіть свій переклад як json файл і все готово. Якщо ви хочете змінити мову за замовчуванням для виведення консолі та повідомлень самого бота, які не можуть визначити мову адміністратора, відредагуйте параметр `"locale"` у `config.json`. Якщо ця мова недоступна, замість неї буде використано `"locale_fallback"`. Якщо обидві мови недоступні - буде показано помилку. Бот може використовувати файли локалізації. Деякі з них встановлено за замовчуванням (англійська та українська), але ви також можете додавати свої власні.
Ми рекомендуємо вносити будь-які зміни лише до вашої окремої мови. Або, принаймні, завжди мати резервну копію, наприклад, `en.json` як запасний варіант. Усі файли локалізації знаходяться у теці `locale`. Просто скопіюйте файл локалі за вашим вибором, назвіть його відповідно до [мовних кодів IETF](https://en.wikipedia.org/wiki/IETF_language_tag) (якщо ви хочете, щоб ваша локаль була сумісна з локалями Telegram) або дайте йому власну назву. Збережіть переклад у форматі json, і все буде готово. Якщо ви хочете змінити локаль за замовчуванням для повідомлень - відредагуйте параметр `"locale"` у файлі `config.json`.
Ми рекомендуємо вносити зміни лише у вашу власну локаль. Або, принаймні, завжди мати резервну копію, наприклад, `en.json` як запасний варіант.

View File

@@ -0,0 +1,8 @@
from enum import Enum
class SubmissionType(Enum):
DOCUMENT = "document"
VIDEO = "video"
# ANIMATION = "animation"
PHOTO = "photo"

63
classes/exceptions.py Normal file
View File

@@ -0,0 +1,63 @@
from typing import Any
class SubmissionUnavailableError(Exception):
pass
class SubmissionUploadError(Exception):
def __init__(self, file_path: str, status_code: int, content: Any) -> None:
self.status_code = status_code
self.content = content
super().__init__(
f"Could not upload photo '{file_path}' due to HTTP {self.status_code}: {self.content}"
)
class SubmissionDuplicatesError(Exception):
def __init__(self, file_path: str, duplicates: list) -> None:
self.duplicates = duplicates
super().__init__(
f"Found duplicates of a photo '{file_path}': {self.duplicates}"
)
class SubmissionUnsupportedError(Exception):
def __init__(self, file_path: str) -> None:
super().__init__(f"Type of file does not seem to be supported: '{file_path}'")
class UserCreationError(Exception):
def __init__(self, code: int, data: str) -> None:
self.code = code
self.data = data
super().__init__(
f"Could not create a new user. API returned HTTP {self.code} with content: {self.data}"
)
class UserCreationDuplicateError(Exception):
def __init__(self, username: str) -> None:
self.username = username
super().__init__(f"User '{self.username} already exists.'")
class AlbumCreationError(Exception):
def __init__(self, code: int, data: str) -> None:
self.code = code
self.data = data
super().__init__(
f"Could not create a new album. API returned HTTP {self.code} with content: {self.data}"
)
class AlbumCreationDuplicateError(Exception):
def __init__(self, name: str) -> None:
self.name = name
super().__init__(f"Album '{self.name} already exists.'")
class AlbumCreationNameError(Exception):
def __init__(self, data: dict) -> None:
self.data = data
super().__init__(data["detail"])

287
classes/pyroclient.py Normal file
View File

@@ -0,0 +1,287 @@
import contextlib
import logging
from datetime import datetime
from io import BytesIO
from os import makedirs, remove, sep
from pathlib import Path
from shutil import rmtree
from time import time
from traceback import format_exc
from typing import Dict, List, Tuple, Union
import aiofiles
from aiohttp import ClientSession
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from bson import ObjectId
from libbot import json_write
from libbot.i18n.sync import _
from libbot.pyrogram.classes import PyroClient
from photosapi_client.errors import UnexpectedStatus
from pyrogram.errors import bad_request_400
from pyrogram.types import Message, User
from pytimeparse.timeparse import timeparse
from ujson import dumps, loads
from classes.enums.submission_types import SubmissionType
from classes.exceptions import (
SubmissionDuplicatesError,
SubmissionUnavailableError,
SubmissionUnsupportedError,
)
from classes.pyrouser import PyroUser
from modules.api_client import (
BodyPhotoUpload,
BodyVideoUpload,
File,
Photo,
Video,
client,
photo_upload,
video_upload,
)
from modules.database import col_submitted, col_users
from modules.http_client import http_session
from modules.sender import send_content
logger = logging.getLogger(__name__)
class PyroClient(PyroClient):
def __init__(self, scheduler: AsyncIOScheduler):
super().__init__(locales_root=Path("locale"), scheduler=scheduler)
self.version: float = 0.3
self.owner: int = self.config["bot"]["owner"]
self.admins: List[int] = self.config["bot"]["admins"] + [
self.config["bot"]["owner"]
]
self.sender_session: Union[ClientSession, None] = None
self.scopes_placeholders: Dict[str, int] = {
"owner": self.owner,
"comments": self.config["posting"]["comments"],
}
async def start(self):
await super().start()
if self.sender_session is None:
self.sender_session = ClientSession()
if self.config["reports"]["update"]:
try:
async with ClientSession(
json_serialize=dumps,
) as http_session:
check_update = await http_session.get(
"https://git.end-play.xyz/api/v1/repos/profitroll/TelegramPoster/releases?page=1&limit=1"
)
response = await check_update.json()
if len(response) == 0:
raise ValueError("No bot releases on git found.")
if float(response[0]["tag_name"].replace("v", "")) > self.version:
logger.info(
"New version %s found (current %s)",
response[0]["tag_name"].replace("v", ""),
self.version,
)
await self.send_message(
self.owner,
self._(
"update_available",
"message",
).format(
response[0]["tag_name"],
response[0]["html_url"],
response[0]["body"],
),
)
else:
logger.info("No updates found, bot is up to date.")
except bad_request_400.PeerIdInvalid:
logger.warning(
"Could not send startup message to bot owner. Perhaps user has not started the bot yet."
)
except Exception as exc:
logger.exception("Update check failed due to %s: %s", exc, format_exc())
if self.config["mode"]["post"]:
if self.config["posting"]["use_interval"]:
self.scheduler.add_job(
send_content,
"interval",
seconds=timeparse(self.config["posting"]["interval"]),
args=[self, self.sender_session],
)
else:
for entry in self.config["posting"]["time"]:
dt_obj = datetime.strptime(entry, "%H:%M")
self.scheduler.add_job(
send_content,
"cron",
hour=dt_obj.hour,
minute=dt_obj.minute,
args=[self, self.sender_session],
)
async def stop(self):
makedirs(self.config["locations"]["cache"], exist_ok=True)
await json_write(
{"timestamp": time()},
Path(f"{self.config['locations']['cache']}/shutdown_time"),
)
await http_session.close()
if self.sender_session is not None:
await self.sender_session.close()
await super().stop()
async def submit_media(
self, id: str, purge_caption: bool = False
) -> Tuple[Union[Message, None], Union[str, None]]:
db_entry = await col_submitted.find_one({"_id": ObjectId(id)})
submission = None
if db_entry is None:
raise SubmissionUnavailableError()
if db_entry["temp"]["uuid"] is None:
try:
submission = await self.get_messages(
db_entry["user"], db_entry["telegram"]["msg_id"]
)
filepath = await self.download_media(
submission, file_name=self.config["locations"]["tmp"] + sep
)
except Exception as exc:
raise SubmissionUnavailableError() from exc
elif not Path(
f"{self.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}/{db_entry['temp']['file']}",
).exists():
raise SubmissionUnavailableError()
else:
filepath = Path(
f"{self.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}/{db_entry['temp']['file']}",
)
with contextlib.suppress(Exception):
submission = await self.get_messages(
db_entry["user"], db_entry["telegram"]["msg_id"]
)
async with aiofiles.open(str(filepath), "rb") as fh:
media_bytes = BytesIO(await fh.read())
try:
if db_entry["type"] == SubmissionType.PHOTO.value:
response = await photo_upload(
self.config["posting"]["api"]["album"],
client=client,
body=BodyPhotoUpload(
File(media_bytes, filepath.name, "image/jpeg")
),
ignore_duplicates=self.config["submission"]["allow_duplicates"],
compress=False,
caption="queue",
)
elif db_entry["type"] == SubmissionType.VIDEO.value:
response = await video_upload(
self.config["posting"]["api"]["album"],
client=client,
body=BodyVideoUpload(File(media_bytes, filepath.name, "video/*")),
caption="queue",
)
# elif db_entry["type"] == SubmissionType.ANIMATION.value:
# response = await video_upload(
# self.config["posting"]["api"]["album"],
# client=client,
# body=BodyVideoUpload(
# File(media_bytes, filepath.name, "video/*")
# ),
# caption="queue",
# )
except UnexpectedStatus as exc:
raise SubmissionUnsupportedError(str(filepath)) from exc
response_dict = (
{}
if not hasattr(response, "content")
else loads(response.content.decode("utf-8"))
)
if "duplicates" in response_dict and len(response_dict["duplicates"]) > 0:
duplicates = []
for index, duplicate in enumerate(response_dict["duplicates"]): # type: ignore
if response_dict["access_token"] is None:
duplicates.append(
f"`{duplicate['id']}`:\n{self.config['posting']['api']['address_external']}/photos/{duplicate['id']}"
)
else:
duplicates.append(
f"`{duplicate['id']}`:\n{self.config['posting']['api']['address_external']}/token/photo/{response_dict['access_token']}?id={index}"
)
raise SubmissionDuplicatesError(str(filepath), duplicates)
db_update = (
{"$set": {"done": True, "caption": None}}
if purge_caption
else {"$set": {"done": True}}
)
await col_submitted.update_one({"_id": ObjectId(id)}, db_update)
try:
if db_entry["temp"]["uuid"] is not None:
rmtree(
Path(
f"{self.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}",
),
ignore_errors=True,
)
else:
remove(str(filepath))
except (FileNotFoundError, NotADirectoryError):
logger.error("Could not delete '%s' on submission accepted", filepath)
return (
submission,
response.parsed.id if hasattr(response, "parsed") else response.id,
)
async def find_user(self, user: Union[int, User]) -> PyroUser:
"""Find User by it's ID or User object
### Args:
* user (`Union[int, User]`): ID or User object to extract ID from
### Returns:
* `PyroUser`: PyroUser object
"""
if (
await col_users.find_one(
{"id": user.id if isinstance(user, User) else user}
) # type: ignore
is None
):
await col_users.insert_one(
{
"id": user.id if isinstance(user, User) else user,
"locale": user.language_code if isinstance(user, User) else None,
"banned": False,
"cooldown": datetime(1970, 1, 1, 0, 0),
"subscription": {"expires": datetime(1970, 1, 1, 0, 0)},
}
) # type: ignore
db_record = await col_users.find_one(
{"id": user.id if isinstance(user, User) else user}
) # type: ignore
return PyroUser(**db_record)

53
classes/pyrouser.py Normal file
View File

@@ -0,0 +1,53 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Union
from bson import ObjectId
from libbot import config_get
from libbot.pyrogram.classes import PyroClient
from modules.database import col_users
@dataclass
class PyroUser:
"""Dataclass of DB entry of a user"""
_id: ObjectId
id: int
locale: Union[str, None]
banned: bool
cooldown: datetime
subscription: dict
async def update_locale(self, locale: str) -> None:
await col_users.update_one({"_id": self._id}, {"$set": {"locale": locale}})
async def update_cooldown(self, time: datetime = datetime.now()) -> None:
await col_users.update_one({"_id": self._id}, {"$set": {"cooldown": time}})
async def block(self) -> None:
"""Ban user from using command and submitting content."""
await col_users.update_one({"_id": self._id}, {"$set": {"banned": True}})
async def unblock(self) -> None:
"""Allow user to use command and submit posts again."""
await col_users.update_one({"_id": self._id}, {"$set": {"banned": False}})
async def is_limited(self, app: Union[PyroClient, None] = None) -> bool:
"""Check if user is on a cooldown after submitting something.
### Returns:
`bool`: Must be `True` if on the cooldown and `False` if not
"""
admins = (
await config_get("admins", "bot") + [await config_get("owner", "bot")]
if app is None
else app.admins
)
return (datetime.now() - self.cooldown).total_seconds() < (
app.config["submission"]["timeout"]
if app is not None
else await config_get("timeout", "submission")
)

View File

@@ -1,80 +0,0 @@
{
"module": null,
"locale": "en",
"locale_fallback": "en",
"admin": 0,
"bot": {
"api_id": 0,
"api_hash": "",
"bot_token": ""
},
"reports": {
"sent": false,
"error": true,
"startup": true,
"shutdown": true
},
"logging": {
"size": 512,
"location": "logs"
},
"locations": {
"data": "data",
"sent": "data/sent",
"queue": "data/queue",
"index": "data/index.json",
"submit": "data/submit.json",
"blocked": "data/blocked.json",
"locale": "locale"
},
"posting": {
"channel": 0,
"silent": false,
"move_sent": false,
"extensions": {
"photo": [
"jpg",
"png",
"gif",
"jpeg"
],
"video": [
"mp4",
"avi",
"mkv",
"webm",
"mov"
]
},
"time": [
"08:00",
"10:00",
"12:00",
"14:00",
"16:00",
"18:00",
"20:00",
"22:00"
]
},
"caption": {
"enabled": false,
"text": "sample text",
"link": null
},
"submission": {
"timeout": 30,
"file_size": 15728640,
"mime_types": [
"image/png",
"image/gif",
"image/jpeg",
"video/mp4",
"video/quicktime"
]
},
"commands": [
"start",
"rules"
]
}

205
config_example.json Normal file
View File

@@ -0,0 +1,205 @@
{
"locale": "en",
"bot": {
"owner": 0,
"admins": [],
"api_id": 0,
"api_hash": "",
"bot_token": "",
"max_concurrent_transmissions": 1,
"scoped_commands": true
},
"database": {
"user": null,
"password": null,
"host": "127.0.0.1",
"port": 27017,
"name": "tgposter"
},
"reports": {
"chat_id": "owner",
"sent": false,
"error": true,
"update": true,
"startup": true,
"shutdown": true
},
"mode": {
"post": true,
"submit": true
},
"logging": {
"size": 512,
"location": "logs"
},
"locations": {
"tmp": "tmp",
"data": "data",
"cache": "cache"
},
"disabled_plugins": [],
"posting": {
"channel": 0,
"comments": 0,
"silent": false,
"move_sent": false,
"use_interval": false,
"interval": "1h30m",
"submitted_caption": {
"enabled": true,
"ignore_admins": true,
"text": "#submitted"
},
"types": {
"photo": true,
"video": false
},
"extensions": {
"photo": [
"jpg",
"png",
"jpeg"
],
"video": [
"mp4",
"avi",
"mkv",
"webm",
"mov"
]
},
"time": [
"08:00",
"10:00",
"12:00",
"14:00",
"16:00",
"18:00",
"20:00",
"22:00"
],
"api": {
"address": "http://localhost:8054",
"address_external": "https://photos.domain.com",
"username": "",
"password": "",
"album": "",
"timeout": 15.0
}
},
"caption": {
"enabled": false,
"link": null,
"text": [
"sample text"
]
},
"submission": {
"timeout": 30,
"file_size": 15728640,
"tmp_size": 15728640,
"allow_duplicates": false,
"send_uploaded_id": false,
"require_confirmation": {
"users": true,
"admins": true
},
"mime_types": [
"image/png",
"image/jpeg",
"video/mp4",
"video/quicktime"
]
},
"commands": {
"start": {
"scopes": [
{
"name": "BotCommandScopeDefault"
},
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
},
"rules": {
"scopes": [
{
"name": "BotCommandScopeDefault"
},
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
},
"language": {
"scopes": [
{
"name": "BotCommandScopeDefault"
},
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
},
"report": {
"scopes": [
{
"name": "BotCommandScopeChat",
"chat_id": "comments"
}
]
},
"forwards": {
"scopes": [
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
},
"import": {
"scopes": [
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
},
"export": {
"scopes": [
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
},
"remove": {
"scopes": [
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
},
"purge": {
"scopes": [
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
},
"shutdown": {
"scopes": [
{
"name": "BotCommandScopeChat",
"chat_id": "owner"
}
]
}
}
}

View File

@@ -1 +0,0 @@
[]

View File

@@ -1,4 +0,0 @@
{
"sent": [],
"captions": {}
}

View File

@@ -1 +0,0 @@
{}

1
docs/config_api.md Normal file
View File

@@ -0,0 +1 @@
# Configuring API connection

1
docs/config_channel.md Normal file
View File

@@ -0,0 +1 @@
# Configuring channel and adding your bot

1
docs/config_posts.md Normal file
View File

@@ -0,0 +1 @@
# Configuring posts and duplicates checker

1
docs/config_systemd.md Normal file
View File

@@ -0,0 +1 @@
# Configuring as systemd service

1
docs/config_time.md Normal file
View File

@@ -0,0 +1 @@
# Configuring posting time

1
docs/updating.md Normal file
View File

@@ -0,0 +1 @@
# Updating your bot

View File

@@ -1,27 +1,82 @@
{ {
"metadata": {
"flag": "🇬🇧",
"name": "English",
"codes": [
"en",
"en-US",
"en-GB"
]
},
"commands": { "commands": {
"start": "Start using the bot", "start": "Start using the bot",
"rules": "Photos submission rules" "rules": "Photos submission rules",
}, "language": "Change bot's language",
"commands_admin": { "report": "Report this post",
"reboot": "Restart the bot" "forwards": "Check post forwards",
"import": "Submit .zip archive with photos",
"export": "Get .zip archive with all photos",
"remove": "Delete photo by its ID",
"purge": "Completely purge bot's queue",
"shutdown": "Turn off the bot"
}, },
"message": { "message": {
"start": "Hi and welcome!\n\nYou can submit your pictures and videos here. We'll review and add them, if we like them. Make sure you send your stuff one at a time and have chosen media that corresponds to our rules.\n\nYou can also write something to us in the description field. We'll send it with the submission itself, if needed.\n\nAlso, make sure you follow the /rules of submission, otherwise your submission will be declined. In case of spam/abuse you may even be blocked.\n\nHave fun and happy submitting!", "start": "Hi and welcome!\n\nYou can submit your pictures and videos here. We'll review and add them, if we like them. Make sure you send your stuff one at a time and have chosen media that corresponds to our rules.\n\nYou can also write something to us in the description field. We'll send it with the submission itself, if needed.\n\nAlso, make sure you follow the /rules of submission, otherwise your submission will be declined. In case of spam/abuse you may even be blocked.\n\nHave fun and happy submitting!",
"rules": "Photos submission rules:\n1. No porn, only erotics and aesthetics\n2. Nipples are semi-allowed, should be either veiled or barely visible\n3. Genitalia strictly prohibited, but labia prints on clothes or nice pubes/panties/butts - are fine", "rules": "Photos submission rules:\n1. No porn, only erotics and aesthetics\n2. Nipples are semi-allowed, should be either veiled or barely visible\n3. Genitalia strictly prohibited, but labia prints on clothes or nice pubes/panties/butts - are fine\n4. Submitting russians is forbidden",
"shutdown": "Shutting down bot with pid `{0}`", "shutdown": "Shutting down bot with pid `{0}`",
"startup": "Starting with pid `{0}`", "startup": "Starting with pid `{0}`",
"startup_downtime_minutes": "Starting with pid `{0}` (was down for {1} m.)",
"startup_downtime_hours": "Starting with pid `{0}` (was down for {1} h.)",
"startup_downtime_days": "Starting with pid `{0}` (was down for {1} d.)",
"sub_yes": "✅ Submission approved and accepted", "sub_yes": "✅ Submission approved and accepted",
"sub_yes_auto": "✅ Submission automatically accepted",
"sub_no": "❌ Submission reviewed and declined", "sub_no": "❌ Submission reviewed and declined",
"sub_dup": "⚠️ Submission automatically declined because database already contains this photo",
"sub_deleted": "⚠️ Submission's database record ({0}) is not available.",
"sub_blocked": "You were blocked and you can't submit media anymore.", "sub_blocked": "You were blocked and you can't submit media anymore.",
"sub_unblocked": "You were unblocked and you can now submit media.", "sub_unblocked": "You were unblocked and you can now submit media.",
"sub_by": "\n\nSubmitted by:", "sub_by": "\n\nSubmitted by:",
"sub_sent": "Media has been submitted.\nWe'll notify you whether it will be accepted or not soon.", "sub_sent": "Media has been submitted.\nWe'll notify you whether it will be accepted or not soon.",
"sub_cooldown": "You can only submit 1 media per {0} seconds", "sub_cooldown": "You can only submit 1 media per {0} seconds",
"sub_media_failed": "Could not upload submission to API. Please check logs for details.",
"sub_media_duplicates": "⚠️ Image duplicates found",
"sub_media_duplicates_list": "It seems like following image has duplicates in API's Db.\n\nNext files marked as similar:\n • {0}",
"document_too_large": "File you've sent is too large. Please submit files not bigger than {0} MB", "document_too_large": "File you've sent is too large. Please submit files not bigger than {0} MB",
"mime_not_allowed": "File type not allowed. Please, consider using one of these: {0}", "mime_not_allowed": "File type not allowed. Please, consider using one of these: {0}",
"post_exception": "Could not send content due to `{exp}`\n\nTraceback:\n```{0}```", "post_exception": "Could not send content due to `{0}`\n\nTraceback:\n```{1}```",
"post_empty": "Could not send content: `Queue folder is empty or contains only unsupported or already sent files.`" "post_invalid_pic": "⚠️ Error {0} while sending photo\n```python\n{1}\n```",
"api_queue_empty": "Could not send content: `Queue is empty or contains only unsupported files.`",
"api_queue_error": "Could not get photo from API's queue. Check the log above or API's errors to get more info.",
"post_low": "Low amount of content: `There are only {0} files left in the queue.`",
"api_creds_invalid": "Could not authorize API access. Please check whether provided in config file are valid and update them if they're not.",
"sub_wip": "Post submission is now WIP. It will be available again in a few days. Thank you for your patience.",
"sub_error": "⚠️ Could not upload this image due to bot error. Admins are advised.",
"sub_error_admin": "User {0} could not submit photo without additional confirmation due to:\n```\n{1}\n```",
"import_request": "Please send me a zip archive with your media to be imported. Use /cancel if you want to abort this operation.",
"import_ignored": "No response, aborting import.",
"import_abort": "Import aborted.",
"import_invalid_media": "File to import must be a zip archive. Aborting.",
"import_invalid_mime": "Provided file is not supported. Please send `application/zip`. Aborting.",
"import_too_big": "You archive is `{0} GiB` big, but system has only `{1} GiB` free. Unpacking may take even more space. Aborting.",
"import_downloading": "Downloading archive...",
"import_unpacking": "Unpacking archive...",
"import_unpack_error": "Could not unpack the archive\n\nException: {0}\n\nTraceback:\n```python\n{1}\n```",
"import_uploading": "Uploading archive contents...",
"import_upload_error_duplicate": "Could not upload `{0}` because there're duplicates on server.",
"import_upload_error_other": "Could not upload `{0}`. Probably disallowed filetype.",
"import_finished": "Import finished.",
"locale_choice": "Alright. Please choose the language using keyboard below.",
"remove_request": "Please send me an ID to delete. You might have it from upload dialog. Use /cancel if you want to abort this operation.",
"remove_ignored": "No response, aborting removal.",
"remove_abort": "Removal aborted.",
"remove_success": "Removed media with ID `{0}`.",
"remove_failure": "Could not remove media with ID `{0}`. Check if provided ID is correct and if it is - you can also check bot's log for details.",
"remove_kind": "Please choose the type of media to delete. Use /cancel if you want to abort this operation.",
"remove_unknown": "Unknown media type. It can only be \"{0}\" or \"{1}\".",
"update_available": "**New version found**\nThere's a newer version of a bot found. You can update your bot to [{0}]({1}) using command line of your host.\n\n**Release notes**\n{2}\n\nRead more about updating you bot on the [wiki page](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Updating-Instance).\n\nPlease not that you can also disable this notification by editing `reports.update` key of the config.",
"shutdown_confirm": "There are {0} unfinished users' contexts. If you turn off the bot, those will be lost. Please confirm shutdown using a button below.",
"report_sent": "We've notified admins about presumable violation. Thank you for cooperation.",
"report_received": "This message has been reported by **{0}** (@{1}, `{2}`)"
}, },
"button": { "button": {
"sub_yes": "✅ Accept", "sub_yes": "✅ Accept",
@@ -29,7 +84,12 @@
"sub_no": "❌ Deny", "sub_no": "❌ Deny",
"sub_block": "☠️ Block sender", "sub_block": "☠️ Block sender",
"sub_unblock": "🏳️ Unblock sender", "sub_unblock": "🏳️ Unblock sender",
"post_view": "View in channel" "post_view": "View in channel",
"accepted": "✅ Accepted",
"declined": "❌ Declined",
"shutdown": "Confirm shutdown",
"photo": "Photo",
"video": "Video"
}, },
"callback": { "callback": {
"sub_yes": "✅ Submission approved", "sub_yes": "✅ Submission approved",
@@ -37,25 +97,10 @@
"sub_block": "User {0} has been blocked", "sub_block": "User {0} has been blocked",
"sub_unblock": "User {0} has been unblocked", "sub_unblock": "User {0} has been unblocked",
"sub_msg_unavail": "Submission message no longer exist", "sub_msg_unavail": "Submission message no longer exist",
"sub_media_unavail": "Could not download submission" "sub_media_unavail": "Could not download submission",
}, "sub_done": "You've already decided what to do with submission",
"console": { "sub_duplicates_found": "There're duplicates in bot's database",
"shutdown": "Shutting down bot with pid {0}", "locale_set": "Your language now is: {locale}",
"startup":"Starting with pid {0}", "nothing": "🏁 This action is already finished"
"keyboard_interrupt": "\nShutting down...",
"exception_occured": "Exception {0} happened on task execution",
"post_sent": "Sent {0} of type {1} to {2} with caption {3} and silently {4}",
"post_exception": "Could not send content due to {0}. Traceback: {1}",
"post_empty": "Could not send content due to queue folder empty with allowed extensions",
"deps_missing": "Required modules are not installed. Run 'pip3 install -r requirements.txt' and restart the program.",
"passed_norun": "Argument --norun passed, not running the main script",
"move_sent_doesnt_exist": "File '{0}' is already moved or does not exist",
"move_sent_doesnt_exception": "Could not move sent file '{0}' to '{1}' due to {2}",
"move_sent_completed": "Moved all sent files to the sent folder",
"cleanup_exception": "Could not remove '{0}' due to {1}",
"cleanup_completed": "Performed cleanup of the sent files",
"cleanup_unathorized": "Requested cleanup of sent files but not authorized. Please pass '--confirm' to perform that",
"cleanup_index_completed": "Performed cleanup of sent files index",
"cleanup_index_unathorized": "Requested cleanup of sent files index but not authorized. Please pass '--confirm' to perform that"
} }
} }

View File

@@ -1,27 +1,81 @@
{ {
"metadata": {
"flag": "🇺🇦",
"name": "Українська",
"codes": [
"uk",
"uk-UA"
]
},
"commands": { "commands": {
"start": "Почати користуватись ботом", "start": "Почати користуватись ботом",
"rules": "Правила пропонування фото" "rules": "Правила пропонування фото",
}, "language": "Змінити мову бота",
"commands_admin": { "report": "Поскаржитись на цей пост",
"reboot": "Перезапустити бота" "forwards": "Переглянути репости",
"import": "Надати боту .zip архів з фотографіями",
"export": "Отримати .zip архів з усіма фотографіями",
"remove": "Видалити фото за його ID",
"purge": "Повністю видалити всю чергу бота",
"shutdown": "Вимкнути бота"
}, },
"message": { "message": {
"start": "Привіт і ласкаво просимо!\n\nТут можна пропонувати свої фотографії та відео. Ми переглянемо та додамо їх, якщо вони нам сподобаються. Переконайтеся, що ви надсилаєте свої матеріали по одному та вибираєте медіа, які відповідають нашим правилам.\n\nВи також можете написати нам щось у полі опису. За потреби ми надішлемо це разом із самим фото.\n\nКрім того, переконайтеся, що ви дотримуєтеся /rules (правил) подання, інакше вашу пропозицію буде відхилено. У разі спаму/зловживань вас можуть навіть заблокувати.\n\nГарного дня та щасливого надсилання!", "start": "Привіт і ласкаво просимо!\n\nТут можна пропонувати свої фотографії та відео. Ми переглянемо та додамо їх, якщо вони нам сподобаються. Переконайтеся, що ви надсилаєте свої матеріали по одному та вибираєте медіа, які відповідають нашим правилам.\n\nВи також можете написати нам щось у полі опису. За потреби ми надішлемо це разом із самим фото.\n\nКрім того, переконайтеся, що ви дотримуєтеся /rules (правил) подання, інакше вашу пропозицію буде відхилено. У разі спаму/зловживань вас можуть навіть заблокувати.\n\nГарного дня та щасливого надсилання!",
"rules": "Правила пропонування фото:\n1. Ніякого порно, тільки еротика та естетика\n2. Соски можна, але або завуальовані, або зовсім ледь помітні\n3. Геніталії суворо ні, а ось відбитки статевих губ на одязі або гарні лобочки/трусики/попки - без проблем", "rules": "Правила пропонування фото:\n1. Ніякого порно, тільки еротика та естетика\n2. Соски можна, але або завуальовані, або зовсім ледь помітні\n3. Геніталії суворо ні, а ось відбитки статевих губ на одязі або гарні лобочки/трусики/попки - без проблем\n4. Пропонувати русню заборонено",
"shutdown": "Вимкнення бота з підом `{0}`", "shutdown": "Вимкнення бота з підом `{0}`",
"startup": "Запуск бота з підом `{0}`", "startup": "Запуск бота з підом `{0}`",
"startup_downtime_minutes": "Запуск бота з підом `{0}` (лежав {1} хв.)",
"startup_downtime_hours": "Запуск бота з підом `{0}` (лежав {1} год.)",
"startup_downtime_days": "Запуск бота з підом `{0}` (лежав {1} дн.)",
"sub_yes": "✅ Подання схвалено та прийнято", "sub_yes": "✅ Подання схвалено та прийнято",
"sub_yes_auto": "✅ Подання автоматично прийнято",
"sub_no": "❌ Подання розглянуто та відхилено", "sub_no": "❌ Подання розглянуто та відхилено",
"sub_blocked": "Вас заблокували, і ви більше не можете надсилати медіафайли.", "sub_dup": "⚠️ Подання автоматично відхилено через наявність цього фото в базі даних",
"sub_unblocked": "Вас розблокували, і тепер ви можете надсилати медіафайли.", "sub_deleted": "⚠️ Запис подання у базі даних ({0}) недоступний.",
"sub_blocked": "Вас заблокували, ви більше не можете надсилати медіафайли.",
"sub_unblocked": "Вас розблокували, тепер ви можете надсилати медіафайли.",
"sub_by": "\n\nПредставлено:", "sub_by": "\n\nПредставлено:",
"sub_sent": "Медіа-файл надіслано.\nСкоро ми повідомимо вас, чи буде його прийнято.", "sub_sent": "Медіа-файл надіслано.\nСкоро ми повідомимо вас, чи буде його прийнято.",
"sub_cooldown": "Ви можете надсилати лише 1 медіафайл на {0} секунд", "sub_cooldown": "Ви можете надсилати лише 1 медіафайл на {0} секунд",
"sub_media_failed": "Не вдалося завантажити подання на сервер. Перевірте логи для деталей.",
"sub_media_duplicates": "⚠️ Знайдено зображення-дублікати",
"sub_media_duplicates_list": "Здається, подане зображення має дублікати в базі даних.\n\nНаступні файли було відмічено як дуже схожі з поданням:\n • {0}",
"document_too_large": "Надісланий файл завеликий. Будь ласка, надсилайте файли не більше {0} Мб", "document_too_large": "Надісланий файл завеликий. Будь ласка, надсилайте файли не більше {0} Мб",
"mime_not_allowed": "Тип файлу не дозволений. Розгляньте можливість використання одного з цих: {0}", "mime_not_allowed": "Тип файлу не дозволений. Розгляньте можливість використання одного з цих: {0}",
"post_exception": "Не вдалося надіслати контент через `{exp}`\n\nTraceback:\n```{0}```", "post_exception": "Не вдалося надіслати контент через `{0}`\n\nTraceback:\n```{1}```",
"post_empty": "Не вдалося надіслати контент: «Папка черги порожня або містить лише непідтримувані або вже надіслані файли»." "post_invalid_pic": "⚠️ Помилка надсилання фото {0}\n```python\n{1}\n```",
"api_queue_empty": "Не вдалося надіслати контент: `Черга порожня або містить лише непідтримувані файли`.",
"api_queue_error": "Не вдалось отримати фото з черги API. Погляньте на логи вище а також на лог помилок API щоб дізнатись подробиці.",
"post_low": "Мала кількість контенту: `Залишилось всього {0} файлів в черзі.`",
"api_creds_invalid": "Не вдалося авторизувати запит до API. Будь ласка, перевірте чи дані авторизації в конфігураційному файлі вірні та оновіть їх, якщо це не так.",
"sub_wip": "Подання постів зараз знаходиться у розробці. Він буде знову доступний через кілька днів. Дякуємо за ваше терпіння.",
"sub_error": "⚠️ Не вдалось завантажити фото через помилку бота. Адміністрацію повідомлено.",
"sub_error_admin": "Користувач {0} не зміг надіслати фото без додаткової перевірки через помилку:\n```\n{1}\n```",
"import_request": "Будь ласка, надішліть zip-архів з медіа для імпортування. Використовуйте /cancel, якщо ви хочете перервати цю операцію.",
"import_ignored": "Немає відповіді, перериваємо імпорт.",
"import_abort": "Імпорт перервано.",
"import_invalid_media": "Файл для імпорту має бути zip-архівом. Перериваємо.",
"import_invalid_mime": "Наданий файл не підтримується. Будь ласка, надішліть `application/zip`. Перервано.",
"import_too_big": "Ваш архів має розмір `{0} GiB`, але система має лише `{1} GiB` вільних. Розпакування може зайняти значно більше місця. Перервано.",
"import_downloading": "Завантажуємо архів...",
"import_unpacking": "Розпаковуємо архів...",
"import_unpack_error": "Не вдалося розпакувати архів\n\nПомилка: {0}\n\nTraceback:\n```python\n{1}\n```",
"import_uploading": "Завантажуємо вміст архіву...",
"import_upload_error_duplicate": "Не вдалося завантажити `{0}`, оскільки на сервері є дублікати.",
"import_upload_error_other": "Не вдалося завантажити `{0}`. Ймовірно, заборонений тип файлу.",
"import_finished": "Імпорт завершено.",
"locale_choice": "Гаразд. Будь ласка, оберіть мову за допомогою клавіатури нижче.",
"remove_request": "Будь ласка, надішліть мені ID для видалення. Ви могли отримати його з діалогу завантаження. Використовуйте /cancel, якщо ви хочете перервати цю операцію.",
"remove_ignored": "Немає відповіді, перериваємо видалення.",
"remove_abort": "Видалення перервано.",
"remove_success": "Видалено медіа з ID `{0}`.",
"remove_failure": "Не вдалося видалити медіа з ID `{0}`. Перевірте, чи вказано правильний ID, і якщо він правильний, ви також можете переглянути логи бота для отримання більш детальної інформації.",
"remove_kind": "Будь ласка, оберіть тип контенту для видалення. Використовуйте /cancel, якщо ви хочете перервати цю операцію.",
"remove_unknown": "Невідомий тип контенту. Може бути тільки \"{0}\" або \"{1}\".",
"update_available": "**Знайдено нову версію**\nЗнайдено нову версію бота. Ви можете оновити бота до [{0}]({1}) за допомогою командного рядка вашого хосту.\n\n**Примітки до релізу**\n{2}\n\nДетальніше про оновлення бота можна знайти на [вікі-сторінці](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Updating-Instance).\n\nЗверніть увагу, що ви також можете вимкнути це сповіщення, відредагувавши ключ `reports.update` у конфігурації.",
"shutdown_confirm": "Існує {0} незавершених контекстів користувачів. Якщо ви вимкнете бота, вони будуть втрачені. Будь ласка, підтвердіть вимкнення за допомогою кнопки нижче.",
"report_sent": "Ми повідомили адміністрацію про потенційне порушення. Дякую за співпрацю.",
"report_received": "На це повідомлення було отримано скаргу від **{0}** (@{1}, `{2}`)"
}, },
"button": { "button": {
"sub_yes": "✅ Прийняти", "sub_yes": "✅ Прийняти",
@@ -29,7 +83,12 @@
"sub_no": "❌ Відхилити", "sub_no": "❌ Відхилити",
"sub_block": "☠️ Заблокувати відправника", "sub_block": "☠️ Заблокувати відправника",
"sub_unblock": "🏳️ Розблокувати відправника", "sub_unblock": "🏳️ Розблокувати відправника",
"post_view": "Переглянути на каналі" "post_view": "Переглянути на каналі",
"accepted": "✅ Прийнято",
"declined": "❌ Відхилено",
"shutdown": "Підтвердити вимкнення",
"photo": "Фото",
"video": "Відео"
}, },
"callback": { "callback": {
"sub_yes": "✅ Подання схвалено", "sub_yes": "✅ Подання схвалено",
@@ -37,25 +96,10 @@
"sub_block": "Користувача {0} заблоковано", "sub_block": "Користувача {0} заблоковано",
"sub_unblock": "Користувача {0} розблоковано", "sub_unblock": "Користувача {0} розблоковано",
"sub_msg_unavail": "Повідомлення більше не існує", "sub_msg_unavail": "Повідомлення більше не існує",
"sub_media_unavail": "Не вдалося завантажити подання" "sub_media_unavail": "Не вдалося завантажити подання",
}, "sub_done": "Ви вже обрали що зробити з цим поданням",
"console": { "sub_duplicates_found": "Знайдено дублікати в базі даних бота",
"shutdown": "Вимкнення бота з підом {0}", "locale_set": "Встановлено мову: {locale}",
"startup": "Запуск бота з підом {0}", "nothing": "🏁 Цю дію вже було завершено"
"keyboard_interrupt": "\nВимикаюсь...",
"exception_occured": "Помилка {0} сталась під час виконання",
"post_sent": "Надіслано {0} типу {1} у {2} з підписом {3} та без звуку {4}",
"post_exception": "Не вдалося надіслати контент через {0}. Traceback: {1}",
"post_empty": "Не вдалося надіслати контент через порожню папку черги з дозволеними розширеннями",
"deps_missing": "Необхідні модулі не встановлені. Запустіть 'pip3 install -r requirements.txt' і перезапустіть програму.",
"passed_norun": "Аргумент --norun надано, основний скрипт не запускається",
"move_sent_doesnt_exist": "Файл '{0}' уже переміщено або він не існує",
"move_sent_doesnt_exception": "Неможливо перемістити надісланий файл '{0}' до '{1}' через {2}",
"move_sent_completed": "Переміщено всі надіслані файли до папки надісланих",
"cleanup_exception": "Не вдалося видалити '{0}' через {1}",
"cleanup_completed": "Виконано очищення надісланих файлів",
"cleanup_unathorized": "Надіслано запит на очищення надісланих файлів, але не авторизовано. Для цього надайте аргумент '--confirm'",
"cleanup_index_completed": "Виконано очищення індексу надісланих файлів",
"cleanup_index_unathorized": "Надіслано запит на очищення індексу надісланих файлів, але не авторизовано. Для цього надайте аргумент '--confirm'"
} }
} }

413
main.py
View File

@@ -1,394 +1,43 @@
import os import contextlib
import random import logging
import shutil from os import getpid
import sys
from threading import Thread
import time
import traceback
from pathlib import Path
from modules.logging import logWrite from convopyro import Conversation
from modules.utils import configGet, jsonLoad, jsonSave, killProc, locale
# Args ===================================================================================================================================== # This import MUST be done earlier than PyroClient!
if "--move-sent" in sys.argv: # Even if isort does not like it...
for entry in jsonLoad(configGet("index", "locations"))["sent"]: from modules.cli import *
try:
shutil.move(configGet("queue", "locations")+os.sep+entry, configGet("sent", "locations")+os.sep+entry)
except FileNotFoundError:
logWrite(locale("move_sent_doesnt_exist", "console", locale=configGet("locale")).format(entry))
except Exception as exp:
logWrite(locale("move_sent_doesnt_exception", "console", locale=configGet("locale")).format(entry, exp))
logWrite(locale("move_sent_completed", "console", locale=configGet("locale")))
if "--cleanup" in sys.argv: from classes.pyroclient import PyroClient
if "--confirm" in sys.argv: from modules.scheduler import scheduler
index = jsonLoad(configGet("index", "locations"))
for entry in index["sent"]:
try:
try:
os.remove(configGet("queue", "locations")+os.sep+entry)
except FileNotFoundError:
pass
try:
os.remove(configGet("sent", "locations")+os.sep+entry)
except FileNotFoundError:
pass
except Exception as exp:
logWrite(locale("cleanup_exception", "console", locale=configGet("locale")).format(entry, exp))
jsonSave(index, jsonLoad(configGet("index", "locations")))
logWrite(locale("cleanup_completed", "console", locale=configGet("locale")))
else:
logWrite(locale("cleanup_unathorized", "console", locale=configGet("locale")))
if "--cleanup-index" in sys.argv: logging.basicConfig(
if "--confirm" in sys.argv: level=logging.INFO,
index = jsonLoad(configGet("index", "locations")) format="%(name)s.%(funcName)s | %(levelname)s | %(message)s",
index["sent"] = [] datefmt="[%X]",
jsonSave(index, jsonLoad(configGet("index", "locations"))) )
logWrite(locale("cleanup_index_completed", "console", locale=configGet("locale")))
else:
logWrite(locale("cleanup_index_unathorized", "console", locale=configGet("locale")))
if "--norun" in sys.argv: logger = logging.getLogger(__name__)
logWrite(locale("passed_norun", "console", locale=configGet("locale")))
sys.exit() with contextlib.suppress(ImportError):
#=========================================================================================================================================== import uvloop
uvloop.install()
# Import =================================================================================================================================== def main():
try: client = PyroClient(scheduler=scheduler)
import schedule # type: ignore Conversation(client)
from pyrogram import Client, filters, idle # type: ignore
from pyrogram.types import ChatPermissions, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton, BotCommand, BotCommandScopeChat # type: ignore
except ModuleNotFoundError:
print(locale("deps_missing", "console", locale=configGet("locale")), flush=True)
sys.exit()
#===========================================================================================================================================
pid = os.getpid()
app = Client("duptsiaposter", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot"))
def send_content():
try: try:
client.run()
list_sent = jsonLoad(configGet("index", "locations"))
list_queue = os.listdir(configGet("queue", "locations"))
for file in list_queue:
if not file in list_sent["sent"]:
ext_match = False
for ext in configGet("photo", "posting", "extensions"):
if file.endswith(ext):
ext_match = True
ext_type = "photo"
break
for ext in configGet("video", "posting", "extensions"):
if file.endswith(ext):
ext_match = True
ext_type = "video"
break
if not ext_match:
list_queue.remove(file)
else:
list_queue.remove(file)
if len(list_queue) > 0:
candidate_file = random.choice(list_queue)
candidate = configGet("queue", "locations")+os.sep+candidate_file
else:
logWrite(locale("post_empty", "console", locale=configGet("locale")))
if configGet("error", "reports"):
app.send_message(configGet("admin"), locale("post_empty", "message", locale=configGet("locale"))) # type: ignore
return
index = jsonLoad(configGet("index", "locations"))
if candidate_file in index["captions"]:
caption = index["captions"][candidate_file]
else:
caption = ""
if configGet("enabled", "caption"):
if configGet("link", "caption") != None:
caption = f"{caption}\n\n[{configGet('text', 'caption')}]({configGet('link', 'caption')})"
else:
caption = f"{caption}\n\n{configGet('text', 'caption')}"
else:
caption = caption
if ext_type == "photo": # type: ignore
if configGet("enabled", "caption"):
if configGet("link", "caption") != None:
sent = app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore
else:
sent = app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore
else:
sent = app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore
elif ext_type == "video": # type: ignore
if configGet("enabled", "caption"):
if configGet("link", "caption") != None:
sent = app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore
else:
sent = app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore
else:
sent = app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore
else:
return
list_sent["sent"].append(candidate_file)
jsonSave(list_sent, configGet("index", "locations"))
if configGet("move_sent", "posting"):
shutil.move(candidate, configGet("sent", "locations")+os.sep+candidate_file)
logWrite(locale("post_sent", "console", locale=configGet("locale")).format(candidate, ext_type, str(configGet("channel", "posting")), caption.replace("\n", "%n"), str(configGet("silent", "posting")))) # type: ignore
if configGet("sent", "reports"):
app.send_message(configGet("admin"), f"Posted `{candidate_file}`", disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup([
[InlineKeyboardButton(locale("post_view", "button", locale=configGet("locale")), url=sent.link)] # type: ignore
])) # type: ignore
except Exception as exp:
logWrite(locale("post_exception", "console", locale=configGet("locale")).format(str(exp), traceback.format_exc()))
if configGet("error", "reports"):
app.send_message(configGet("admin"), locale("post_exception", "message", locale=configGet("locale")).format(exp, traceback.format_exc())) # type: ignore
pass
@app.on_message(~ filters.scheduled & filters.command(["start"], prefixes="/"))
def cmd_start(app, msg):
if msg.from_user.id not in jsonLoad(configGet("blocked", "locations")):
msg.reply_text(locale("start", "message", locale=msg.from_user.language_code))
@app.on_message(~ filters.scheduled & filters.command(["rules", "help"], prefixes="/"))
def cmd_rules(app, msg):
if msg.from_user.id not in jsonLoad(configGet("blocked", "locations")):
msg.reply_text(locale("rules", "message", locale=msg.from_user.language_code))
@app.on_message(~ filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"]))
def cmd_kill(app, msg):
if msg.from_user.id == configGet("admin"):
logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid)))
msg.reply_text(locale("shutdown", "message", locale=configGet("locale")).format(str(pid)))
killProc(pid)
# Submission =====================================================================================================================================
def subLimit(user):
submit = jsonLoad(configGet("submit", "locations"))
submit[str(user.id)] = time.time()
jsonSave(submit, configGet("submit", "locations"))
def subLimited(user):
if user.id == configGet("admin"):
return False
else:
submit = jsonLoad(configGet("submit", "locations"))
if str(user.id) in submit:
if (time.time() - submit[str(user.id)]) < configGet("timeout", "submission"):
return True
else:
return False
else:
return False
def subBlock(user):
blocked = jsonLoad(configGet("blocked", "locations"))
if user not in blocked:
blocked.append(user)
jsonSave(blocked, configGet("blocked", "locations"))
def subUnblock(user):
blocked = jsonLoad(configGet("blocked", "locations"))
if user in blocked:
blocked.remove(user)
jsonSave(blocked, configGet("blocked", "locations"))
@app.on_message(~ filters.scheduled & filters.photo | filters.video | filters.animation | filters.document)
def get_submission(_, msg):
if msg.from_user.id not in jsonLoad(configGet("blocked", "locations")):
user_locale = msg.from_user.language_code
if not subLimited(msg.from_user):
if msg.document != None:
if msg.document.mime_type not in configGet("mime_types", "submission"):
msg.reply_text(locale("mime_not_allowed", "message", locale=user_locale), quote=True)
return
if msg.document.file_size > configGet("file_size", "submission"):
msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True)
return
if msg.video != None:
if msg.video.file_size > configGet("file_size", "submission"):
msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True)
return
buttons = [
[
InlineKeyboardButton(text=locale("sub_yes", "button", locale=configGet("locale")), callback_data=f"sub_yes_{msg.from_user.id}_{msg.id}")
]
]
if msg.caption != None:
caption = str(msg.caption)
buttons[0].append(
InlineKeyboardButton(text=locale("sub_yes_caption", "button", locale=configGet("locale")), callback_data=f"sub_yes_{msg.from_user.id}_{msg.id}_caption")
)
buttons[0].append(
InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{msg.from_user.id}_{msg.id}")
)
else:
caption = ""
buttons[0].append(
InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{msg.from_user.id}_{msg.id}")
)
caption += locale("sub_by", "message", locale=locale(configGet("locale")))
if msg.from_user.first_name != None:
caption += f" {msg.from_user.first_name}"
if msg.from_user.last_name != None:
caption += f" {msg.from_user.last_name}"
if msg.from_user.username != None:
caption += f" (@{msg.from_user.username})"
if msg.from_user.phone_number != None:
caption += f" ({msg.from_user.phone_number})"
msg.copy(configGet("admin"), caption=caption, reply_markup=InlineKeyboardMarkup(buttons))
if msg.from_user.id != configGet("admin"):
buttons += [
[
InlineKeyboardButton(text=locale("sub_block", "button", locale=configGet("locale")), callback_data=f"sub_block_{msg.from_user.id}")
],
[
InlineKeyboardButton(text=locale("sub_unblock", "button", locale=configGet("locale")), callback_data=f"sub_unblock_{msg.from_user.id}")
]
]
msg.reply_text(locale("sub_sent", "message", locale=user_locale), quote=True)
subLimit(msg.from_user)
else:
msg.reply_text(locale("sub_cooldown", "message", locale=user_locale).format(str(configGet("timeout", "submission"))))
@app.on_callback_query(filters.regex("sub_yes_[\s\S]*_[\s\S]*")) # type: ignore
def callback_query_yes(app, clb): # type: ignore
fullclb = clb.data.split("_")
user_locale = clb.from_user.language_code
try:
submission = app.get_messages(int(fullclb[2]), int(fullclb[3]))
except:
clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True)
return
try:
media = app.download_media(submission, file_name=configGet("queue", "locations")+os.sep)
if clb.data.endswith("_caption"):
index = jsonLoad(configGet("index", "locations"))
index["captions"][Path(media).name] = submission.caption
jsonSave(index, configGet("index", "locations"))
except:
clb.answer(text=locale("sub_media_unavail", "message", locale=user_locale), show_alert=True)
return
submission.reply_text(locale("sub_yes", "message", locale=submission.from_user.language_code), quote=True)
clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True)
@app.on_callback_query(filters.regex("sub_no_[\s\S]*_[\s\S]*")) # type: ignore
def callback_query_no(app, clb): # type: ignore
fullclb = clb.data.split("_")
user_locale = clb.from_user.language_code
try:
submission = app.get_messages(int(fullclb[2]), int(fullclb[3]))
except:
clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True)
return
submission.reply_text(locale("sub_no", "message", locale=submission.from_user.language_code), quote=True)
clb.answer(text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]), show_alert=True)
@app.on_callback_query(filters.regex("sub_block_[\s\S]*")) # type: ignore
def callback_query_block(app, clb): # type: ignore
fullclb = clb.data.split("_")
user_locale = clb.from_user.language_code
app.send_message(int(fullclb[2]), locale("sub_msg_unavail", "message", locale=configGet("locale")))
subBlock(int(fullclb[2]))
clb.answer(text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]), show_alert=True)
@app.on_callback_query(filters.regex("sub_unblock_[\s\S]*")) # type: ignore
def callback_query_unblock(app, clb): # type: ignore
fullclb = clb.data.split("_")
user_locale = clb.from_user.language_code
app.send_message(int(fullclb[2]), locale("sub_msg_unavail", "message", locale=configGet("locale")))
subUnblock(int(fullclb[2]))
clb.answer(text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]), show_alert=True)
#===========================================================================================================================================
for entry in configGet("time", "posting"):
schedule.every().day.at(entry).do(send_content)
def background_task():
try:
while True:
try:
schedule.run_pending()
time.sleep(1)
except:
pass
except Exception as exp:
logWrite(locale("exception_occured", "console", locale=configGet("locale")).format(exp))
except KeyboardInterrupt: except KeyboardInterrupt:
logWrite(locale("keyboard_interrupt", "console", locale=configGet("locale"))) logger.warning("Forcefully shutting down with PID %s...", getpid())
if configGet("shutdown", "reports"): finally:
app.send_message(configGet("admin"), locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) # type: ignore if client.scheduler is not None:
killProc(pid) client.scheduler.shutdown()
exit()
if __name__ == "__main__": if __name__ == "__main__":
main()
logWrite(locale("startup", "console", locale=configGet("locale")).format(str(pid)))
app.start() # type: ignore
if configGet("startup", "reports"):
app.send_message(configGet("admin"), locale("startup", "message", locale=configGet("locale")).format(str(pid))) # type: ignore
t = Thread(target=background_task)
t.start()
for entry in os.listdir(configGet("locale", "locations")):
if entry.endswith(".json"):
commands_list = []
for command in configGet("commands"):
commands_list.append(BotCommand(command, locale(command, "commands", locale=entry.replace(".json", ""))))
app.set_bot_commands(commands_list, language_code=entry.replace(".json", "")) # type: ignore
commands_list = []
for command in configGet("commands"):
commands_list.append(BotCommand(command, locale(command, "commands", locale=configGet("locale_fallback"))))
app.set_bot_commands(commands_list) # type: ignore
app.set_bot_commands([ # type: ignore
BotCommand("reboot", locale("reboot", "commands_admin", locale=configGet("locale"))),
],
scope=BotCommandScopeChat(chat_id=configGet("admin")))
idle()
app.send_message(configGet("admin"), locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) # type: ignore
logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid)))
killProc(pid)

149
modules/api_client.py Normal file
View File

@@ -0,0 +1,149 @@
import asyncio
import logging
from base64 import b64decode, b64encode
from os import makedirs, path, sep
from pathlib import Path
from typing import Union
import aiofiles
from aiohttp import ClientSession
from libbot import config_get, i18n, sync
from photosapi_client import AuthenticatedClient, Client
from photosapi_client.api.default.album_create_albums_post import (
asyncio as album_create,
)
from photosapi_client.api.default.album_delete_album_id_delete import (
asyncio as album_delete,
)
from photosapi_client.api.default.album_find_albums_get import asyncio as album_find
from photosapi_client.api.default.login_for_access_token_token_post import sync as login
from photosapi_client.api.default.photo_delete_photos_id_delete import (
asyncio as photo_delete,
)
from photosapi_client.api.default.photo_find_albums_album_photos_get import (
asyncio as photo_find,
)
from photosapi_client.api.default.photo_get_photos_id_get import asyncio as photo_get
from photosapi_client.api.default.photo_patch_photos_id_patch import (
asyncio as photo_patch,
)
from photosapi_client.api.default.photo_random_albums_album_photos_random_get import (
asyncio as photo_random,
)
from photosapi_client.api.default.photo_upload_albums_album_photos_post import (
asyncio_detailed as photo_upload,
)
from photosapi_client.api.default.user_create_users_post import asyncio as user_create
from photosapi_client.api.default.user_me_users_me_get import sync as user_me
from photosapi_client.api.default.video_delete_videos_id_delete import (
asyncio as video_delete,
)
from photosapi_client.api.default.video_find_albums_album_videos_get import (
asyncio as video_find,
)
from photosapi_client.api.default.video_get_videos_id_get import asyncio as video_get
from photosapi_client.api.default.video_patch_videos_id_patch import (
asyncio as video_patch,
)
from photosapi_client.api.default.video_random_albums_album_videos_random_get import (
asyncio as video_random,
)
from photosapi_client.api.default.video_upload_albums_album_videos_post import (
asyncio as video_upload,
)
from photosapi_client.models.body_login_for_access_token_token_post import (
BodyLoginForAccessTokenTokenPost,
)
from photosapi_client.models.body_photo_upload_albums_album_photos_post import (
BodyPhotoUploadAlbumsAlbumPhotosPost as BodyPhotoUpload,
)
from photosapi_client.models.body_video_upload_albums_album_videos_post import (
BodyVideoUploadAlbumsAlbumVideosPost as BodyVideoUpload,
)
from photosapi_client.models.http_validation_error import HTTPValidationError
from photosapi_client.models.photo import Photo
from photosapi_client.models.photo_search import PhotoSearch
from photosapi_client.models.token import Token
from photosapi_client.models.video import Video
from photosapi_client.models.video_search import VideoSearch
from photosapi_client.types import File
from modules.http_client import http_session
logger = logging.getLogger(__name__)
async def authorize(custom_session: Union[ClientSession, None] = None) -> str:
makedirs(await config_get("cache", "locations"), exist_ok=True)
session = http_session if custom_session is None else custom_session
if path.exists(await config_get("cache", "locations") + sep + "api_access") is True:
async with aiofiles.open(
await config_get("cache", "locations") + sep + "api_access", "rb"
) as file:
token = b64decode(await file.read()).decode("utf-8")
if (
await session.get(
await config_get("address", "posting", "api") + "/users/me/",
headers={"Authorization": f"Bearer {token}"},
)
).status == 200:
return token
payload = {
"grant_type": "password",
"scope": "me albums.list albums.read albums.write photos.list photos.read photos.write videos.list videos.read videos.write",
"username": await config_get("username", "posting", "api"),
"password": await config_get("password", "posting", "api"),
}
response = await session.post(
await config_get("address", "posting", "api") + "/token", data=payload
)
if not response.ok:
logger.warning(
"Incorrect API credentials! Could not login into '%s' using login '%s': HTTP %s",
await config_get("address", "posting", "api"),
await config_get("username", "posting", "api"),
response.status,
)
raise ValueError
async with aiofiles.open(
str(Path(f"{await config_get('cache', 'locations')}/api_access")),
"wb",
) as file:
await file.write(
b64encode((await response.json())["access_token"].encode("utf-8"))
)
return (await response.json())["access_token"]
unauthorized_client = Client(
sync.config_get("address", "posting", "api"),
raise_on_unexpected_status=True,
timeout=sync.config_get("timeout", "posting", "api"),
)
login_token = login(
client=unauthorized_client,
body=BodyLoginForAccessTokenTokenPost(
grant_type="password",
scope="me albums.list albums.read albums.write photos.list photos.read photos.write videos.list videos.read videos.write",
username=sync.config_get("username", "posting", "api"),
password=sync.config_get("password", "posting", "api"),
),
)
if not isinstance(login_token, Token):
logger.warning(
"Could not initialize connection due to invalid token: %s", login_token
)
exit()
client = AuthenticatedClient(
sync.config_get("address", "posting", "api"),
token=login_token.access_token,
raise_on_unexpected_status=True,
timeout=sync.config_get("timeout", "posting", "api"),
)
if __name__ == "__main__":
print(asyncio.run(authorize()))

118
modules/cli.py Normal file
View File

@@ -0,0 +1,118 @@
import asyncio
from argparse import ArgumentParser
from sys import exit
from traceback import print_exc
from libbot import config_get, config_set, sync
from photosapi_client.api.default.album_create_albums_post import (
asyncio as album_create,
)
from photosapi_client.api.default.login_for_access_token_token_post import (
asyncio as login,
)
from photosapi_client.api.default.user_create_users_post import asyncio as user_create
from photosapi_client.client import AuthenticatedClient, Client
from photosapi_client.models.body_login_for_access_token_token_post import (
BodyLoginForAccessTokenTokenPost,
)
from photosapi_client.models.body_user_create_users_post import BodyUserCreateUsersPost
parser = ArgumentParser(
prog="Telegram Poster",
description="Bot for posting some of your stuff and also receiving submissions.",
)
parser.add_argument("--create-user", action="store_true")
parser.add_argument("--create-album", action="store_true")
args = parser.parse_args()
if args.create_user or args.create_album:
unauthorized_client = Client(
base_url=sync.config_get("address", "posting", "api"),
timeout=5.0,
verify_ssl=True,
raise_on_unexpected_status=True,
follow_redirects=False,
)
async def cli_create_user() -> None:
print(
"To set up Photos API connection you need to create a new user.\nIf you have email confirmation enabled in your Photos API config - you need to use a real email that will get a confirmation code afterwards.",
flush=True,
)
username = input("Choose username for new Photos API user: ").strip()
email = input(f"Choose email for user '{username}': ").strip()
password = input(f"Choose password for user '{username}': ").strip()
try:
result_1 = await user_create(
client=unauthorized_client,
form_data=BodyUserCreateUsersPost(
user=username, email=email, password=password
),
)
# asyncio.run(create_user(username, email, password))
await config_set("username", username, "posting", "api")
await config_set("password", password, "posting", "api")
none = input(
"Alright. If you have email confirmation enabled - please confirm registration by using the link in your email. After that press Enter. Otherwise just press Enter."
)
except Exception as exc:
print(f"Could not create a user due to {exc}", flush=True)
print_exc()
exit()
if not args.create_album:
print("You're done!", flush=True)
exit()
return None
async def cli_create_album() -> None:
print(
"To use Photos API your user needs to have an album to store its data.\nThis wizard will help you to create a new album with its name and title.",
flush=True,
)
name = input("Choose a name for your album: ").strip()
title = input(f"Choose a title for album '{name}': ").strip()
try:
login_token = await login(
client=unauthorized_client,
form_data=BodyLoginForAccessTokenTokenPost(
grant_type="password",
scope="me albums.list albums.read albums.write photos.list photos.read photos.write videos.list videos.read videos.write",
username=await config_get("username", "posting", "api"),
password=await config_get("password", "posting", "api"),
),
)
client = AuthenticatedClient(
base_url=await config_get("address", "posting", "api"),
timeout=5.0,
verify_ssl=True,
raise_on_unexpected_status=True,
token=login_token.access_token,
follow_redirects=False,
)
result_2 = await album_create(client=client, name=name, title=title)
# asyncio.run(create_album(name, title))
await config_set("album", name, "posting", "api")
except Exception as exc:
print(f"Could not create an album due to {exc}", flush=True)
print_exc()
exit()
print("You're done!", flush=True)
exit()
if args.create_user or args.create_album:
loop = asyncio.get_event_loop()
tasks = []
if args.create_user:
loop.run_until_complete(asyncio.wait([loop.create_task(cli_create_user())]))
if args.create_album:
loop.run_until_complete(asyncio.wait([loop.create_task(cli_create_album())]))
loop.close()

View File

@@ -1,22 +0,0 @@
RESET = '\u001b[0m'
BLACK = '\u001b[30m'
RED = '\u001b[31m'
GREEN = '\u001b[32m'
YELLOW = '\u001b[33m'
BLUE = '\u001b[34m'
MAGENTA = '\u001b[35m'
CYAN = '\u001b[36m'
WHITE = '\u001b[37m'
BBLACK = '\u001b[30;1m'
BRED = '\u001b[31;1m'
BGREEN = '\u001b[32;1m'
BYELLOW = '\u001b[33;1m'
BBLUE = '\u001b[34;1m'
BMAGENTA = '\u001b[35;1m'
BCYAN = '\u001b[36;1m'
BWHITE = '\u001b[37;1m'
ULINE = '\u001b[4m'
REVERSE = '\u001b[7m'

18
modules/custom_filters.py Normal file
View File

@@ -0,0 +1,18 @@
"""Custom message filters"""
from pyrogram import filters
from pyrogram.types import Message
from classes.pyroclient import PyroClient
async def _mode_post_func(_, __: PyroClient, message: Message):
return __.config["mode"]["post"]
async def _mode_submit_func(_, __: PyroClient, message: Message):
return __.config["mode"]["submit"]
mode_post = filters.create(_mode_post_func)
mode_submit = filters.create(_mode_submit_func)

26
modules/database.py Normal file
View File

@@ -0,0 +1,26 @@
"""Module that provides all database columns"""
from async_pymongo import AsyncClient
from libbot import sync
db_config = sync.config_get("database")
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 = db_client.get_database(name=db_config["name"])
col_sent = db.get_collection("sent")
col_users = db.get_collection("users")
col_submitted = db.get_collection("submitted")

6
modules/http_client.py Normal file
View File

@@ -0,0 +1,6 @@
from aiohttp import ClientSession
from ujson import dumps
http_session = ClientSession(
json_serialize=dumps,
)

View File

@@ -1,48 +0,0 @@
import os
import gzip
import json
import shutil
from datetime import datetime
with open(os.getcwd()+os.path.sep+"config.json", "r", encoding='utf8') as file:
json_contents = json.loads(file.read())
log_size = json_contents["logging"]["size"]
log_folder = json_contents["logging"]["location"]
file.close()
# Check latest log size
def checkSize():
global log_folder
try:
os.makedirs(log_folder, exist_ok=True)
log = os.stat(os.path.join(log_folder, "latest.log"))
if (log.st_size / 1024) > log_size:
with open(os.path.join(log_folder, "latest.log"), 'rb') as f_in:
with gzip.open(os.path.join(log_folder, f'{datetime.now().strftime("%d.%m.%Y_%H:%M:%S")}.log.gz'), 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
print(f'Copied {os.path.join(log_folder, datetime.now().strftime("%d.%m.%Y_%H:%M:%S"))}.log.gz')
open(os.path.join(log_folder, "latest.log"), 'w').close()
except FileNotFoundError:
print(f'Log file {os.path.join(log_folder, "latest.log")} does not exist')
pass
# Append string to log
def logAppend(message):
global log_folder
message_formatted = f'[{datetime.now().strftime("%d.%m.%Y")}] [{datetime.now().strftime("%H:%M:%S")}] {message}'
checkSize()
log = open(os.path.join(log_folder, "latest.log"), 'a')
log.write(f'{message_formatted}\n')
log.close()
# Print to stdout and then to log
def logWrite(message):
# save to log file and rotation is to be done
logAppend(f'{message}')
print(f"{message}", flush=True)

3
modules/scheduler.py Normal file
View File

@@ -0,0 +1,3 @@
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()

270
modules/sender.py Normal file
View File

@@ -0,0 +1,270 @@
import logging
from datetime import datetime
from os import makedirs, path
from random import choice, sample
from shutil import rmtree
from traceback import format_exc, print_exc
from typing import Union
from uuid import uuid4
import aiofiles
from aiohttp import ClientSession
from libbot.pyrogram.classes import PyroClient
from photosapi_client.errors import UnexpectedStatus
from PIL import Image
from modules.api_client import (
File,
PhotoSearch,
VideoSearch,
authorize,
client,
photo_get,
photo_patch,
photo_random,
video_get,
video_patch,
video_random,
)
from modules.database import col_sent, col_submitted
logger = logging.getLogger(__name__)
async def send_content(app: PyroClient, http_session: ClientSession) -> None:
try:
try:
token = await authorize(http_session)
except ValueError:
await app.send_message(
app.owner,
app._("api_creds_invalid", "message"),
)
return
try:
funcs = []
if app.config["posting"]["types"]["photo"]:
funcs.append((photo_random, photo_get, app.send_photo, photo_patch))
if app.config["posting"]["types"]["video"]:
funcs.append((video_random, video_get, app.send_video, video_patch))
if not funcs:
raise KeyError(
"No media source provided: all seem to be disabled in config"
)
if len(funcs) > 1:
found = False
for func_iter in sample(funcs, len(funcs)):
func = func_iter
random_results = (
await func_iter[0](
album=app.config["posting"]["api"]["album"],
caption="queue",
client=client,
limit=1,
)
).results
if not random_results:
continue
media: Union[PhotoSearch, VideoSearch] = random_results[0]
try:
response: File = await func_iter[1](id=media.id, client=client)
except Exception as exc:
print_exc()
logger.error("Media is invalid: %s", exc)
if app.config["reports"]["error"]:
await app.send_message(
app.owner, f"Media is invalid: {exc}"
)
return
found = True
break
if not found:
raise KeyError("No media found")
else:
func = funcs[0]
media: Union[PhotoSearch, VideoSearch] = (
await func[0](
album=app.config["posting"]["api"]["album"],
caption="queue",
client=client,
limit=1,
)
).results[0]
try:
response: File = await func[1](id=media.id, client=client)
except Exception as exc:
print_exc()
logger.error("Media is invalid: %s", exc)
if app.config["reports"]["error"]:
await app.send_message(app.owner, f"Media is invalid: {exc}")
return
except (KeyError, AttributeError, TypeError, IndexError):
logger.info(
"Could not send content due to queue empty or contains only forbidden extensions"
)
if app.config["reports"]["error"]:
await app.send_message(
app.owner,
app._("api_queue_empty", "message"),
)
return
except (ValueError, UnexpectedStatus):
if app.config["reports"]["error"]:
await app.send_message(
app.owner,
app._("api_queue_error", "message"),
)
return
tmp_dir = str(uuid4())
makedirs(path.join(app.config["locations"]["tmp"], tmp_dir), exist_ok=True)
tmp_path = path.join(tmp_dir, media.filename)
async with aiofiles.open(
path.join(app.config["locations"]["tmp"], tmp_path), "wb"
) as out_file:
await out_file.write(response.payload.read())
logger.info(
"Candidate %s (%s) is %s bytes big",
media.filename,
media.id,
path.getsize(path.join(app.config["locations"]["tmp"], tmp_path)),
)
if (
path.getsize(path.join(app.config["locations"]["tmp"], tmp_path)) > 5242880
) and func[0] is photo_random:
image = Image.open(path.join(app.config["locations"]["tmp"], tmp_path))
width, height = image.size
image = image.resize((int(width / 2), int(height / 2)), Image.LANCZOS)
if tmp_path.lower().endswith(".jpeg") or tmp_path.lower().endswith(".jpg"):
image.save(
path.join(app.config["locations"]["tmp"], tmp_path),
"JPEG",
optimize=True,
quality=50,
)
elif tmp_path.lower().endswith(".png"):
image.save(
path.join(app.config["locations"]["tmp"], tmp_path),
"PNG",
optimize=True,
compress_level=8,
)
image.close()
if (
path.getsize(path.join(app.config["locations"]["tmp"], tmp_path)) > 5242880
) and func[0] is photo_random:
rmtree(
path.join(app.config["locations"]["tmp"], tmp_dir), ignore_errors=True
)
raise BytesWarning
del response
submitted = await col_submitted.find_one({"temp.file": media.filename})
if submitted is not None and submitted["caption"] is not None:
caption = submitted["caption"].strip()
else:
caption = ""
if (
submitted is not None
and app.config["posting"]["submitted_caption"]["enabled"]
and (
(submitted["user"] not in app.admins)
or (
app.config["posting"]["submitted_caption"]["ignore_admins"] is False
)
)
):
caption = (
f"{caption}\n\n{app.config['posting']['submitted_caption']['text']}\n"
)
else:
caption = f"{caption}\n\n"
if app.config["caption"]["enabled"]:
if app.config["caption"]["link"] is not None:
caption = f"{caption}[{choice(app.config['caption']['text'])}]({app.config['caption']['link']})"
else:
caption = f"{caption}{choice(app.config['caption']['text'])}"
else:
caption = caption
try:
sent = await func[2](
app.config["posting"]["channel"],
path.join(app.config["locations"]["tmp"], tmp_path),
caption=caption,
disable_notification=app.config["posting"]["silent"],
)
except Exception as exc:
logger.error(
"Could not send media %s (%s) due to %s", media.filename, media.id, exc
)
if app.config["reports"]["error"]:
await app.send_message(
app.owner,
app._("post_exception", "message").format(exc, format_exc()),
)
# rmtree(path.join(app.config['locations']['tmp'], tmp_dir), ignore_errors=True)
return
await col_sent.insert_one(
{
"date": datetime.now(),
"image": media.id,
"filename": media.filename,
"channel": app.config["posting"]["channel"],
"caption": None
if (submitted is None or submitted["caption"] is None)
else submitted["caption"].strip(),
}
)
await func[3](id=media.id, client=client, caption="sent")
rmtree(path.join(app.config["locations"]["tmp"], tmp_dir), ignore_errors=True)
logger.info(
"Sent %s to %s with caption %s and silently %s",
media.id,
str(app.config["posting"]["channel"]),
caption.replace("\n", "%n"),
str(app.config["posting"]["silent"]),
)
except Exception as exc:
logger.error(
"Could not send content due to %s. Traceback: %s", exc, format_exc()
)
if app.config["reports"]["error"]:
await app.send_message(
app.owner,
app._("post_exception", "message").format(exc, format_exc()),
)
try:
rmtree(
path.join(app.config["locations"]["tmp"], tmp_dir), ignore_errors=True
)
except:
pass

View File

@@ -1,116 +1,33 @@
try: import logging
import ujson as json from os import makedirs, path
from ujson import JSONDecodeError as JSONDecodeError from pathlib import Path
except ModuleNotFoundError: from typing import List, Union
import json from zipfile import ZipFile
from json import JSONDecodeError as JSONDecodeError
import os import aiofiles
import sys
import traceback
from signal import SIGKILL # type: ignore logger = logging.getLogger(__name__)
from modules.logging import logWrite
def jsonLoad(filename): USERS_WITH_CONTEXT: List[int] = []
"""Loads arg1 as json and returns its contents"""
with open(filename, "r", encoding='utf8') as file:
try:
output = json.loads(file.read())
except JSONDecodeError:
logWrite(f"Could not load json file {filename}: file seems to be incorrect!\n{traceback.print_exc()}")
raise
except FileNotFoundError:
logWrite(f"Could not load json file {filename}: file does not seem to exist!\n{traceback.print_exc()}")
raise
file.close()
return output
def jsonSave(contents, filename):
"""Dumps dict/list arg1 to file arg2""" async def extract_and_save(handle: ZipFile, filename: str, destpath: Union[str, Path]):
"""Extract and save file from archive
### Args:
* handle (`ZipFile`): ZipFile handler
* filename (`str`): File base name
* path (`Union[str, Path]`): Path where to store
"""
data = handle.read(filename)
filepath = path.join(str(destpath), filename)
try: try:
with open(filename, "w", encoding='utf8') as file: makedirs(path.dirname(filepath), exist_ok=True)
file.write(json.dumps(contents, ensure_ascii=False, indent=4)) async with aiofiles.open(filepath, "wb") as fd:
file.close() await fd.write(data)
except Exception as exp: logger.debug("Unzipped %s", filename)
logWrite(f"Could not save json file {filename}: {exp}\n{traceback.print_exc()}") except IsADirectoryError:
return makedirs(filepath, exist_ok=True)
def configSet(key: str, value, *args: str):
"""Set key to a value
Args:
* key (str): The last key of the keys path.
* value (str/int/float/list/dict/None): Some needed value.
* *args (str): Path to key like: dict[args][key].
"""
this_dict = jsonLoad("config.json")
string = "this_dict"
for arg in args:
string += f'["{arg}"]'
if type(value) in [str]:
string += f'["{key}"] = "{value}"'
else:
string += f'["{key}"] = {value}'
exec(string)
jsonSave(this_dict, "config.json")
return
def configGet(key: str, *args: str):
"""Get value of the config key
Args:
* key (str): The last key of the keys path.
* *args (str): Path to key like: dict[args][key].
Returns:
* any: Value of provided key
"""
this_dict = jsonLoad("config.json")
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
return this_key[key]
def locale(key: str, *args: str, locale=configGet("locale")):
"""Get value of locale string
Args:
* key (str): The last key of the locale's keys path.
* *args (list): Path to key like: dict[args][key].
* locale (str): Locale to looked up in. Defaults to config's locale value.
Returns:
* any: Value of provided locale key
"""
if (locale == None):
locale = configGet("locale")
try:
this_dict = jsonLoad(f'{configGet("locale", "locations")}{os.sep}{locale}.json')
except FileNotFoundError: except FileNotFoundError:
try: pass
this_dict = jsonLoad(f'{configGet("locale", "locations")}{os.sep}{configGet("locale")}.json') return
except FileNotFoundError:
try:
this_dict = jsonLoad(f'{configGet("locale_fallback", "locations")}{os.sep}{configGet("locale")}.json')
except:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"'
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
return this_key[key]
except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"'
try:
import psutil
except ModuleNotFoundError:
print(locale("deps_missing", "console", locale=configGet("locale")), flush=True)
sys.exit()
def killProc(pid):
if os.name == "posix":
os.kill(pid, SIGKILL)
else:
p = psutil.Process(pid)
p.kill()

View File

@@ -0,0 +1,12 @@
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import CallbackQuery
from classes.pyroclient import PyroClient
@Client.on_callback_query(filters.regex("nothing"))
async def callback_query_nothing(app: PyroClient, callback: CallbackQuery):
user = await app.find_user(callback.from_user)
await callback.answer(text=app._("nothing", "callback", locale=user.locale))

View File

@@ -0,0 +1,25 @@
from os import makedirs, path
from time import time
from libbot import config_get, json_write
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import CallbackQuery
from classes.pyroclient import PyroClient
@Client.on_callback_query(filters.regex("shutdown"))
async def callback_query_nothing(app: PyroClient, callback: CallbackQuery):
if callback.from_user.id not in app.admins:
return
await callback.answer()
makedirs(await config_get("cache", "locations"), exist_ok=True)
await json_write(
{"timestamp": time()},
path.join(await config_get("cache", "locations"), "shutdown_time"),
)
exit()

View File

@@ -0,0 +1,285 @@
import logging
from os import path
from pathlib import Path
from shutil import rmtree
from bson import ObjectId
from libbot import config_get
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
from classes.exceptions import (
SubmissionDuplicatesError,
SubmissionUnavailableError,
SubmissionUnsupportedError,
)
from classes.pyroclient import PyroClient
from modules.database import col_submitted
logger = logging.getLogger(__name__)
@Client.on_callback_query(filters.regex("sub_yes_[\s\S]*"))
async def callback_query_yes(app: PyroClient, callback: CallbackQuery):
user = await app.find_user(callback.from_user)
fullcallback = str(callback.data).split("_")
db_entry = await col_submitted.find_one({"_id": ObjectId(fullcallback[2])})
try:
submission = await app.submit_media(
fullcallback[2],
purge_caption=("caption" not in fullcallback),
)
except SubmissionUnavailableError:
await callback.answer(
text=app._("sub_msg_unavail", "callback", locale=user.locale),
show_alert=True,
)
return
except SubmissionUnsupportedError:
await callback.answer(
text=app._("mime_not_allowed", "message", locale=user.locale).format(
", ".join(app.config["submission"]["mime_types"]), quote=True
),
show_alert=True,
)
return
except SubmissionDuplicatesError as exc:
await callback.answer(
text=app._("sub_duplicates_found", "callback", locale=user.locale),
show_alert=True,
)
await callback.message.reply_text(
app._("sub_media_duplicates_list", "message", locale=user.locale).format(
"\n".join(exc.duplicates)
),
quote=True,
)
logger.info(
"Submission with ID '%s' could not be accepted because of the duplicates: %s",
fullcallback[2],
str(exc.duplicates),
)
return
if submission[0] is not None:
await submission[0].reply_text(
app._(
"sub_yes",
"message",
locale=(await app.find_user(submission[0].from_user)).locale,
),
quote=True,
)
elif db_entry is not None:
await app.send_message(
db_entry["user"],
app._(
"sub_yes",
"message",
locale=(await app.find_user(db_entry["user"])).locale,
),
)
await callback.answer(
text=app._("sub_yes", "callback", locale=user.locale).format(fullcallback[2]),
show_alert=True,
)
edited_markup = (
[
[
InlineKeyboardButton(
text=str(app._("accepted", "button", locale=user.locale)),
callback_data="nothing",
)
],
callback.message.reply_markup.inline_keyboard[1],
]
if len(callback.message.reply_markup.inline_keyboard) > 1
else [
[
InlineKeyboardButton(
text=str(app._("accepted", "button", locale=user.locale)),
callback_data="nothing",
)
]
]
)
if await config_get("send_uploaded_id", "submission"):
await callback.message.edit_caption(
f"{callback.message.caption}\n\nID: `{submission[1]}`"
)
await callback.message.edit_reply_markup(
reply_markup=InlineKeyboardMarkup(edited_markup)
)
logger.info(
"Submission with ID '%s' accepted and uploaded with ID '%s'",
fullcallback[2],
submission[1],
)
logger.info(
"Submission with ID '%s' accepted and uploaded with ID '%s'",
fullcallback[2],
submission[1],
)
@Client.on_callback_query(filters.regex("sub_no_[\s\S]*"))
async def callback_query_no(app: PyroClient, callback: CallbackQuery):
user = await app.find_user(callback.from_user)
fullcallback = str(callback.data).split("_")
db_entry = await col_submitted.delete_one({"_id": ObjectId(fullcallback[2])})
if db_entry.deleted_count == 0:
await callback.answer(
text=app._("sub_deleted", "callback", locale=user.locale).format(
fullcallback[2]
),
show_alert=True,
)
return
if (
db_entry.raw_result["temp"]["uuid"] is not None
and Path(
f"{app.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}"
).exists()
):
rmtree(
Path(
f"{app.config['locations']['data']}/submissions/{db_entry['temp']['uuid']}"
),
ignore_errors=True,
)
try:
submission = await app.get_messages(
db_entry["user"], db_entry["telegram"]["msg_id"]
)
except Exception as exc:
await callback.answer(
text=app._("sub_msg_unavail", "message", locale=user.locale),
show_alert=True,
)
return
await submission.reply_text(
app._(
"sub_no",
"message",
locale=(await app.find_user(submission.from_user)).locale,
),
quote=True,
)
await callback.answer(
text=app._("sub_no", "callback", locale=user.locale).format(fullcallback[2]),
show_alert=True,
)
edited_markup = (
[
[
InlineKeyboardButton(
text=str(app._("declined", "button", locale=user.locale)),
callback_data="nothing",
)
],
callback.message.reply_markup.inline_keyboard[1],
]
if len(callback.message.reply_markup.inline_keyboard) > 1
else [
[
InlineKeyboardButton(
text=str(app._("declined", "button", locale=user.locale)),
callback_data="nothing",
)
]
]
)
await callback.message.edit_reply_markup(
reply_markup=InlineKeyboardMarkup(edited_markup)
)
logger.info(
"Submission with ID '%s' rejected",
fullcallback[2],
)
@Client.on_callback_query(filters.regex("sub_block_[\s\S]*"))
async def callback_query_block(app: PyroClient, callback: CallbackQuery):
user = await app.find_user(callback.from_user)
fullcallback = str(callback.data).split("_")
await app.send_message(
int(fullcallback[2]),
app._(
"sub_blocked",
"message",
locale=(await app.find_user(int(fullcallback[2]))).locale,
),
)
await user.block()
await callback.answer(
text=app._("sub_block", "callback", locale=user.locale).format(fullcallback[2]),
show_alert=True,
)
edited_markup = [
callback.message.reply_markup.inline_keyboard[0],
[
InlineKeyboardButton(
text=str(app._("sub_unblock", "button", locale=user.locale)),
callback_data=f"sub_unblock_{fullcallback[2]}",
)
],
]
await callback.message.edit_reply_markup(
reply_markup=InlineKeyboardMarkup(edited_markup)
)
logger.info("User %s has been blocked", fullcallback[2])
@Client.on_callback_query(filters.regex("sub_unblock_[\s\S]*"))
async def callback_query_unblock(app: PyroClient, callback: CallbackQuery):
user = await app.find_user(callback.from_user)
fullcallback = str(callback.data).split("_")
await app.send_message(
int(fullcallback[2]),
app._(
"sub_unblocked",
"message",
locale=(await app.find_user(int(fullcallback[2]))).locale,
),
)
await user.unblock()
await callback.answer(
text=app._("sub_unblock", "callback", locale=user.locale).format(
fullcallback[2]
),
show_alert=True,
)
edited_markup = [
callback.message.reply_markup.inline_keyboard[0],
[
InlineKeyboardButton(
text=str(app._("sub_block", "button", locale=user.locale)),
callback_data=f"sub_block_{fullcallback[2]}",
)
],
]
await callback.message.edit_reply_markup(
reply_markup=InlineKeyboardMarkup(edited_markup)
)
logger.info("User %s has been unblocked", fullcallback[2])

View File

@@ -0,0 +1,48 @@
import asyncio
from os import makedirs
from pathlib import Path
from time import time
from libbot import json_write
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
from classes.pyroclient import PyroClient
from modules.utils import USERS_WITH_CONTEXT
@Client.on_message(
~filters.scheduled & filters.command(["shutdown"], prefixes=["", "/"])
)
async def cmd_kill(app: PyroClient, message: Message):
if message.from_user.id not in app.admins:
return
user = await app.find_user(message.from_user)
if len(USERS_WITH_CONTEXT) > 0:
await message.reply_text(
app._("shutdown_confirm", "message", locale=user.locale).format(
len(USERS_WITH_CONTEXT)
),
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(
app._("shutdown", "button", locale=user.locale),
callback_data="shutdown",
)
]
]
),
)
return
makedirs(app.config["locations"]["cache"], exist_ok=True)
await json_write(
{"timestamp": time()},
Path(f"{app.config['locations']['cache']}/shutdown_time"),
)
asyncio.get_event_loop().create_task(app.stop())

View File

@@ -0,0 +1,34 @@
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import Message
from classes.pyroclient import PyroClient
from modules import custom_filters
@Client.on_message(
custom_filters.mode_submit
& ~filters.scheduled
& filters.command(["start"], prefixes="/")
)
async def cmd_start(app: PyroClient, message: Message):
user = await app.find_user(message.from_user)
if user.banned:
return
await message.reply_text(app._("start", "message", locale=user.locale))
@Client.on_message(
custom_filters.mode_submit
& ~filters.scheduled
& filters.command(["rules", "help"], prefixes="/")
)
async def cmd_rules(app: PyroClient, message: Message):
user = await app.find_user(message.from_user)
if user.banned:
return
await message.reply_text(app._("rules", "message", locale=user.locale))

360
plugins/commands/photos.py Normal file
View File

@@ -0,0 +1,360 @@
import asyncio
import logging
from glob import iglob
from io import BytesIO
from os import getcwd, makedirs, path, remove
from pathlib import Path
from shutil import disk_usage, rmtree
from traceback import format_exc
from uuid import uuid4
from zipfile import ZipFile
from convopyro import listen_message
from photosapi_client.errors import UnexpectedStatus
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import (
KeyboardButton,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from ujson import loads
from classes.pyroclient import PyroClient
from modules.api_client import (
BodyPhotoUpload,
File,
client,
photo_delete,
photo_upload,
video_delete,
video_upload,
)
from modules.utils import USERS_WITH_CONTEXT, extract_and_save
logger = logging.getLogger(__name__)
@Client.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"]))
async def cmd_import(app: PyroClient, message: Message):
if message.from_user.id not in app.admins:
return
global USERS_WITH_CONTEXT
if message.from_user.id not in USERS_WITH_CONTEXT:
USERS_WITH_CONTEXT.append(message.from_user.id)
else:
return
user = await app.find_user(message.from_user)
await message.reply_text(app._("import_request", "message", locale=user.locale))
answer = await listen_message(app, message.chat.id, timeout=600)
USERS_WITH_CONTEXT.remove(message.from_user.id)
if answer is None:
await message.reply_text(
app._("import_ignored", "message", locale=user.locale),
quote=True,
)
return
if answer.text == "/cancel":
await answer.reply_text(app._("import_abort", "message", locale=user.locale))
return
if answer.document is None:
await answer.reply_text(
app._(
"import_invalid_media",
"message",
locale=user.locale,
),
quote=True,
)
return
if answer.document.mime_type != "application/zip":
await answer.reply_text(
app._("import_invalid_mime", "message", locale=user.locale),
quote=True,
)
return
if disk_usage(getcwd())[2] < (answer.document.file_size) * 3:
await message.reply_text(
app._("import_too_big", "message", locale=user.locale).format(
answer.document.file_size // (2**30),
disk_usage(getcwd())[2] // (2**30),
)
)
return
tmp_dir = str(uuid4())
logging.info(
"Importing '%s' file %s bytes big (TMP ID %s)",
answer.document.file_name,
answer.document.file_size,
tmp_dir,
)
makedirs(Path(f"{app.config['locations']['tmp']}/{tmp_dir}"), exist_ok=True)
tmp_path = Path(f"{app.config['locations']['tmp']}/{answer.document.file_id}")
downloading = await answer.reply_text(
app._("import_downloading", "message", locale=user.locale),
quote=True,
)
await app.download_media(answer, file_name=str(tmp_path))
await downloading.edit(app._("import_unpacking", "message", locale=user.locale))
try:
with ZipFile(tmp_path, "r") as handle:
tasks = [
extract_and_save(
handle, name, Path(f"{app.config['locations']['tmp']}/{tmp_dir}")
)
for name in handle.namelist()
]
_ = await asyncio.gather(*tasks)
except Exception as exc:
logger.error(
"Could not import '%s' due to %s: %s",
answer.document.file_name,
exc,
format_exc(),
)
await answer.reply_text(
app._("import_unpack_error", "message", locale=user.locale).format(
exc, format_exc()
)
)
return
logger.info("Downloaded '%s' - awaiting upload", answer.document.file_name)
await downloading.edit(app._("import_uploading", "message", locale=user.locale))
remove(tmp_path)
for filename in iglob(
str(Path(f"{app.config['locations']['tmp']}/{tmp_dir}")) + "**/**",
recursive=True,
):
if not path.isfile(filename):
continue
with open(str(filename), "rb") as fh:
photo_bytes = BytesIO(fh.read())
try:
# VIDEO SUPPORT IS PLANNED HERE TOO
uploaded = await photo_upload(
app.config["posting"]["api"]["album"],
client=client,
body=BodyPhotoUpload(
File(photo_bytes, Path(filename).name, "image/jpeg")
),
ignore_duplicates=app.config["submission"]["allow_duplicates"],
compress=False,
caption="queue",
)
except UnexpectedStatus as exc:
logger.error(
"Could not upload '%s' from '%s': %s",
filename,
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
exc,
)
await message.reply_text(
app._(
"import_upload_error_other",
"message",
locale=user.locale,
).format(path.basename(filename)),
disable_notification=True,
)
continue
uploaded_dict = loads(uploaded.content.decode("utf-8"))
if "duplicates" in uploaded_dict:
logger.warning(
"Could not upload '%s' from '%s'. Duplicates: %s",
filename,
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
str(uploaded_dict["duplicates"]),
)
if len(uploaded_dict["duplicates"]) > 0:
await message.reply_text(
app._(
"import_upload_error_duplicate",
"message",
locale=user.locale,
).format(path.basename(filename)),
disable_notification=True,
)
else:
await message.reply_text(
app._(
"import_upload_error_other",
"message",
locale=user.locale,
).format(path.basename(filename)),
disable_notification=True,
)
else:
logger.info(
"Uploaded '%s' from '%s' and got ID %s",
filename,
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
uploaded.parsed.id,
)
await downloading.delete()
logger.info(
"Removing '%s' after uploading",
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
)
rmtree(Path(f"{app.config['locations']['tmp']}/{tmp_dir}"), ignore_errors=True)
await answer.reply_text(
app._("import_finished", "message", locale=user.locale),
quote=True,
)
return
@Client.on_message(~filters.scheduled & filters.command(["export"], prefixes=["", "/"]))
async def cmd_export(app: PyroClient, message: Message):
if message.from_user.id not in app.admins:
return
@Client.on_message(~filters.scheduled & filters.command(["remove"], prefixes=["", "/"]))
async def cmd_remove(app: PyroClient, message: Message):
if message.from_user.id not in app.admins:
return
global USERS_WITH_CONTEXT
if message.from_user.id not in USERS_WITH_CONTEXT:
USERS_WITH_CONTEXT.append(message.from_user.id)
else:
return
user = await app.find_user(message.from_user)
await message.reply_text(app._("remove_request", "message", locale=user.locale))
answer_id = await app.listen.Message(
filters.text & ~filters.me, id=filters.user(message.from_user.id), timeout=600
)
USERS_WITH_CONTEXT.remove(message.from_user.id)
if answer_id is None:
await message.reply_text(
app._("remove_ignored", "message", locale=user.locale),
quote=True,
)
return
if answer_id.text == "/cancel":
await answer_id.reply_text(app._("remove_abort", "message", locale=user.locale))
return
await message.reply_text(
app._("remove_kind", "message", locale=user.locale),
reply_markup=ReplyKeyboardMarkup(
[
[
KeyboardButton(app._("photo", "button", locale=user.locale)),
KeyboardButton(app._("video", "button", locale=user.locale)),
]
],
resize_keyboard=True,
one_time_keyboard=True,
),
)
USERS_WITH_CONTEXT.append(message.from_user.id)
answer_kind = await app.listen.Message(
filters.text & ~filters.me, id=filters.user(message.from_user.id), timeout=600
)
USERS_WITH_CONTEXT.remove(message.from_user.id)
if answer_kind is None:
await message.reply_text(
app._("remove_ignored", "message", locale=user.locale),
quote=True,
reply_markup=ReplyKeyboardRemove(),
)
return
if answer_kind.text == "/cancel":
await answer_kind.reply_text(
app._("remove_abort", "message", locale=user.locale),
reply_markup=ReplyKeyboardRemove(),
)
return
if answer_kind.text in app.in_all_locales("photo", "button"):
func = photo_delete
elif answer_kind.text in app.in_all_locales("video", "button"):
func = video_delete
else:
await answer_kind.reply_text(
app._("remove_unknown", "message", locale=user.locale).format(
app._("photo", "button", locale=user.locale),
app._("video", "button", locale=user.locale),
),
reply_markup=ReplyKeyboardRemove(),
)
return
response = await func(id=answer_id.text, client=client)
if response is None:
logger.info(
"Removed %s '%s' by request of user %s",
answer_kind.text,
answer_id.text,
answer_id.from_user.id,
)
await answer_kind.reply_text(
app._("remove_success", "message", locale=user.locale).format(
answer_id.text
),
reply_markup=ReplyKeyboardRemove(),
)
else:
logger.warning(
"Could not remove %s '%s' by request of user %s",
answer_kind.text,
answer_id.text,
answer_id.from_user.id,
)
await answer_kind.reply_text(
app._("remove_failure", "message", locale=user.locale).format(
answer_id.text
),
reply_markup=ReplyKeyboardRemove(),
)
@Client.on_message(~filters.scheduled & filters.command(["purge"], prefixes=["", "/"]))
async def cmd_purge(app: PyroClient, message: Message):
if message.from_user.id not in app.admins:
return

View File

@@ -0,0 +1,44 @@
from libbot import sync
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import Message, User
from classes.pyroclient import PyroClient
from modules import custom_filters
@Client.on_message(
custom_filters.mode_post
& ~filters.scheduled
& filters.chat(sync.config_get("comments", "posting"))
& filters.reply
& filters.command(["report"], prefixes=["", "/"])
)
async def command_report(app: PyroClient, message: Message):
if (
message.reply_to_message.forward_from_chat.id
!= app.config["posting"]["channel"]
):
return
user = await app.find_user(message.from_user)
await message.reply_text(
app._(
"report_sent",
"message",
locale=user.locale if message.from_user is not None else None,
)
)
report_sent = await message.reply_to_message.forward(app.owner)
sender = message.from_user if message.from_user is not None else message.sender_chat
sender_name = sender.first_name if isinstance(sender, User) else sender.title
await report_sent.reply_text(
app._("report_received", "message", locale=user.locale).format(
sender_name, sender.username, sender.id
),
quote=True,
)

View File

@@ -0,0 +1,325 @@
import logging
from datetime import datetime
from os import makedirs, path, sep
from pathlib import Path
from traceback import format_exc
from uuid import uuid4
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.enums.chat_action import ChatAction
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
from classes.enums.submission_types import SubmissionType
from classes.exceptions import SubmissionDuplicatesError, SubmissionUnsupportedError
from classes.pyroclient import PyroClient
from modules import custom_filters
from modules.database import col_submitted
from modules.utils import USERS_WITH_CONTEXT
logger = logging.getLogger(__name__)
@Client.on_message(
custom_filters.mode_submit & ~filters.scheduled & filters.private & filters.photo
| filters.video
# | filters.animation
| filters.document
)
async def get_submission(app: PyroClient, message: Message):
global USERS_WITH_CONTEXT
if not hasattr(message.from_user, "id"):
return
if message.from_user.id in USERS_WITH_CONTEXT:
return
user = await app.find_user(message.from_user)
user_owner = await app.find_user(app.owner)
try:
if user.banned:
return
await app.send_chat_action(message.chat.id, ChatAction.TYPING)
save_tmp = True
contents = None
if await user.is_limited():
await message.reply_text(
app._("sub_cooldown", "message", locale=user.locale).format(
app.config["submission"]["timeout"]
)
)
return
if message.document is not None:
logger.info(
"User %s is trying to submit a file of type '%s' with name '%s' and size of %s MB",
message.from_user.id,
message.document.mime_type,
message.document.file_name,
message.document.file_size / 1024 / 1024,
)
if message.document.mime_type not in app.config["submission"]["mime_types"]:
await message.reply_text(
app._("mime_not_allowed", "message", locale=user.locale).format(
", ".join(app.config["submission"]["mime_types"])
),
quote=True,
)
return
if message.document.file_size > app.config["submission"]["file_size"]:
await message.reply_text(
app._("document_too_large", "message", locale=user.locale).format(
app.config["submission"]["file_size"] / 1024 / 1024
),
quote=True,
)
return
if message.document.file_size > app.config["submission"]["tmp_size"]:
save_tmp = False
contents = (
message.document.file_id,
SubmissionType.DOCUMENT,
) # , message.document.file_name
if message.video is not None:
logger.info(
"User %s is trying to submit a video with name '%s' and size of %s MB",
message.from_user.id,
message.video.file_name,
message.video.file_size / 1024 / 1024,
)
if message.video.file_size > app.config["submission"]["file_size"]:
await message.reply_text(
app._("document_too_large", "message", locale=user.locale).format(
app.config["submission"]["file_size"] / 1024 / 1024
),
quote=True,
)
return
if message.video.file_size > app.config["submission"]["tmp_size"]:
save_tmp = False
contents = (
message.video.file_id,
SubmissionType.VIDEO,
) # , message.video.file_name
# if message.animation is not None:
# logger.info(
# "User %s is trying to submit an animation with name '%s' and size of %s MB",
# message.from_user.id,
# message.animation.file_name,
# message.animation.file_size / 1024 / 1024,
# )
# if message.animation.file_size > app.config["submission"]["file_size"]:
# await message.reply_text(
# app._("document_too_large", "message", locale=user.locale).format(
# str(app.config["submission"]["file_size"] / 1024 / 1024)
# ),
# quote=True,
# )
# return
# if message.animation.file_size > app.config["submission"]["tmp_size"]:
# save_tmp = False
# contents = (
# message.animation.file_id,
# SubmissionType.ANIMATION,
# ) # , message.animation.file_name
if message.photo is not None:
logger.info(
"User %s is trying to submit a photo with ID '%s' and size of %s MB",
message.from_user.id,
message.photo.file_id,
message.photo.file_size / 1024 / 1024,
)
contents = (
message.photo.file_id,
SubmissionType.PHOTO,
) # , "please_generate"
if contents is None:
return
if save_tmp is not None:
tmp_id = str(uuid4())
# filename = tmp_id if contents[1] == "please_generate" else contents[1]
makedirs(
Path(f"{app.config['locations']['data']}/submissions/{tmp_id}"),
exist_ok=True,
)
downloaded = await app.download_media(
message,
str(Path(f"{app.config['locations']['data']}/submissions/{tmp_id}"))
+ sep,
)
inserted = await col_submitted.insert_one(
{
"user": message.from_user.id,
"date": datetime.now(),
"done": False,
"type": contents[1].value,
"temp": {"uuid": tmp_id, "file": path.basename(str(downloaded))},
"telegram": {"msg_id": message.id, "file_id": contents[0]},
"caption": str(message.caption)
if message.caption is not None
else None,
}
)
else:
inserted = await col_submitted.insert_one(
{
"user": message.from_user.id,
"date": datetime.now(),
"done": False,
"type": contents[1].value,
"temp": {"uuid": None, "file": None},
"telegram": {"msg_id": message.id, "file_id": contents[0]},
"caption": str(message.caption)
if message.caption is not None
else None,
}
)
buttons = [
[
InlineKeyboardButton(
text=app._("sub_yes", "button", locale=user_owner.locale),
callback_data=f"sub_yes_{str(inserted.inserted_id)}",
)
]
]
if message.caption is not None:
caption = str(message.caption)
buttons[0].append(
InlineKeyboardButton(
text=app._("sub_yes_caption", "button", locale=user_owner.locale),
callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption",
)
)
else:
caption = ""
buttons[0].append(
InlineKeyboardButton(
text=app._("sub_no", "button", locale=user_owner.locale),
callback_data=f"sub_no_{str(inserted.inserted_id)}",
)
)
caption += app._("sub_by", "message", locale=user_owner.locale)
if message.from_user.first_name is not None:
caption += f" {message.from_user.first_name}"
if message.from_user.last_name is not None:
caption += f" {message.from_user.last_name}"
if message.from_user.username is not None:
caption += f" (@{message.from_user.username})"
if message.from_user.phone_number is not None:
caption += f" ({message.from_user.phone_number})"
if (
message.from_user.id in app.admins
and app.config["submission"]["require_confirmation"]["admins"] is False
):
try:
submitted = await app.submit_media(str(inserted.inserted_id))
await message.reply_text(
app._("sub_yes_auto", "message", locale=user.locale),
disable_notification=True,
quote=True,
)
if app.config["submission"]["send_uploaded_id"]:
caption += f"\n\nID: `{submitted[1]}`"
await message.copy(
app.owner, caption=caption, disable_notification=True
)
return
except SubmissionUnsupportedError:
await message.reply_text(
app._("mime_not_allowed", "message", locale=user.locale).format(
", ".join(app.config["submission"]["mime_types"]), quote=True
),
quote=True,
)
return
except SubmissionDuplicatesError as exc:
await message.reply_text(
app._(
"sub_media_duplicates_list", "message", locale=user.locale
).format("\n".join(exc.duplicates)),
quote=True,
)
return
except Exception as exc:
await message.reply_text(exc, quote=True)
return
elif (
message.from_user.id not in app.admins
and app.config["submission"]["require_confirmation"]["users"] is False
):
try:
submitted = await app.submit_photo(str(inserted.inserted_id))
await message.reply_text(
app._("sub_yes_auto", "message", locale=user.locale),
disable_notification=True,
quote=True,
)
if app.config["submission"]["send_uploaded_id"]:
caption += f"\n\nID: `{submitted[1]}`"
await message.copy(app.owner, caption=caption)
return
except SubmissionUnsupportedError:
await message.reply_text(
app._("mime_not_allowed", "message", locale=user.locale).format(
", ".join(app.config["submission"]["mime_types"]), quote=True
)
)
return
except SubmissionDuplicatesError as exc:
await message.reply_text(
app._("sub_dup", "message", locale=user.locale), quote=True
)
return
except Exception as exc:
await app.send_message(
app.owner,
app._(
"sub_error_admin", "message", locale=user_owner.locale
).format(message.from_user.id, format_exc()),
)
await message.reply_text("sub_error", quote=True)
return
if message.from_user.id not in app.admins:
buttons += [
[
InlineKeyboardButton(
text=app._("sub_block", "button", locale=user_owner.locale),
callback_data=f"sub_block_{message.from_user.id}",
)
]
]
await user.update_cooldown()
if message.from_user.id != app.owner:
await message.reply_text(
app._("sub_sent", "message", locale=user.locale),
disable_notification=True,
quote=True,
)
await message.copy(
app.owner, caption=caption, reply_markup=InlineKeyboardMarkup(buttons)
)
except AttributeError:
logger.error("'from_user' does not seem to contain 'id'")

42
plugins/language.py Normal file
View File

@@ -0,0 +1,42 @@
from pykeyboard import InlineButton, InlineKeyboard
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import CallbackQuery, Message
from classes.pyroclient import PyroClient
@Client.on_message(
~filters.scheduled & filters.private & filters.command(["language"], prefixes=["/"]) # type: ignore
)
async def command_language(app: PyroClient, message: Message):
user = await app.find_user(message.from_user)
keyboard = InlineKeyboard(row_width=2)
buttons = []
for locale, data in app.in_every_locale("metadata").items():
buttons.append(
InlineButton(f"{data['flag']} {data['name']}", f"language:{locale}")
)
keyboard.add(*buttons)
await message.reply_text(
app._("locale_choice", "message", locale=user.locale),
reply_markup=keyboard,
)
@Client.on_callback_query(filters.regex(r"language:[\s\S]*")) # type: ignore
async def callback_language(app: PyroClient, callback: CallbackQuery):
user = await app.find_user(callback.from_user)
language = str(callback.data).split(":")[1]
await user.update_locale(language)
await callback.answer(
app._("locale_set", "callback", locale=language).format(
locale=app._("name", "metadata", locale=language)
),
show_alert=True,
)

View File

@@ -0,0 +1,13 @@
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import Message
from classes.pyroclient import PyroClient
@Client.on_message(
~filters.scheduled & filters.private & filters.command(["remove_commands"], prefixes=["/"]) # type: ignore
)
async def command_remove_commands(app: PyroClient, message: Message):
await message.reply_text("Okay.")
await app.remove_commands(command_sets=await app.collect_commands())

View File

@@ -1 +0,0 @@
ujson

View File

@@ -1,3 +1,11 @@
schedule aiohttp~=3.10.2
pyrogram>=2.0.0 async_pymongo==0.1.6
psutil convopyro==0.5
pillow~=10.4.0
pykeyboard==0.1.7
pytimeparse~=1.1.8
tgcrypto==1.2.5
#uvloop==0.19.0
--extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple
libbot[speed,pyrogram]==3.2.3
photosapi_client==0.6.0