Compare commits

...

No commits in common. "stable" and "master" have entirely different histories.

153 changed files with 10741 additions and 6760 deletions

View file

@ -6,8 +6,11 @@ name = "python"
[analyzers.meta] [analyzers.meta]
runtime_version = "3.x.x" runtime_version = "3.x.x"
[[transformers]]
name = "yapf"
[[transformers]] [[transformers]]
name = "isort" name = "isort"
[[transformers]] [[transformers]]
name = "black" name = "ruff"

3
.github/FUNDING.yml vendored
View file

@ -1,5 +1,4 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [yasirarism] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: [yasirarism] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
paypal: ['']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] other: ['https://yasirpedia.eu.org/images/my-qris.jpg', 'https://paypal.me/yasirarism'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
QRIS: ['https://yasirpedia.eu.org/images/my-qris.jpg']

View file

@ -1,33 +0,0 @@
name: Deploying to heroku
on: workflow_dispatch
env:
IMAGE_NAME: worker
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
CONFIG_FILE_URL: ${{ secrets.CONFIG_FILE_URL }}
jobs:
build_and_push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the image
run: docker build . --file Dockerfile -t "${IMAGE_NAME}"
- name: Login into heroku container registry
run: heroku container:login
- name: Create heroku app name
run: heroku create --region eu ${{ secrets.HEROKU_APP_NAME }}
- name: Push the image container to heroku
run: heroku container:push "${IMAGE_NAME}" -a ${{ secrets.HEROKU_APP_NAME }}
- name: Release image to heroku
run: heroku container:release "${IMAGE_NAME}" -a ${{ secrets.HEROKU_APP_NAME }}
- name: Setting up config env
run: heroku config:set CONFIG_FILE_URL="${CONFIG_FILE_URL}" HEROKU_API_KEY="${HEROKU_API_KEY}" HEROKU_APP_NAME="${{ secrets.HEROKU_APP_NAME }}" -a ${{ secrets.HEROKU_APP_NAME }}

36
.github/workflows/h3r0ku.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: H3r0ku Deployer
on: workflow_dispatch
env:
IMAGE_NAME: misskatybotnew
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
HEROKU_APP: ${{ secrets.HEROKU_APP }}
jobs:
build_and_push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the image
run: docker build . --file Dockerfile --tag "${IMAGE_NAME}"
- name: Login into Heroku Container registry
run: heroku container:login
- name: Push the image to Heroku
run: heroku container:push "${IMAGE_NAME}" -a "${HEROKU_APP}"
- name: Release image to Heroku
run: heroku container:release "${IMAGE_NAME}" -a "${HEROKU_APP}"
- name: Check ENV
run: |
API_KEY="$(heroku config:get HEROKU_API_KEY -a ${HEROKU_APP})"
APP_NAME="$(heroku config:get HEROKU_APP -a ${HEROKU_APP})"
if [[ -z "${API_KEY}" && -z "${APP_NAME}" ]]; then
heroku config:set HEROKU_API_KEY="${HEROKU_API_KEY}" \
HEROKU_APP="${HEROKU_APP}"
fi

View file

@ -19,8 +19,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Notify the commit on Telegram. - name: Notify the commit on Telegram YasirPediaFeed.
uses: EverythingSuckz/github-telegram-notify@main uses: EverythingSuckz/github-telegram-notify@main
with: with:
bot_token: '${{ secrets.BOT_TOKEN }}' bot_token: '${{ secrets.BOT_TOKEN }}'
chat_id: '${{ secrets.CHAT_ID }}' chat_id: '${{ secrets.CHAT_ID }}'
- name: Notify the commit on Telegram SecretGrup.
uses: EverythingSuckz/github-telegram-notify@main
with:
bot_token: '${{ secrets.BOT_TOKEN }}'
chat_id: '-1001777794636'
topic_id: '436599'

View file

@ -1,19 +1,19 @@
# * @author Yasir Aris M <yasiramunandar@gmail.com> # * @author Yasir Aris M <yasiramunandar@gmail.com>
# * @date 2022-12-01 09:12:27 # * @date 2022-12-01 09:12:27
# * @projectName MissKatyPyro # * @projectName MissKatyPyro
# * Copyright ©YasirPedia All rights reserved # * Copyright ©YasirPedia All rights reserved
# Base Docker Using Alpine 3.18, Python 3.11.4 and Built In Pip # Base Docker Using Ubuntu 24.04, Python 3.12 and Built In Pip
## With Built in Pip Package ## With Built in Pip Package
FROM yasirarism/misskaty-docker:alpine FROM yasirarism/misskaty-docker:py3.13
## Without Built in Pip Package ## Without Built in Pip Package
# FROM yasirarism/misskaty-docker:free # FROM yasirarism/misskaty-docker:free
# Set Hostname # Set Hostname
ENV HOSTNAME yasir-server ENV HOSTNAME=yasir-server
# Copy Files # Copy Files
COPY . . COPY . .
# Instal pip package # Instal pip package if you use free depedencies
# RUN pip3 install --no-cache-dir -r requirements.txt --break-system-packages # RUN pip3 install --no-cache-dir -r requirements.txt
# Set CMD Bot # Set CMD Bot
CMD ["bash", "start.sh"] CMD ["bash", "start.sh"]

1
Procfile Normal file
View file

@ -0,0 +1 @@
worker: bash start.sh

View file

@ -1,194 +1,206 @@
# MissKatyPyro # MissKatyPyro
```diff
<!--Badges--> - Saya tidak akan memberikan dukungan apapun terhadap fork repo ini, jadi apapun yang terjadi adalah tanggungjawabmu. Jangan menghubungi saya karena kesalahanmu sendiri. Saya berhenti melalukan update repo ini, hanya beberapa perbaikan kecil yang akan saya lakukan.
![MIT License][license-shield] ![Repository Size][repository-size-shield] ![Issue Closed][issue-closed-shield] ```
<!--Project Title Image--> <!--Badges-->
<p align="center"> ![MIT License][license-shield] ![Repository Size][repository-size-shield] ![Issue Closed][issue-closed-shield]
<img src="https://repository-images.githubusercontent.com/433350689/26cb713b-43c3-4dec-94cb-6c80599547e8" width="200" height="200"/>
</p> <!--Project Title Image-->
<p align="center">
<!--Project Buttons--> <img src="https://repository-images.githubusercontent.com/433350689/26cb713b-43c3-4dec-94cb-6c80599547e8" width="200" height="200"/>
[![Readme in Indonesian][readme-ko-shield]][readme-ko-url] [![View Demo][view-demo-shield]][view-demo-url] [![Report bug][report-bug-shield]][report-bug-url] [![Request feature][request-feature-shield]][request-feature-url] </p>
<!--Table of Contents--> <!--Project Buttons-->
# Table of Contents [![Readme in Indonesian][readme-ko-shield]][readme-ko-url] [![View Demo][view-demo-shield]][view-demo-url] [![Report bug][report-bug-shield]][report-bug-url] <!-- [![Request feature][request-feature-shield]][request-feature-url] -->
- [[1] About MissKaty](#1-about-misskaty)
- [[2] Framework Tools And Server That Used To Build This Bot](#2-framework-tools-and-server-that-used-to-build-this-bot) <!--Table of Contents-->
- [[3] Donation](#3-donation) # Table of Contents
- [[4] Notes](#4-notes) - [[1] Tentang MissKaty](#1-about-misskaty)
- [[5] Features](#5-features) - [[2] Alat Kerangka Dan Server Yang Digunakan Untuk Membangun Bot Ini](#2-framework-tools-and-server-that-used-to-build-this-bot)
- [[6] Variables](#6-variables) - [[3] Donation](#3-donation)
- [[7] Deploying Tutorial](#7-deploy-recommended-using-dockerdocker-compose) - [[4] Notes](#4-notes)
- [Build And Run Using Legacy Method](#build-and-run-using-legacy-method) - [[5] Features](#5-features)
- [Build And Run Using Docker](#build-and-run-using-docker) - [[6] Variables](#6-variables)
- [Build And Run The Docker Image Using docker-compose](#build-and-run-the-docker-image-using-docker-compose) - [[7] Deploying Tutorial](#7-deploy-recommended-using-dockerdocker-compose)
- [[8] Credits](#8-thanks-to) - [Build And Run Using Legacy Method](#build-and-run-using-legacy-method)
- [[9] Disclaimer](#8-disclaimer) - [Build And Run Using Docker](#build-and-run-using-docker)
- [Build And Run The Docker Image Using docker-compose](#build-and-run-the-docker-image-using-docker-compose)
# [1] About MissKaty - [[8] Credits](#8-thanks-to)
*MissKaty* adalah Bot Telegram yang dibuat menggunakan Python dan library Pyrogram. Banyak fitur yang berguna untuk kita gunakan. Saya berharap suatu saat jika project ini dihentikan, ada yang melanjutkan atau mengembangkannya lagi. Saya memberi nama MissKaty karena saya suka kucing, hewan lucu yang suka bermain dan bersahabat dengan manusia. - [[9] Disclaimer](#8-disclaimer)
## [2] Framework Tools And Server That Used To Build This Bot # [1] Tentang MissKaty
🌱 PyroFork v2.x.x (Fork Pyrogram dengan Dukungan Topik dan Beberapa Patch)<br> *MissKaty* adalah Bot Telegram yang dibuat menggunakan Python dan library Pyrogram. Banyak fitur yang berguna untuk kita gunakan. Saya berharap suatu saat jika project ini dihentikan, ada yang melanjutkan atau mengembangkannya lagi. Saya memberi nama MissKaty karena saya suka kucing, hewan lucu yang suka bermain dan bersahabat dengan manusia.
🌱 Dukungan Python 3.11<br>
🌱 MongoDB sebagai Database<br> ## [2] Alat Kerangka Dan Server Yang Digunakan Untuk Membangun Bot Ini
🌱 PyKeyboard for Building Pagination<br> 🌱 PyroFork v2.x.x (Fork Pyrogram dengan Dukungan Topik, Stories dan Beberapa Patch)<br>
🌱 VS Code<br> 🌱 Dukungan Python 3.11<br>
🌱 VPS/Server With Docker Support (Recommended)<br> 🌱 MongoDB sebagai Database<br>
🌱 PyKeyboard for Building Pagination<br>
## [3] Donation 🌱 VS Code<br>
*Khusus Indonesia Saja:*<br> 🌱 VPS/Server dengan Root dan Docker Support (Recommended)<br>
🌱 [QRIS][qris-url]<br>
## [3] Donation
*Untuk Semua Negara:*<br> *Khusus Indonesia Saja:*<br>
🌱 [Paypal][paypal-url]<br> 🌱 [QRIS][qris-url]<br>
🌱 [Mayar ID][mayar]<br>
## [4] Notes
Jika Anda ingin membantu saya memperbaiki beberapa kesalahan di bot saya, Anda dapat membuat PR ke repo ini. Saya sangat senang jika Anda dapat membantu saya. Anda juga dapat memberikan dukungan kepada saya untuk membeli server. *Untuk Semua Negara:*<br>
🌱 [Paypal][paypal-url]<br>
## [5] Features
## [4] Notes
| FEATURE MY BOT |🌱| Jika Anda ingin membantu saya memperbaiki beberapa kesalahan di bot saya, Anda dapat membuat PR ke repo ini. Saya sangat senang jika Anda dapat membantu saya. Anda juga dapat memberikan dukungan kepada saya untuk membeli server.
| ------------- | ------------- |
| Basic Admin Feature |✔️| ## [5] Features
| AFK Feature |✔️|
| Downloader FB, TikTok and YT-DLP Support |✔️| | FEATURE MY BOT |🌱|
| MultiLanguage Support (Still Beta) |⚠️| | ------------- | ------------- |
| NightMode |✔️| | Basic Admin Feature |✔️|
| ChatBot based on OpenAI |✔️| | AFK Feature |✔️|
| MissKaty Mata |✔️| | Downloader FB, TikTok and YT-DLP Support |✔️|
| Inline Search |✔️| | MultiLanguage Support (Still Beta) |⚠️|
| Sticker Tools |✔️| | NightMode |✔️|
| PasteBin Tools |✔️| | ChatBot based on OpenAI, and Google Bard |✔️|
| WebScraper (Pahe, MelongMovie, LK21, Terbit21, Kusonime, etc) |✔️| | MissKaty Mata |✔️|
| IMDB Search With Multi Language Per User |✔️| | Inline Search |✔️|
| GenSS From Media and MediaInfo Generator |✔️| | Sticker Tools |✔️|
| And Many More.. |✔️| | PasteBin Tools |✔️|
| WebScraper (Pahe, MelongMovie, LK21, Terbit21, Kusonime, etc) |✔️|
## [6] Variables | IMDB Search With Multi Language Per User |✔️|
| GenSS From Media and MediaInfo Generator |✔️|
### Variabel yang Diperlukan | And Many More.. |✔️|
* `BOT_TOKEN`: Buat bot menggunakan [@BotFather](https://t.me/BotFather), dan dapatkan token Telegram API.
* `API_ID`: Dapatkan value ini dari [telegram.org](https://my.telegram.org/apps) ## [6] Variables
* `API_HASH`: Dapatkan value ini dari [telegram.org](https://my.telegram.org/apps)
* `DATABASE_URI`: [mongoDB](https://www.mongodb.com) URI. Dapatkan value ini dari [mongoDB](https://www.mongodb.com). Untuk bantuan lebih lanjut, tonton [video] ini(https://youtu.be/1G1XwEOnxxo) ### Variabel yang Diperlukan
* `LOG_CHANNEL` : Channel untuk mencatat aktivitas bot. Pastikan bot adalah admin di channel. * `BOT_TOKEN`: Buat bot menggunakan [@BotFather](https://t.me/BotFather), dan dapatkan token Telegram API.
* `API_ID`: Dapatkan value ini dari [telegram.org](https://my.telegram.org/apps)
### Variabel Opsional * `API_HASH`: Dapatkan value ini dari [telegram.org](https://my.telegram.org/apps)
* `USER_SESSION` : String session untuk Userbot. * `DATABASE_URI`: [mongoDB](https://www.mongodb.com) URI. Dapatkan value ini dari [mongoDB](https://www.mongodb.com).
* `DATABASE_NAME`: Nama database di MongoDB * `LOG_CHANNEL` : Channel untuk mencatat aktivitas bot. Pastikan bot adalah admin di channel.
* `COMMAND_HANDLER`: Daftar perintah handler bot dipisahkan dengan spasi. Contoh: `. !` > jadi bot akan merespon dengan `.cmd` atau `!cmd`
* `SUDO`: User ID yang memiliki akses ke bot, dipisahkan dengan spasi ### Variabel Opsional
* `OPENAI_API`: Dapatkan dari Web OpenAI * `YT_COOKIES` : Dapatkan cookies Youtube menggunakan https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc?pli=1 dan simpan isi file di github gist. Salin raw url dan isi di vars ini.
* `CURRENCY_API`: Dapatkan API Key di https://app.exchangerate-api.com/sign-up * `USER_SESSION` : String session untuk Userbot.
* `DATABASE_NAME`: Nama database di MongoDB
## [7] Tutorial Deploy (Recommended using Docker/Docker Compose) * `PAYDISINI_KEY`: Api Key PayDisini
* `PAYDISINI_CHANNEL_ID`: Channel ID QRIS paydisini
#### Bangun Dan Jalankan Menggunakan Metode Lama * `COMMAND_HANDLER`: Daftar perintah handler bot dipisahkan dengan spasi. Contoh: `. !` > jadi bot akan merespon dengan `.cmd` atau `!cmd`
- Pastikan versi python minimum adalah 3.8 untuk mencegah beberapa error. Periksa dengan perintah ini: * `SUDO`: User ID yang memiliki akses ke bot, dipisahkan dengan spasi
``` * `OPENAI_API`: Dapatkan dari Web OpenAI (Deprecated Temporary)
python3 --version * `GOOGLE_API`: Pelajari dari ini https://github.com/dsdanielpark/Bard-API untuk mendapatkan cookies dan set sebagai api key.
``` * `CURRENCY_API`: Dapatkan API Key di https://app.exchangerate-api.com/sign-up
- Instal semua dependensi yang membutuhkan bot untuk dijalankan. *(memerlukan akses root, Anda dapat melewati ini jika server Anda tidak memiliki akses root tetapi beberapa plugin tidak berfungsi)*
``` ## [7] Tutorial Deploy (Recommended using Docker/Docker Compose)
apt update -y & apt install libjpeg-dev zlib1g-dev libwebp-dev python3-pip python3-lxml git wget curl lokal ffmpeg tzdata neofetch mediainfo speedtest-cli -y
``` #### Bangun Dan Jalankan Menggunakan Metode Lama
- Instal requirements.txt, jika menggunakan python 3.11, Anda harus menggunakan opsi venv saat menginstal.<br/> - Pastikan versi python minimum adalah 3.8 dan maksimal python 3.11 untuk mencegah beberapa error. Periksa dengan perintah ini:
*Python < 3.10* ```
``` python3 --version
pip3 install -r requirements.txt ```
``` - Instal semua dependensi yang membutuhkan bot untuk dijalankan. *(memerlukan akses root, Anda dapat melewati ini jika server Anda tidak memiliki akses root tetapi beberapa plugin tidak berfungsi)*
*Python 3.11* ```
``` apt update -y & apt install libjpeg-dev zlib1g-dev libwebp-dev python3-pip python3-lxml git wget curl lokal ffmpeg tzdata neofetch mediainfo speedtest-cli -y
Install venv dari terminal server kamu ```
pip3 install -r requirements.txt - Instal requirements.txt, jika menggunakan python 3.11, Anda harus menggunakan opsi venv saat menginstal.<br/>
``` *Python < 3.10*
- Atur config environment saat menjalankan bot dan jangan lupa isi semua value yang wajib di isi. ```
- Jalankan Bot pip3 install -r requirements.txt
``` ```
bash start.sh *Python 3.11*
``` ```
python3 -m venv nama_venv
#### Build And Run Using Docker source nama_venv/bin/activate
pip3 install -r requirements.txt
- Mulai daemon Docker (Lewati jika sudah berjalan): ```
``` - Atur config environment saat menjalankan bot dan jangan lupa isi semua value yang wajib di isi.
sudo dockerd - Jalankan Bot
``` ```
- Build Docker image: bash start.sh
``` ```
sudo docker build . -t misskaty
``` #### Build And Run Using Docker
- Jalankan Docker image:
``` - Mulai daemon Docker (Lewati jika sudah berjalan):
sudo docker run misskaty ```
``` sudo dockerd
- Untuk Menghentikan image: ```
``` - Build Docker image:
sudo docker ps ```
sudo docker stop <pid> sudo docker build . -t misskaty
``` ```
- Jalankan Docker image:
#### Build And Run The Docker Image Using docker-compose ```
sudo docker run misskaty
- Install docker-compose ```
``` - Untuk Menghentikan image:
sudo apt install docker-compose ```
``` sudo docker ps
- Build and run Docker image or to view current running image: sudo docker stop <pid>
``` ```
sudo docker-compose up
``` #### Build And Run The Docker Image Using docker-compose
- After editing files with nano for example (nano start.sh):
``` - Install docker-compose
sudo docker-compose up --build ```
``` sudo apt install docker-compose
- To stop the running image: ```
``` - Build and run Docker image or to view current running image:
sudo docker ps ```
``` sudo docker-compose up
``` ```
sudo docker-compose stop <pid> - After editing files with nano for example (nano start.sh):
``` ```
sudo docker-compose up --build
---- ```
- To stop the running image:
```
## [8] Thanks to sudo docker ps
- Terima kasih Kepada Allah Swt. ```
- Terima kasih Kepada Dan [Pyrogram Library](https://github.com/pyrogram/pyrogram). ```
- Terima kasih Kepada [The Hamker Cat](https://github.com/TheHamkerCat) Untuk Kode WilliamButcher. sudo docker-compose stop <pid>
- Terima kasih Kepada [Team Yukki](https://github.com/TeamYukki) Untuk Kode AFK Bot. ```
- Terima kasih Kepada [Wrench](https://github.com/EverythingSuckz) Untuk Beberapa Kode.
- Terima kasih Kepada [AmanoTeam](https://github.com/AmanoTeam) Untuk Template MultiBahasa. ----
- Dan Semua Orang Yang Membantuku Dalam Hidupku...
Jika kode Anda digunakan dalam repo ini dan ingin memberikan kredit, silakan buka masalah..
## [8] Thanks to
## [9] Disclaimer - Terimakasih Kepada Allah Swt.
[![GNU Affero General Public License 2.0](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html#header) - Terimakasih Kepada Dan [Pyrogram Library](https://github.com/pyrogram/pyrogram) sebagai base pyrofork.
Dilisensikan di bawah [GNU AGPL 2.0.](https://github.com/yasirarism/MissKatyPyro/blob/master/LICENSE) - Terimakasih kepada Mayuri [Mayuri-Chan](https://github.com/Mayuri-Chan) sebagai pemilik library Pyrofork.
PERINGATAN: Menjual Kode Kepada Orang Lain Demi Uang *Dilarang Keras*. Tuhan selalu melihatmu dimanapun kamu berada. - Terimakasih kepada TeamDriveCok dan Secret Group TBK di Telegram.
- Terimakasih Kepada [The Hamker Cat](https://github.com/TheHamkerCat) Untuk Kode WilliamButcher.
<!--Url for Badges--> - Terimakasih Kepada [Team Yukki](https://github.com/TeamYukki) Untuk Kode AFK Bot.
[license-shield]: https://img.shields.io/github/license/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=04B4AE - Terimakasih Kepada [Wrench](https://github.com/EverythingSuckz) Untuk Beberapa Kode.
[repository-size-shield]: https://img.shields.io/github/repo-size/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=BE81F7 - Terimakasih Kepada [AmanoTeam](https://github.com/AmanoTeam) Untuk Template MultiBahasa.
[issue-closed-shield]: https://img.shields.io/github/issues-closed/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=FE9A2E - Dan Semua Orang Yang Membantuku Dalam Hidupku...
Jika kode Anda digunakan dalam repo ini dan ingin memberikan kredit, silakan buka masalah..
<!--Url for Buttons-->
[readme-ko-shield]: https://img.shields.io/badge/-readme%20in%20Indonesian-2E2E2E?style=for-the-badge ## [9] Disclaimer
[view-demo-shield]: https://img.shields.io/badge/-%F0%9F%98%8E%20view%20demo-F3F781?style=for-the-badge [![GNU Affero General Public License 2.0](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html#header)
[view-demo-url]: https://t.me/MissKatyPyro Dilisensikan di bawah [GNU AGPL 2.0.](https://github.com/yasirarism/MissKatyPyro/blob/master/LICENSE)
[report-bug-shield]: https://img.shields.io/badge/-%F0%9F%90%9E%20report%20bug-F5A9A9?style=for-the-badge PERINGATAN: *Dilarang Keras* Menjual Kode Kepada Orang Lain Demi Uang Tanpa Seijin Saya. Atau saya akan menghentikan project ini selamanyaa....
[report-bug-url]: https://github.com/yasirarism/MissKatyPyro/issues
[request-feature-shield]: https://img.shields.io/badge/-%E2%9C%A8%20request%20feature-A9D0F5?style=for-the-badge <!--Url for Badges-->
[request-feature-url]: https://github.com/yasirarism/MissKatyPyro/issues [license-shield]: https://img.shields.io/github/license/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=04B4AE
[repository-size-shield]: https://img.shields.io/github/repo-size/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=BE81F7
<!--URLS--> [issue-closed-shield]: https://img.shields.io/github/issues-closed/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=FE9A2E
[readme-ko-url]: README.md
[kofi-url]: https://ko-fi.com/yasirarism <!--Url for Buttons-->
[paypal-url]: https://paypal.me/yasirarism [readme-ko-shield]: https://img.shields.io/badge/-readme%20in%20Indonesian-2E2E2E?style=for-the-badge
[qris-url]: https://telegra.ph/file/2acf7698f300ef3d9138f.jpg [view-demo-shield]: https://img.shields.io/badge/-%F0%9F%98%8E%20view%20demo-F3F781?style=for-the-badge
[sociabuzz-url]: https://sociabuzz.com/yasirarism/tribe [view-demo-url]: https://t.me/MissKatyPyro
[saweria-url]: https://saweria.co/yasirarism [report-bug-shield]: https://img.shields.io/badge/-%F0%9F%90%9E%20report%20bug-F5A9A9?style=for-the-badge
[trakteer-url]: https://trakteer.id/yasir-aris-sp7cn [report-bug-url]: https://github.com/yasirarism/MissKatyPyro/issues
[request-feature-shield]: https://img.shields.io/badge/-%E2%9C%A8%20request%20feature-A9D0F5?style=for-the-badge
[request-feature-url]: https://github.com/yasirarism/MissKatyPyro/issues
<!--URLS-->
[readme-ko-url]: README.md
[kofi-url]: https://ko-fi.com/yasirarism
[paypal-url]: https://paypal.me/yasirarism
[qris-url]: https://img.yasirweb.eu.org/file/2acf7698f300ef3d9138f.jpg
[mayar]: https://yasirarism.mayar.link/payme
[sociabuzz-url]: https://sociabuzz.com/yasirarism/tribe
[saweria-url]: https://saweria.co/yasirarism
[trakteer-url]: https://trakteer.id/yasir-aris-sp7cn

399
README.md
View file

@ -1,194 +1,205 @@
# MissKatyPyro # MissKatyPyro
```diff
<!--Badges--> - I will not give any support to your fork, so try learn by yourself!! Don't contact me because of your fault. I will stop update to this repo, and i will only give minor bugfix to this repo.
![MIT License][license-shield] ![Repository Size][repository-size-shield] ![Issue Closed][issue-closed-shield] ```
<!--Project Title Image--> <!--Badges-->
<p align="center"> ![MIT License][license-shield] ![Repository Size][repository-size-shield] ![Issue Closed][issue-closed-shield]
<img src="https://repository-images.githubusercontent.com/433350689/26cb713b-43c3-4dec-94cb-6c80599547e8" width="200" height="200"/>
</p> <!--Project Title Image-->
<p align="center">
<!--Project Buttons--> <img src="https://repository-images.githubusercontent.com/433350689/26cb713b-43c3-4dec-94cb-6c80599547e8" width="200" height="200"/>
[![Readme in Indonesian][readme-ko-shield]][readme-ko-url] [![View Demo][view-demo-shield]][view-demo-url] [![Report bug][report-bug-shield]][report-bug-url] [![Request feature][request-feature-shield]][request-feature-url] </p>
<!--Table of Contents--> <!--Project Buttons-->
# Table of Contents [![Readme in Indonesian][readme-ko-shield]][readme-ko-url] [![View Demo][view-demo-shield]][view-demo-url] [![Report bug][report-bug-shield]][report-bug-url] <!-- [![Request feature][request-feature-shield]][request-feature-url] -->
- [[1] About MissKaty](#1-about-misskaty)
- [[2] Framework Tools And Server That Used To Build This Bot](#2-framework-tools-and-server-that-used-to-build-this-bot) <!--Table of Contents-->
- [[3] Donation](#3-donation) # Table of Contents
- [[4] Notes](#4-notes) - [[1] About MissKaty](#1-about-misskaty)
- [[5] Features](#5-features) - [[2] Framework Tools And Server That Used To Build This Bot](#2-framework-tools-and-server-that-used-to-build-this-bot)
- [[6] Variables](#6-variables) - [[3] Support Creator](#3-donation)
- [[7] Deploying Tutorial](#7-deploy-recommended-using-dockerdocker-compose) - [[4] Notes](#4-notes)
- [Build And Run Using Legacy Method](#build-and-run-using-legacy-method) - [[5] Features](#5-features)
- [Build And Run Using Docker](#build-and-run-using-docker) - [[6] Variables](#6-variables)
- [Build And Run The Docker Image Using docker-compose](#build-and-run-the-docker-image-using-docker-compose) - [[7] Deploying Tutorial](#7-deploy-recommended-using-dockerdocker-compose)
- [[8] Credits](#8-thanks-to) - [Build And Run Using Legacy Method](#build-and-run-using-legacy-method)
- [[9] Disclaimer](#8-disclaimer) - [Build And Run Using Docker](#build-and-run-using-docker)
- [Build And Run The Docker Image Using docker-compose](#build-and-run-the-docker-image-using-docker-compose)
# [1] About MissKaty - [[8] Credits](#8-thanks-to)
*MissKaty* is a Telegram Bot built using Python and the Pyrogram library. Many useful features for us to use. I hope that one day this project will be discontinued, someone will continue or develop it again. I gave the name MissKaty because I like cats, a cute animal that likes to be played with and friendly with humans. - [[9] Disclaimer](#8-disclaimer)
## [2] Framework Tools And Server That Used To Build This Bot # [1] About MissKaty
🌱 PyroFork v2.x.x (Fork of Pyrogram with Topics Support and Some Patch)<br> *MissKaty* is a Telegram Bot built using Python and the Pyrogram library. Many useful features for us to use. I hope that one day this project will be discontinued, someone will continue or develop it again. I gave the name MissKaty because I like cats, a cute animal that likes to be played with and friendly with humans.
🌱 Python 3.11 Support<br>
🌱 MongoDB as Database<br> ## [2] Framework Tools And Server That Used To Build This Bot
🌱 PyKeyboard for Building Pagination<br> 🌱 PyroFork v2.x.x (Fork of Pyrogram with Topics, Stories Support and Some Patch)<br>
🌱 VS Code<br> 🌱 Python 3.12 Support<br>
🌱 VPS/Server With Docker Support (Recommended)<br> 🌱 MongoDB as Database<br>
🌱 PyKeyboard for Building Pagination<br>
## [3] Donation and Support 🌱 VS Code<br>
*For Indonesian Only and some supported country:*<br> 🌱 VPS/Server With Root and Docker Support (Recommended)<br>
🌱 [QRIS][qris-url]<br>
## [3] Donation and Support
*For International Payment:*<br> *For Indonesian Only and some supported country:*<br>
🌱 [Paypal][paypal-url]<br> 🌱 [QRIS][qris-url]<br>
## [4] Notes *For International Payment:*<br>
If you want help me fixing some error in my bot, you can make pull request to this repo. I'm very glad if you can help me. You can also give support to me for buying server. 🌱 [Paypal][paypal-url]<br>
## [5] Features ## [4] Notes
If you want help me fixing some error in my bot, you can make pull request to this repo. I'm very glad if you can help me. You can also give support to me for buying server.
| FEATURE MY BOT |🌱|
| ------------- | ------------- | ## [5] Features
| Basic Admin Feature |✔️|
| AFK Feature |✔️| | FEATURE MY BOT |🌱|
| Downloader FB, TikTok and YT-DLP Support |✔️| | ------------- | ------------- |
| MultiLanguage Support (Unfinished) |⚠️| | Basic Admin Feature |✔️|
| NightMode |✔️| | AFK Feature |✔️|
| ChatBot based on OpenAI |✔️| | Downloader FB, TikTok and YT-DLP Support |✔️|
| MissKaty Mata |✔️| | MultiLanguage Support (Unfinished) |⚠️|
| Inline Search |✔️| | NightMode |✔️|
| Sticker Tools |✔️| | ChatBot based on OpenAI and Google Bard |✔️|
| PasteBin Tools |✔️| | MissKaty Mata |✔️|
| WebScraper (Pahe, MelongMovie, LK21, Terbit21, Kusonime, etc) |✔️| | Inline Search |✔️|
| IMDB Search With Multi Language Per User |✔️| | Sticker Tools |✔️|
| GenSS From Media and MediaInfo Generator |✔️| | PasteBin Tools |✔️|
| And Many More.. |✔️| | WebScraper (Pahe, MelongMovie, LK21, Terbit21, Kusonime, etc) |✔️|
| IMDB Search With Multi Language Per User |✔️|
## [6] Variables | GenSS From Media and MediaInfo Generator |✔️|
| And Many More.. |✔️|
### Required Variables
* `BOT_TOKEN`: Create a bot using [@BotFather](https://t.me/BotFather), and get the Telegram API token. ## [6] Variables
* `API_ID`: Get this value from [telegram.org](https://my.telegram.org/apps)
* `API_HASH`: Get this value from [telegram.org](https://my.telegram.org/apps) ### Required Variables
* `DATABASE_URI`: [mongoDB](https://www.mongodb.com) URI. Get this value from [mongoDB](https://www.mongodb.com). For more help watch this [video](https://youtu.be/1G1XwEOnxxo) * `BOT_TOKEN`: Create a bot using [@BotFather](https://t.me/BotFather), and get the Telegram API token.
* `LOG_CHANNEL` : A channel to log the activities of bot. Make sure bot is an admin in the channel. * `API_ID`: Get this value from [telegram.org](https://my.telegram.org/apps)
* `API_HASH`: Get this value from [telegram.org](https://my.telegram.org/apps)
### Optional Variables * `DATABASE_URI`: [mongoDB](https://www.mongodb.com) URI. Get this value from [mongoDB](https://www.mongodb.com).
* `USER_SESSION` : Session string for Userbot. * `LOG_CHANNEL` : A channel to log the activities of bot. Make sure bot is an admin in the channel.
* `DATABASE_NAME`: Name of the database in MongoDB
* `COMMAND_HANDLER`: List of handler bot command splitted by space. Ex: `. !` > so bot will respond with `.cmd` or `!cmd` ### Optional Variables
* `SUDO`: User ID that have access to bot, split by space * `YT_COOKIES` : Get YT cookies using https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc?pli=1 and save cookies value on github gist. Copy raw url and fill in this vars.
* `OPENAI_API`: Get it from OpenAI Web * `USER_SESSION` : Session string for Userbot.
* `CURRENCY_API`: Get API Key from https://app.exchangerate-api.com/sign-up * `DATABASE_NAME`: Name of the database in MongoDB
* `PAYDISINI_KEY`: Api Key PayDisini
## [7] Tutorial Deploy (Recommended using Docker/Docker Compose) * `PAYDISINI_CHANNEL_ID`: Channel ID QRIS paydisini
* `COMMAND_HANDLER`: List of handler bot command splitted by space. Ex: `. !` > so bot will respond with `.cmd` or `!cmd`
#### Build And Run Using Legacy Method * `SUDO`: User ID that have access to bot, split by space
- Make sure minimum python version is 3.8 to prevent some errors. Check it with this command: * `OPENAI_API`: Create personal access token from github, and set as this env. Make sure you have access to Github Model.
``` * `GOOGLEAI_KEY`: Learn how to get api key from this https://ai.google.dev/tutorials/python_quickstart?hl=en.
python3 --version * `CURRENCY_API`: Get API Key from https://app.exchangerate-api.com/sign-up
```
- Install all dependency that needed bot to run. *(need root access, you can skip this if your server didn't have root access but some plugins will not work)* ## [7] Tutorial Deploy (Recommended using Docker/Docker Compose)
```
apt update -y & apt install libjpeg-dev zlib1g-dev libwebp-dev python3-pip python3-lxml git wget curl ffmpeg locales tzdata neofetch mediainfo speedtest-cli -y #### Build And Run Using Legacy Method
``` - Make sure minimum python version is 3.8 and max python 3.12 to prevent some errors. Check it with this command:
- Install requirements.txt, if using python 3.11, you need use venv when install pip package.<br/> ```
*Python < 3.10* python3 --version
``` ```
pip3 install -r requirements.txt - Install all dependency that needed bot to run. *(need root access, you can skip this if your server didn't have root access but some plugins will not work)*
``` ```
*Python 3.11* apt update -y & apt install libjpeg-dev zlib1g-dev libwebp-dev python3-pip python3-lxml git wget curl ffmpeg locales tzdata neofetch mediainfo speedtest-cli -y
``` ```
Install venv from your terminal and activate it - Install requirements.txt, if using python => 3.11, you need use venv when install pip package.<br/>
pip3 install -r requirements.txt *Python < 3.10*
``` ```
- Setting your config.env or via environment and dont forget fill all required value. pip3 install -r requirements.txt
- Run Bot ```
``` *Python => 3.11*
bash start.sh ```
``` python3 -m venv nama_venv
source nama_venv/bin/activate
#### Build And Run Using Docker pip3 install -r requirements.txt
```
- Start Docker daemon (Skip if already running): - Setting your config.env or via environment and dont forget fill all required value.
``` - Run Bot
sudo dockerd ```
``` bash start.sh
- Build Docker image: ```
```
sudo docker build . -t misskaty #### Build And Run Using Docker
```
- Run the image: - Start Docker daemon (Skip if already running):
``` ```
sudo docker run misskaty sudo dockerd
``` ```
- To stop the image: - Build Docker image:
``` ```
sudo docker ps sudo docker build . -t misskaty
sudo docker stop <pid> ```
``` - Run the image:
```
#### Build And Run The Docker Image Using docker-compose sudo docker run misskaty
```
- Install docker-compose - To stop the image:
``` ```
sudo apt install docker-compose sudo docker ps
``` sudo docker stop <pid>
- Build and run Docker image or to view current running image: ```
```
sudo docker-compose up #### Build And Run The Docker Image Using docker-compose
```
- After editing files with nano for example (nano start.sh): - Install docker-compose
``` ```
sudo docker-compose up --build sudo apt install docker-compose
``` ```
- To stop the running image: - Build and run Docker image or to view current running image:
``` ```
sudo docker ps sudo docker-compose up
``` ```
``` - After editing files with nano for example (nano start.sh):
sudo docker-compose stop <pid> ```
``` sudo docker-compose up --build
```
---- - To stop the running image:
```
sudo docker ps
## [8] Thanks to ```
- Thanks To Allah Swt. ```
- Thanks To Dan For His Awesome [Library](https://github.com/pyrogram/pyrogram). sudo docker-compose stop <pid>
- Thanks To [The Hamker Cat](https://github.com/TheHamkerCat) For WilliamButcher Code. ```
- Thanks To [Team Yukki](https://github.com/TeamYukki) For AFK Bot Code.
- Thanks To [Wrench](https://github.com/EverythingSuckz) For Some Code. ----
- Thanks To [AmanoTeam](https://github.com/AmanoTeam) For MultiLanguage Template.
- And All People Who Help Me In My Life...
If your code used in this repo and want to give credit please open issue.. ## [8] Thanks to
- Thanks To Allah Swt.
## [9] Disclaimer - Thanks To Dan For [Pyrogram Library](https://github.com/pyrogram/pyrogram) as founder of pyrogram.
[![GNU Affero General Public License 2.0](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html#header) - Thanks To Mayuri For [Pyrofork Library](https://github.com/Mayuri-Chan) as owner of pyrofork library.
Licensed under [GNU AGPL 2.0.](https://github.com/yasirarism/MissKatyPyro/blob/master/LICENSE) - Thanks To TeamDrivecok and SecretGroup TBK in Telegram.
WARNING: Selling The Codes To Other People For Money Is *Strictly Prohibited*. God always sees you. - Thanks To [The Hamker Cat](https://github.com/TheHamkerCat) For WilliamButcher Code.
- Thanks To [Team Yukki](https://github.com/TeamYukki) For AFK Bot Code.
<!--Url for Badges--> - Thanks To [Wrench](https://github.com/EverythingSuckz) For Some Code.
[license-shield]: https://img.shields.io/github/license/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=04B4AE - Thanks To [AmanoTeam](https://github.com/AmanoTeam) For MultiLanguage Template.
[repository-size-shield]: https://img.shields.io/github/repo-size/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=BE81F7 - And All People Who Help Me In My Life...
[issue-closed-shield]: https://img.shields.io/github/issues-closed/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=FE9A2E If your code used in this repo and want to give credit please open issue..
<!--Url for Buttons--> ## [9] Disclaimer
[readme-ko-shield]: https://img.shields.io/badge/-readme%20in%20Indonesian-2E2E2E?style=for-the-badge [![GNU Affero General Public License 2.0](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html#header)
[view-demo-shield]: https://img.shields.io/badge/-%F0%9F%98%8E%20view%20demo-F3F781?style=for-the-badge Licensed under [GNU AGPL 2.0.](https://github.com/yasirarism/MissKatyPyro/blob/master/LICENSE)
[view-demo-url]: https://t.me/MissKatyPyro WARNING: Selling The Codes To Other People For Money Is *Strictly Prohibited*. Or i will stop this project forever.
[report-bug-shield]: https://img.shields.io/badge/-%F0%9F%90%9E%20report%20bug-F5A9A9?style=for-the-badge
[report-bug-url]: https://github.com/yasirarism/MissKatyPyro/issues <!--Url for Badges-->
[request-feature-shield]: https://img.shields.io/badge/-%E2%9C%A8%20request%20feature-A9D0F5?style=for-the-badge [license-shield]: https://img.shields.io/github/license/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=04B4AE
[request-feature-url]: https://github.com/yasirarism/MissKatyPyro/issues [repository-size-shield]: https://img.shields.io/github/repo-size/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=BE81F7
[issue-closed-shield]: https://img.shields.io/github/issues-closed/yasirarism/MissKatyPyro?labelColor=D8D8D8&color=FE9A2E
<!--URLS-->
[readme-ko-url]: README.id.md <!--Url for Buttons-->
[kofi-url]: https://ko-fi.com/yasirarism [readme-ko-shield]: https://img.shields.io/badge/-readme%20in%20Indonesian-2E2E2E?style=for-the-badge
[paypal-url]: https://paypal.me/yasirarism [view-demo-shield]: https://img.shields.io/badge/-%F0%9F%98%8E%20view%20demo-F3F781?style=for-the-badge
[qris-url]: https://telegra.ph/file/9427d61d6968b8ee4fb2f.jpg [view-demo-url]: https://t.me/MissKatyBot
[sociabuzz-url]: https://sociabuzz.com/yasirarism/tribe [report-bug-shield]: https://img.shields.io/badge/-%F0%9F%90%9E%20report%20bug-F5A9A9?style=for-the-badge
[saweria-url]: https://saweria.co/yasirarism [report-bug-url]: https://github.com/yasirarism/MissKatyPyro/issues
[trakteer-url]: https://trakteer.id/yasir-aris-sp7cn [request-feature-shield]: https://img.shields.io/badge/-%E2%9C%A8%20request%20feature-A9D0F5?style=for-the-badge
[request-feature-url]: https://github.com/yasirarism/MissKatyPyro/issues
<!--URLS-->
[readme-ko-url]: README.id.md
[kofi-url]: https://ko-fi.com/yasirarism
[paypal-url]: https://paypal.me/yasirarism
[qris-url]: https://img.yasirweb.eu.org/file/ee74ce527fb8264b54691.jpg
[mayar]: https://yasirarism.mayar.link/payme
[sociabuzz-url]: https://sociabuzz.com/yasirarism/tribe
[saweria-url]: https://saweria.co/yasirarism
[trakteer-url]: https://trakteer.id/yasir-aris-sp7cn

61
app.json Normal file
View file

@ -0,0 +1,61 @@
{
"name": "MissKaty RoBot",
"description": "Multi Function Telegram Bot, made in python using Pyrofork.",
"logo": "https://img.yasirweb.eu.org/file/2136400c0c5505d1b41e4.jpg",
"keywords": [
"pyrogram",
"pyrofork",
"telegram",
"userbot",
"python",
"group-management"
],
"repository": "https://git.yasirweb.eu.org/yasirarism/MissKatyPyro",
"website": "https://yasirpedia.eu.orh",
"success_url": "https://t.me/YasirPediaChannel",
"stack": "container",
"env": {
"API_ID": {
"description": "Your api id, from my.telegram.org or @ScrapperRoBot.",
"value": "",
"required": false
},
"API_HASH": {
"description": "Your api hash, from my.telegram.org or @ScrapperRoBot.",
"value": "",
"required": false
},
"USER_SESSION": {
"description": "Session String (pyrogram) for your telegram user account. The userbot will NOT work without a session string!!",
"value": ""
},
"DATABASE_URI": {
"description": "Mongodb URL",
"value": ""
},
"SUDO": {
"description": "Allowed user in to use sudo command.",
"value": ""
},
"OWNER_ID": {
"description": "OWNER OF THIS BOT.",
"value": ""
},
"GOOGLE_AI_KEY": {
"description": "Your Gemini AI Key",
"value": "",
"required": false
},
"DATABASE_NAME": {
"description": "Your database name.",
"value": "",
"required": false
}
},
"formation": {
"misskaty": {
"quantity": 1,
"size": "basic"
}
}
}

View file

@ -2,7 +2,7 @@
API_HASH= API_HASH=
API_ID= API_ID=
BOT_TOKEN= BOT_TOKEN=
DATABASE_URI=mongodb+srv:// DATABASE_URI=
LOG_CHANNEL= LOG_CHANNEL=
# Optional Vars # Optional Vars
@ -12,4 +12,5 @@ SUPPORT_CHAT=YasirPediaChannel
COMMAND_HANDLER= COMMAND_HANDLER=
USER_SESSION= USER_SESSION=
OPENAI_API= OPENAI_API=
GOOGLEAI_API=
CURRENCY_API= CURRENCY_API=

View file

@ -1,9 +1,10 @@
""" """
* @author yasir <yasiramunandar@gmail.com> * @author yasir <yasiramunandar@gmail.com>
* @date 2022-09-06 10:12:09 * @date 2022-09-06 10:12:09
* @projectName MissKatyPyro * @projectName MissKatyPyro
* Copyright @YasirPedia All rights reserved * Copyright @YasirPedia All rights reserved
""" """
from async_pymongo import AsyncClient from async_pymongo import AsyncClient
from misskaty.vars import DATABASE_NAME, DATABASE_URI from misskaty.vars import DATABASE_NAME, DATABASE_URI

View file

@ -1,33 +1,35 @@
from database import dbname from typing import List
from typing import List
from database import dbname
blacklist_filtersdb = dbname["blacklistFilters"]
blacklist_filtersdb = dbname["blacklistFilters"]
async def get_blacklisted_words(chat_id: int) -> List[str]:
_filters = await blacklist_filtersdb.find_one({"chat_id": chat_id})
if not _filters: async def get_blacklisted_words(chat_id: int) -> List[str]:
return [] _filters = await blacklist_filtersdb.find_one({"chat_id": chat_id})
return _filters["filters"] return [] if not _filters else _filters["filters"]
async def save_blacklist_filter(chat_id: int, word: str):
word = word.lower().strip() async def save_blacklist_filter(chat_id: int, word: str):
_filters = await get_blacklisted_words(chat_id) word = word.lower().strip()
_filters.append(word) _filters = await get_blacklisted_words(chat_id)
await blacklist_filtersdb.update_one( _filters.append(word)
{"chat_id": chat_id}, await blacklist_filtersdb.update_one(
{"$set": {"filters": _filters}}, {"chat_id": chat_id},
upsert=True, {"$set": {"filters": _filters}},
) upsert=True,
)
async def delete_blacklist_filter(chat_id: int, word: str) -> bool:
filtersd = await get_blacklisted_words(chat_id)
word = word.lower().strip() async def delete_blacklist_filter(chat_id: int, word: str) -> bool:
if word in filtersd: filtersd = await get_blacklisted_words(chat_id)
filtersd.remove(word) word = word.lower().strip()
await blacklist_filtersdb.update_one( if word in filtersd:
{"chat_id": chat_id}, filtersd.remove(word)
{"$set": {"filters": filtersd}}, await blacklist_filtersdb.update_one(
upsert=True, {"chat_id": chat_id},
) {"$set": {"filters": filtersd}},
return True upsert=True,
return False )
return True
return False

153
database/feds_db.py Normal file
View file

@ -0,0 +1,153 @@
from datetime import datetime
import pytz
from database import dbname
from misskaty.vars import SUDO, OWNER_ID
fedsdb = dbname["federation"]
def get_fed_info(fed_id):
get = fedsdb.find_one({"fed_id": str(fed_id)})
return False if get is None else get
async def get_fed_id(chat_id):
get = await fedsdb.find_one({"chat_ids.chat_id": int(chat_id)})
if get is None:
return False
return next(
(
get["fed_id"]
for chat_info in get.get("chat_ids", [])
if chat_info["chat_id"] == int(chat_id)
),
False,
)
async def get_feds_by_owner(owner_id):
cursor = fedsdb.find({"owner_id": owner_id})
feds = await cursor.to_list(length=None)
if not feds:
return False
return [{"fed_id": fed["fed_id"], "fed_name": fed["fed_name"]} for fed in feds]
async def transfer_owner(fed_id, current_owner_id, new_owner_id):
if await is_user_fed_owner(fed_id, current_owner_id):
await fedsdb.update_one(
{"fed_id": fed_id, "owner_id": current_owner_id},
{"$set": {"owner_id": new_owner_id}},
)
return True
else:
return False
async def set_log_chat(fed_id, log_group_id: int):
await fedsdb.update_one(
{"fed_id": fed_id}, {"$set": {"log_group_id": log_group_id}}
)
return
async def get_fed_name(chat_id):
get = await fedsdb.find_one(int(chat_id))
return False if get is None else get["fed_name"]
async def is_user_fed_owner(fed_id, user_id: int):
getfed = await get_fed_info(fed_id)
if not getfed:
return False
owner_id = getfed["owner_id"]
return user_id == owner_id or user_id not in SUDO or user_id != OWNER_ID
async def search_fed_by_id(fed_id):
get = await fedsdb.find_one({"fed_id": str(fed_id)})
return get if get is not None else False
def chat_join_fed(fed_id, chat_name, chat_id):
return fedsdb.update_one(
{"fed_id": fed_id},
{"$push": {"chat_ids": {"chat_id": int(chat_id), "chat_name": chat_name}}},
)
async def chat_leave_fed(chat_id):
result = await fedsdb.update_one(
{"chat_ids.chat_id": int(chat_id)},
{"$pull": {"chat_ids": {"chat_id": int(chat_id)}}},
)
return result.modified_count > 0
async def user_join_fed(fed_id, user_id):
result = await fedsdb.update_one(
{"fed_id": fed_id}, {"$addToSet": {"fadmins": int(user_id)}}, upsert=True
)
return result.modified_count > 0
async def user_demote_fed(fed_id, user_id):
result = await fedsdb.update_one(
{"fed_id": fed_id}, {"$pull": {"fadmins": int(user_id)}}
)
return result.modified_count > 0
async def search_user_in_fed(fed_id, user_id):
getfed = await search_fed_by_id(fed_id)
return False if getfed is None else user_id in getfed["fadmins"]
async def chat_id_and_names_in_fed(fed_id):
getfed = await search_fed_by_id(fed_id)
if getfed is None or "chat_ids" not in getfed:
return [], []
chat_ids = [chat["chat_id"] for chat in getfed["chat_ids"]]
chat_names = [chat["chat_name"] for chat in getfed["chat_ids"]]
return chat_ids, chat_names
async def add_fban_user(fed_id, user_id, reason):
current_date = datetime.now(pytz.timezone("Asia/Jakarta")).strftime(
"%Y-%m-%d %H:%M"
)
await fedsdb.update_one(
{"fed_id": fed_id},
{
"$push": {
"banned_users": {
"user_id": int(user_id),
"reason": reason,
"date": current_date,
}
}
},
upsert=True,
)
async def remove_fban_user(fed_id, user_id):
await fedsdb.update_one(
{"fed_id": fed_id}, {"$pull": {"banned_users": {"user_id": int(user_id)}}}
)
async def check_banned_user(fed_id, user_id):
result = await fedsdb.find_one({"fed_id": fed_id, "banned_users.user_id": user_id})
if result and "banned_users" in result:
for user in result["banned_users"]:
if user.get("user_id") == user_id:
return {"reason": user.get("reason"), "date": user.get("date")}
return False

View file

@ -24,6 +24,10 @@ async def delete_filter(chat_id: int, name: str) -> bool:
return False return False
async def deleteall_filters(chat_id: int):
return await filtersdb.delete_one({"chat_id": chat_id})
async def get_filter(chat_id: int, name: str) -> Union[bool, dict]: async def get_filter(chat_id: int, name: str) -> Union[bool, dict]:
name = name.lower().strip() name = name.lower().strip()
_filters = await _get_filters(chat_id) _filters = await _get_filters(chat_id)

View file

@ -1,22 +1,22 @@
from database import dbname from database import dbname
gbansdb = dbname["gban"] gbansdb = dbname["gban"]
async def is_gbanned_user(user_id: int) -> bool: async def is_gbanned_user(user_id: int) -> bool:
user = await gbansdb.find_one({"user_id": user_id}) user = await gbansdb.find_one({"user_id": user_id})
return bool(user) return bool(user)
async def add_gban_user(user_id: int): async def add_gban_user(user_id: int):
is_gbanned = await is_gbanned_user(user_id) is_gbanned = await is_gbanned_user(user_id)
if is_gbanned: if is_gbanned:
return return
return await gbansdb.insert_one({"user_id": user_id}) return await gbansdb.insert_one({"user_id": user_id})
async def remove_gban_user(user_id: int): async def remove_gban_user(user_id: int):
is_gbanned = await is_gbanned_user(user_id) is_gbanned = await is_gbanned_user(user_id)
if not is_gbanned: if not is_gbanned:
return return
return await gbansdb.delete_one({"user_id": user_id}) return await gbansdb.delete_one({"user_id": user_id})

19
database/greetings_db.py Normal file
View file

@ -0,0 +1,19 @@
from database import dbname
greetingdb = dbname["greetings"]
async def is_welcome(chat_id: int) -> bool:
return bool(await greetingdb.find_one({"chat_id": chat_id}))
async def toggle_welcome(chat_id: int):
if await is_welcome(chat_id):
await greetingdb.delete_one({"chat_id": chat_id})
return False
else:
await greetingdb.insert_one({"chat_id": chat_id})
return True
# todo other features for custom welcome here

View file

@ -42,3 +42,7 @@ async def save_note(chat_id: int, name: str, note: dict):
await notesdb.update_one( await notesdb.update_one(
{"chat_id": chat_id}, {"$set": {"notes": _notes}}, upsert=True {"chat_id": chat_id}, {"$set": {"notes": _notes}}, upsert=True
) )
async def deleteall_notes(chat_id: int):
return await notesdb.delete_one({"chat_id": chat_id})

16
database/payment_db.py Normal file
View file

@ -0,0 +1,16 @@
from database import dbname
from typing import Optional
autopay = dbname["autpay"]
async def delete_autopay(uniqueCode: str):
await autopay.delete_one({"_id": uniqueCode})
async def get_autopay(uniqueCode: str):
exists = await autopay.find_one({"_id": uniqueCode})
return exists
async def autopay_update(msg_id: Optional[int] = "", note: Optional[str] = "", user_id: Optional[int] = "", amount: Optional[int] = "", status: Optional[str] = "", uniqueCode: Optional[str] = "", createdAt: Optional[str] = ""):
data = {"msg_id": msg_id, "note": note, "user_id": user_id, "amount": amount, "status": status, "createdAt": createdAt}
await autopay.update_one({"_id": uniqueCode}, {"$set": data}, upsert=True)

View file

@ -7,7 +7,7 @@ class UsersData:
def __init__(self, uri, database_name): def __init__(self, uri, database_name):
self._client = AsyncClient(uri) self._client = AsyncClient(uri)
self.db = self._client[database_name] self.db = self._client[database_name]
self.col = self.db["users"] self.col = self.db["userlist"]
self.grp = self.db["groups"] self.grp = self.db["groups"]
@staticmethod @staticmethod
@ -44,17 +44,14 @@ class UsersData:
return await self.col.count_documents({}) return await self.col.count_documents({})
async def remove_ban(self, id): async def remove_ban(self, id):
ban_status = dict(is_banned=False, ban_reason="") return await self.col.delete_one({"_id": id})
await self.col.update_one({"id": id}, {"$set": {"ban_status": ban_status}})
async def ban_user(self, user_id, ban_reason="No Reason"): async def ban_user(self, user_id, ban_reason="No Reason"):
ban_status = dict(is_banned=True, ban_reason=ban_reason) return await self.col.insert_one({"_id": user_id, "reason": ban_reason})
await self.col.update_one({"id": user_id}, {"$set": {"ban_status": ban_status}})
async def get_ban_status(self, id): async def get_ban_status(self, id):
default = dict(is_banned=False, ban_reason="") user = await self.col.find_one({"_id": int(id)})
user = await self.col.find_one({"id": int(id)}) return user if user else False
return user.get("ban_status", default) if user else default
async def get_all_users(self): async def get_all_users(self):
return self.col.find({}) return self.col.find({})

View file

@ -1,9 +1,15 @@
version: "3.3"
services: services:
app: misskaty:
image: misskaty
container_name: misskaty
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
command: bash start.sh command: bash start.sh
restart: on-failure restart: on-failure
# restarter:
# image: docker:cli
# volumes: ["/var/run/docker.sock:/var/run/docker.sock"]
# command: ["/bin/sh", "-c", "while true; do sleep 259200; docker restart misskaty; done"]
# restart: on-failure

View file

@ -1,3 +1,3 @@
build: build:
docker: docker:
worker: Dockerfile misskaty: Dockerfile

View file

@ -25,8 +25,8 @@
"ban_admin_err": "Lol, it's crazy if i can banned an admin.", "ban_admin_err": "Lol, it's crazy if i can banned an admin.",
"mute_admin_err": "Lol, it's crazy if i can mute an admin.", "mute_admin_err": "Lol, it's crazy if i can mute an admin.",
"warn_admin_err": "Lol, it's crazy if i can warn an admin.", "warn_admin_err": "Lol, it's crazy if i can warn an admin.",
"kick_msg": "**Kicked User:** {mention} [{id}]\n**Kicked By:** {kicker}\n**Reason:** {reasonmsg}", "kick_msg": "**Kicked User:** {mention} [`{id}`]\n**Kicked By:** {kicker}\n**Reason:** {reasonmsg}",
"ban_msg": "**Banned User:** {mention} [{id}]\n**Banned By:** {banner}\n", "ban_msg": "**Banned User:** {mention} [`{id}`]\n**Banned By:** {banner}\n",
"unban_msg": "__Banned removed by {mention}__", "unban_msg": "__Banned removed by {mention}__",
"no_ban_permission": "Please give me ban permission to ban user in this group.", "no_ban_permission": "Please give me ban permission to ban user in this group.",
"no_more_99": "You can't use more than 99", "no_more_99": "You can't use more than 99",

View file

@ -1,5 +1,5 @@
{ {
"no_question": "Please use command <code>/{cmd} [question]</code> to ask your question.", "no_question": "Please use command <code>/{cmd} [question]</code> to ask your question with AI.",
"find_answers_str": "Wait a moment looking for your answer..", "find_answers_str": "Wait a moment looking for your answer..",
"dont_spam": "Don't spam please, please wait {tm} second or i will ban you from this bot.", "dont_spam": "Don't spam please, please wait {tm} second or i will ban you from this bot.",
"answers_too_long": "Question for your answer has exceeded TG text limit, check this link to view.\n\n{answerlink}" "answers_too_long": "Question for your answer has exceeded TG text limit, check this link to view.\n\n{answerlink}"

View file

@ -1,11 +1,12 @@
{ {
"already_up": "Its already up-to date!", "already_up": "It's already up-to-date!",
"up_and_rest": "<b>Updated with default branch, restarting now.</b>", "up_and_rest": "<b>Updated with default branch, restarting now.</b>",
"cl_btn": "❌ Close", "cl_btn": "❌ Close",
"no_eval": "__No evaluate message!__", "no_eval": "__No evaluate message!__",
"privacy_policy": "<b>Privacy Policy For {botname}</b>\n\n<b>Effective Date:</b> 02 August 2024\n\nThis Privacy Policy explains how we collect, use, and protect your information when you interact with our Telegram bot designed for group management.\n\n<b>1. Information We Collect</b>\n\nWhen you use our Telegram bot, we may collect the following types of information:\n- <b>User Information</b>: Basic information such as your Telegram ID, username, and any other data you choose to provide.\n- <b>Group Information</b>: Data related to the groups you manage, including group ID, group name, and member details.\n\n<b>2. How We Use Your Information</b>\n\nWe use the collected information to:\n- Manage and maintain group functionalities.\n- Provide support and improve our services.\n- Communicate important updates or changes related to the bot.\n\n<b>3. How We Protect Your Information</b>\n\nWe implement various security measures to protect your information from unauthorized access, alteration, disclosure, or destruction. However, please be aware that no method of transmission over the internet or electronic storage is 100% secure.\n\n<b>4. Sharing Your Information</b>\n\nWe do not share your information with third parties, except as required by law or as necessary to provide the services described.\n\n<b>5. Your Rights</b>\n\nYou have the right to access, correct, or delete your personal information. If you wish to exercise any of these rights, please contact us directly through the bot.\n\n<b>6. Changes to This Privacy Policy</b>\n\nWe may update this Privacy Policy from time to time. We will notify you of any significant changes by updating the policy in the bot. It is your responsibility to review this policy periodically.\n\n<b>7. Contact Us</b>\n\nIf you have any questions or concerns about this Privacy Policy or our practices, please contact us my owner.",
"run_eval": "<i>Processing eval pyrogram..</i>", "run_eval": "<i>Processing eval pyrogram..</i>",
"run_exec": "<i>Processing exec pyrogram..</i>", "run_exec": "<i>Processing exec pyrogram..</i>",
"no_cmd": "No command to execute was given.", "no_cmd": "No command to execute was given.",
"success": "Success", "success": "Success",
"no_reply": "No Reply" "no_reply": "No Reply"
} }

View file

@ -1,5 +1,6 @@
{ {
"back_btn": "« Go back", "back_btn": "« Go back",
"no_results": "No Results.", "no_results": "No Results.",
"unknown_id": "Sorry I can't recognize this user. Maybe I've never met him.",
"exp_task": "😶‍🌫️ Timeout. Task has been cancelled!" "exp_task": "😶‍🌫️ Timeout. Task has been cancelled!"
} }

View file

@ -9,5 +9,6 @@
"err_parse": "Failed parse URL, check logs..", "err_parse": "Failed parse URL, check logs..",
"wait": "Please wait..", "wait": "Please wait..",
"unauth": "Not Your Task..", "unauth": "Not Your Task..",
"endlist": "That's the end of list" "endlist": "That's the end of list",
"vip-btn": "Because some user abuse and my server cannot handle it, best video now only for bot owner."
} }

View file

@ -25,8 +25,8 @@
"ban_admin_err": "Hah, sungguh gila jika saya bisa melarang seorang admin.", "ban_admin_err": "Hah, sungguh gila jika saya bisa melarang seorang admin.",
"mute_admin_err": "Hah, sungguh gila jika saya bisa membisukan admin.", "mute_admin_err": "Hah, sungguh gila jika saya bisa membisukan admin.",
"warn_admin_err": "Hah, sungguh gila jika saya bisa memperingatkan seorang admin.", "warn_admin_err": "Hah, sungguh gila jika saya bisa memperingatkan seorang admin.",
"kick_msg": "**Pengguna yang Ditendang:** {mention} [{id}]\n**Ditendang Oleh:** {kicker}\n**Alasan:** {reasonmsg}", "kick_msg": "**Pengguna yang Ditendang:** {mention} [`{id}`]\n**Ditendang Oleh:** {kicker}\n**Alasan:** {reasonmsg}",
"ban_msg": "**Pengguna yang Dilarang:** {mention} [{id}]\n**Dilarang Oleh:** {banner}\n", "ban_msg": "**Pengguna yang Dibanned:** {mention} [`{id}`]\n**Dibanned Oleh:** {banner}\n",
"unban_msg": "__Banned dihapus oleh {mention}__", "unban_msg": "__Banned dihapus oleh {mention}__",
"no_ban_permission": "Tolong beri saya izin banned untuk membanned pengguna di grup ini.", "no_ban_permission": "Tolong beri saya izin banned untuk membanned pengguna di grup ini.",
"no_more_99": "Anda tidak dapat menggunakan lebih dari 99", "no_more_99": "Anda tidak dapat menggunakan lebih dari 99",

View file

@ -1,6 +1,6 @@
{ {
"no_question": "Harap gunakan perintah <code>/{cmd} [question]</code> untuk mengajukan pertanyaan Anda menggunakan OpenAI.", "no_question": "Harap gunakan perintah <code>/{cmd} [question]</code> untuk mengajukan pertanyaan Anda menggunakan AI.",
"find_answers_str": "Sedang mencari jawaban terbaik buat Anda..", "find_answers_str": "Sedang mencari jawaban terbaik buat Anda..",
"dont_spam": "Tolong jangan melakukan spam, harap tunggu {tm} detik atau saya akan membanned Anda dari bot ini.", "dont_spam": "Tolong jangan melakukan spam, harap tunggu {tm} detik atau saya akan membanned Anda dari bot ini.",
"answers_too_long": "Pertanyaan untuk jawaban Anda telah melampaui batas teks TG, periksa tautan ini untuk melihatnya.\n\n{answerlink}" "answers_too_long": "Jawaban untuk pertanyaan Anda telah melampaui limit batas teks Telegram, periksa tautan ini untuk melihatnya.\n\n{answerlink}"
} }

View file

@ -3,6 +3,7 @@
"up_and_rest": "<b>Diperbarui dengan branch default, dimulai ulang sekarang.</b>", "up_and_rest": "<b>Diperbarui dengan branch default, dimulai ulang sekarang.</b>",
"cl_btn": "❌ Tutup", "cl_btn": "❌ Tutup",
"no_eval": "__Tidak ada pesan eval!__", "no_eval": "__Tidak ada pesan eval!__",
"privacy_policy": "<b>Kebijakan Privasi Untuk {botname}</b>\n\n<b>Tanggal Efektif:</b> 02 Agustus 2024\n\nKebijakan Privasi ini menjelaskan bagaimana kami mengumpulkan, menggunakan, dan melindungi informasi Anda saat Anda berinteraksi dengan bot Telegram kami yang dirancang untuk manajemen grup.\n\n<b>1. Informasi yang Kami Kumpulkan</b>\n\nSaat Anda menggunakan bot Telegram kami, kami dapat mengumpulkan jenis informasi berikut:\n- <b>Informasi Pengguna</b>: Informasi dasar seperti ID Telegram Anda, nama pengguna, dan data lain yang Anda pilih untuk diberikan.\n- <b>Informasi Grup</b>: Data yang terkait dengan grup yang Anda kelola, termasuk ID grup, nama grup, dan detail anggota.\n\n<b>2. Bagaimana Kami Menggunakan Informasi Anda</b>\n\nKami menggunakan informasi yang dikumpulkan untuk:\n- Mengelola dan memelihara fungsionalitas grup.\n- Memberikan dukungan dan meningkatkan layanan kami.\n- Mengomunikasikan pembaruan atau perubahan penting yang terkait dengan bot.\n\n<b>3. Cara Kami Melindungi Informasi Anda</b>\n\nKami menerapkan berbagai langkah keamanan untuk melindungi informasi Anda dari akses, perubahan, pengungkapan, atau penghancuran yang tidak sah. Namun, perlu diketahui bahwa tidak ada metode transmisi melalui internet atau penyimpanan elektronik yang 100% aman.\n\n<b>4. Berbagi Informasi Anda</b>\n\nKami tidak membagikan informasi Anda dengan pihak ketiga, kecuali sebagaimana diwajibkan oleh hukum atau sebagaimana diperlukan untuk menyediakan layanan yang dijelaskan.\n\n<b>5. Hak Anda</b>\n\nAnda memiliki hak untuk mengakses, memperbaiki, atau menghapus informasi pribadi Anda. Jika Anda ingin menggunakan salah satu hak ini, silakan hubungi kami langsung melalui bot.\n\n<b>6. Perubahan pada Kebijakan Privasi Ini</b>\n\nKami dapat memperbarui Kebijakan Privasi ini dari waktu ke waktu. Kami akan memberi tahu Anda tentang setiap perubahan signifikan dengan memperbarui kebijakan di bot. Merupakan tanggung jawab Anda untuk meninjau kebijakan ini secara berkala.\n\n<b>7. Hubungi Kami</b>\n\nJika Anda memiliki pertanyaan atau masalah tentang Kebijakan Privasi ini atau praktik kami, silakan hubungi kami sebagai pemilik saya.",
"run_eval": "<i>Memproses pyrogram eval..</i>", "run_eval": "<i>Memproses pyrogram eval..</i>",
"run_exec": "<i>Memproses pyrogram eksekutif..</i>", "run_exec": "<i>Memproses pyrogram eksekutif..</i>",
"no_cmd": "Tidak ada perintah untuk dieksekusi.", "no_cmd": "Tidak ada perintah untuk dieksekusi.",

View file

@ -1,5 +1,6 @@
{ {
"back_btn": "« Kembali", "back_btn": "« Kembali",
"unknown_id": "Maaf saya tidak bisa mengenali pengguna ini. Mungkin saya belum pernah bertemu dengannya.",
"no_results": "Tidak ada hasil yang ditemukan.", "no_results": "Tidak ada hasil yang ditemukan.",
"exp_task": "😶‍🌫️ Waktu Habis. Tugas Telah Dibatalkan!" "exp_task": "😶‍🌫️ Waktu Habis. Tugas Telah Dibatalkan!"
} }

View file

@ -9,5 +9,6 @@
"err_parse": "Gagal menguraikan URL, periksa log..", "err_parse": "Gagal menguraikan URL, periksa log..",
"tunggu": "Harap tunggu..", "tunggu": "Harap tunggu..",
"unauth": "Bukan Tugas Anda..", "unauth": "Bukan Tugas Anda..",
"endlist": "Itu adalah akhir dari daftar" "endlist": "Itu adalah akhir dari daftar",
"vip-btn": "Karena beberapa penyalahgunaan pengguna, video terbaik sekarang hanya untuk pemilik bot."
} }

View file

@ -25,8 +25,8 @@
"ban_admin_err": "Lol, edan yen aku bisa nglarang admin.", "ban_admin_err": "Lol, edan yen aku bisa nglarang admin.",
"mute_admin_err": "Lol, edan yen aku bisa bisu admin.", "mute_admin_err": "Lol, edan yen aku bisa bisu admin.",
"warn_admin_err": "Lol, edan yen aku bisa ngelingake admin.", "warn_admin_err": "Lol, edan yen aku bisa ngelingake admin.",
"kick_msg": "**Panganggo Ditendhang:** {mention} [{id}]\n**Ditendhang dening:** {kicker}\n**Alasan:** {reasonmsg}", "kick_msg": "**Panganggo Ditendhang:** {mention} [`{id}`]\n**Ditendhang dening:** {kicker}\n**Alasan:** {reasonmsg}",
"ban_msg": "**Panganggo sing Dicekal:** {mention} [{id}]\n**Dicekal Dening:** {banner}\n", "ban_msg": "**Panganggo sing Dicekal:** {mention} [`{id}`]\n**Dicekal Dening:** {banner}\n",
"unban_msg": "__Dicekal dibusak kanthi {mention}__", "unban_msg": "__Dicekal dibusak kanthi {mention}__",
"no_ban_permission": "Mangga kula nyuwun idin nglarang panganggo ing grup punika.", "no_ban_permission": "Mangga kula nyuwun idin nglarang panganggo ing grup punika.",
"no_more_99": "Sampeyan ora bisa nggunakake luwih saka 99", "no_more_99": "Sampeyan ora bisa nggunakake luwih saka 99",

View file

@ -1,5 +1,5 @@
{ {
"no_question": "Tulung gunakake printah <code>/{cmd} [pitakon]</code> kanggo takon nganggo fitur OpenAI.", "no_question": "Tulung gunakake printah <code>/{cmd} [pitakon]</code> kanggo takon nganggo fitur AI.",
"find_answers_str": "Lagi goleki jawaban paling apik kanggo sampeyan..", "find_answers_str": "Lagi goleki jawaban paling apik kanggo sampeyan..",
"dont_spam": "Aja spam, mangga ngenteni {tm} detik utawa aku bakal nglarang sampeyan saka bot iki.", "dont_spam": "Aja spam, mangga ngenteni {tm} detik utawa aku bakal nglarang sampeyan saka bot iki.",
"answers_too_long": "Pitakonan kanggo jawaban sampeyan wis ngluwihi wates teks TG, priksa pranala iki kanggo ndeleng.\n\n{answerlink}" "answers_too_long": "Pitakonan kanggo jawaban sampeyan wis ngluwihi wates teks TG, priksa pranala iki kanggo ndeleng.\n\n{answerlink}"

View file

@ -3,6 +3,7 @@
"up_and_rest": "<b>Dianyari nganggo branch standar, diwiwiti maneh saiki.</b>", "up_and_rest": "<b>Dianyari nganggo branch standar, diwiwiti maneh saiki.</b>",
"cl_btn": "❌ Tutup", "cl_btn": "❌ Tutup",
"no_eval": "__Ora ana pesen eval!__", "no_eval": "__Ora ana pesen eval!__",
"privacy_policy": "<b>Kabijakan Privasi Kanggo {botname}</b>\n\n<b>Tanggal Efektif:</b> 26 Muharram 1446 H\n\nKebijakan Privasi iki nerangake carane kita ngumpulake, nggunakake, lan nglindhungi informasi nalika sampeyan sesambungan karo bot Telegram sing dirancang kanggo manajemen grup.\n\n<b>1. Informasi sing Diklumpukake</b>\n\nYen sampeyan nggunakake bot Telegram, kita bisa ngumpulake jinis informasi ing ngisor iki:\n- <b>Informasi Pangguna</b>: Informasi dhasar kayata ID Telegram, jeneng pangguna, lan data liyane sing sampeyan pilih kanggo diwenehake.\n- <b>Informasi Grup</b>: Data sing ana hubungane karo grup sing sampeyan atur, kalebu ID grup, jeneng grup, lan rincian anggota.\n\n<b>2. Carane Kita Gunakake Informasi Panjenengan</b>\n\nKita nggunakake informasi sing diklumpukake kanggo:\n- Ngatur lan njaga fungsi grup.\n- Nyedhiyani dhukungan lan ningkatake layanan kita.\n- Nyedhiyakake nganyari penting utawa owah-owahan sing ana gandhengane karo bot.\n\n<b>3. Carane Kita Nglindhungi Informasi Panjenengan</b>\n\nKita nindakake macem-macem langkah keamanan kanggo nglindhungi informasi saka akses ora sah, owah-owahan, pambocoran, utawa karusakan. Nanging, elinga yen ora ana cara transmisi liwat Internet utawa panyimpenan elektronik sing 100% aman.\n\n<b>4. Nuduhake Informasi Panjenengan</b>\n\nKita ora nuduhake informasi sampeyan karo pihak katelu, kajaba sing dibutuhake dening hukum utawa minangka perlu kanggo nyedhiyani layanan diterangake.\n\n<b>5. Hak Panjenengan</b>\n\nSampeyan duwe hak kanggo ngakses, mbenerake, utawa mbusak informasi pribadhi. Yen sampeyan pengin nggunakake hak kasebut, hubungi kita langsung liwat bot.\n\n<b>6. Owah-owahan ing Kebijakan Privasi Iki</b>\n\nKita bisa nganyari Kebijakan Privasi iki saka wektu kanggo wektu. Kita bakal menehi kabar babagan owah-owahan sing signifikan kanthi nganyari kabijakan ing bot. Sampeyan tanggung jawab kanggo mriksa kabijakan iki sacara periodik.\n\n<b>7. Hubungi Kita</b>\n\nYen sampeyan duwe pitakonan utawa uneg-uneg babagan Kebijakan Privasi iki utawa praktik kita, hubungi pemilikku.",
"run_eval": "<i>Ngolah pyrogram eval..</i>", "run_eval": "<i>Ngolah pyrogram eval..</i>",
"run_exec": "<i>Ngolah pyrogram exec..</i>", "run_exec": "<i>Ngolah pyrogram exec..</i>",
"no_cmd": "Ora ana prentah kanggo nglakokaké.", "no_cmd": "Ora ana prentah kanggo nglakokaké.",

View file

@ -1,5 +1,6 @@
{ {
"back_btn": "« Balik Maneh", "back_btn": "« Balik Maneh",
"unknown_id": "Nganpunten, aku ora ngerti pengguna iki. Mungkin aku durung pernah ketemu mbi dek'e.",
"no_results": "Ora ana asil sing ditemokake.", "no_results": "Ora ana asil sing ditemokake.",
"exp_task": "😶‍🌫️ Wektu wis entek. Tugas wis dibatalake!" "exp_task": "😶‍🌫️ Wektu wis entek. Tugas wis dibatalake!"
} }

View file

@ -9,5 +9,6 @@
"err_parse": "Gagal ngurai URL, mriksa log..", "err_parse": "Gagal ngurai URL, mriksa log..",
"ngenteni": "Tulung ngenteni..", "ngenteni": "Tulung ngenteni..",
"unauth": "Ora Tugasmu..", "unauth": "Ora Tugasmu..",
"endlist": "Iku pungkasan dhaptar" "endlist": "Iku pungkasan dhaptar",
"vip-btn": "Amarga sawetara pangguna penyalahgunaan, video paling apik saiki mung sing nduwe bot iki."
} }

68
locales/ru-RU/admin.json Normal file
View file

@ -0,0 +1,68 @@
{
"no_admin_error": "Вы должны быть администратором, чтобы воспользоваться этой командой.",
"no_permission_error": "К сожалению у Вас нет необходимых прав для выполнения команды. Отсутствующие права: {permissions}",
"private_not_allowed": "Данную команду невозможно использовать в приватном чате. Если Вам нужна помощь, пожалуйста, воспользуйтесь командой /help.",
"purge_no_reply": "Ответьте На Сообщение, Чтобы Очистить Его.",
"delete_no_reply": "Ответьте На Сообщение, Чтобы Удалить Его.",
"pin_no_reply": "Ответьте На Сообщение, Чтобы Закрепить/Открепить Его.",
"report_no_reply": "Ответьте На Сообщение, Для Жалобы на Пользователя.",
"no_delete_perm": "Пожалуйста, предоставьте мне доступ на удаление сообщений.",
"purge_success": "Успешно удалено {del_total} сообщений..",
"user_not_found": "Я не могу найти такого пользователя.",
"invalid_id_uname": "⚠️ Неправильный userid/username",
"kick_self_err": "Я не могу выгнать себя, я могу уйти если вы хотите.",
"ban_self_err": "Я не могу заблокировать себя, я могу уйти если вы хотите.",
"report_self_err": "Зачем вы жалуетесь на себя?",
"demote_self_err": "Я не могу понизить себя.",
"warn_self_err": "Я не могу дать предупреждение себе.",
"mute_self_err": "Я не могу дать мут себе.",
"kick_sudo_err": "Ого, ты хочешь выгнать моего хозяина?",
"ban_sudo_err": "Ого, ты хочешь заблокировать моего хозяина?",
"demote_sudo_err": "Ого, ты хочешь понизить моего хозяина?",
"warn_sudo_err": "Ого, ты хочешь дать предупреждение моему хозяину?",
"mute_sudo_err": "Ого, ты хочешь дать мут моему хозяину?",
"kick_admin_err": "Лол, это безумие если я выгоню администратора.",
"ban_admin_err": "Лол, это безумие если я заблокирую администратора.",
"mute_admin_err": "Лол, это безумие если я дам мут администратору.",
"warn_admin_err": "Лол, это безумие если я дам предупреждение администратору.",
"kick_msg": "**Выгнан Пользователь:** {mention} [`{id}`]\n**Выгнал:** {kicker}\n**Причина:** {reasonmsg}",
"ban_msg": "**Заблокирован Пользователь:** {mention} [`{id}`]\n**Выгнал:** {banner}\n",
"unban_msg": "__Блокировка убрана с {mention}__",
"no_ban_permission": "Пожалуйста, предоставьте мне доступ на блокировку пользователей.",
"no_more_99": "Вы не можете использовать более 99.",
"banned_time": "**Заблокирован на:** {val}\n",
"muted_time": "**Дан мут на:** {val}\n",
"banned_reason": "**Причина:** {reas}",
"unban_channel_err": "Вы не можете разблокировать канал.",
"give_unban_user": "Укажите имя пользователя или ответьте на сообщение, чтобы разблокировать пользователя.",
"unban_success": "Успешно разблокирован {umention}!",
"give_idban_with_msg_link": "Укажите userid/username вместе с ссылкой на сообщением и причиной для внесения в список блокировки.",
"give_idunban_with_msg_link": "Укажите userid/username вместе с ссылкой на сообщением и причиной для внесения в список разблокировки.",
"give_reason_list_ban": "Вы должны указать причину для внесения в список блокировки",
"Invalid_tg_link": "Введена неверная ссылка на сообщение.",
"multiple_ban_progress": "`Блокировка пользователя к нескольким группам. Это может занять некоторое время.`",
"multiple_unban_progress": "`Разблокировка пользователя к нескольким группам. Это может занять некоторое время.`",
"failed_get_uname": "Не удалось получить username пользователей групп.",
"listban_msg": "**Блокировка пользователя по списку:** {mention}\n**ID заблокированного пользователя:** `{uid}`\n**Администратор:** {frus}\n**Затронутые чаты:** `{ct}`\n**Причина:** {reas}",
"listunban_msg": "**Разблокировка пользователя по списку:** {mention}\n**ID разблокированного пользователя:** `{uid}`\n**Администратор:** {frus}\n**Затронутые чаты:** `{ct}`\n**Причина:** {reas}",
"promote_self_err": "Я не могу понизить себя.",
"no_promote_perm": "К сожалению, у меня нет прав для повышения прав пользователей.",
"full_promote": "Полностью повышен {umention}!",
"normal_promote": "Повышен {umention}!",
"pin_success": "**Закреплено [Это]({link}) сообщение.**",
"unpin_success": "**Откреплено [Это]({link}) сообщение.**",
"pin_no_perm": "Пожалуйста, дайте мне права на закрепления сообщения, чтобы воспользоваться командой!.",
"report_msg": "Сообщил {user_mention} администраторам!",
"reported_is_admin": "Вы же знаете, что этот пользователь Администратор?",
"user_no_warn": "У пользователя {mention} нет предупреждений.",
"ch_warn_msg": "У пользователя {mention} есть {warns}/3 предупреждений.",
"warn_msg": "**Предуждение Пользователю:** {mention}\n**Дал предупреждение:** {warner}\n**Причина:** {reas}\n**Предупреждений:** {twarn}/3",
"rmwarn_msg": "Предупрждения сняты с {mention}.",
"unwarn_msg": "Предупрждения сняты {mention}.",
"rmmute_msg": "__Мут снят с {mention}__",
"unmute_msg": "Размучен! {umention}",
"reply_to_rm_warn": "Ответьте на сообщение, чтобы убрать с пользователя предупрждения.",
"exceed_warn_msg": "Лимит предупреждений у {mention} достигнуто, ЗАБЛОКИРОВАН!",
"mute_msg": "**Замучен пользователь:** {mention}\n**Дал мут:** {muter}\n",
"rm_warn_btn": "🚨 Предупреждение удалено 🚨"
}

13
locales/ru-RU/afk.json Normal file
View file

@ -0,0 +1,13 @@
{
"no_channel": "Эта функция недоступна каналам.",
"on_afk_msg_no_r": "**{usr}** [<code>{id}</code>] вернулся в онлайн и отсуствовал в течении {tm}\n\n",
"on_afk_msg_with_r": "**{usr}** [<code>{id}</code>] вернулся в онлайн и отсуствовал в течении {tm}\n\n**Причина:** `{reas}`\n\n",
"is_afk_msg_no_r": "**{usr}** [<code>{id}</code>] в АФК на протяжении {tm}.\n\n",
"is_afk_msg_with_r": "**{usr}** [<code>{id}</code>] В АФК на протяжении {tm}.\n\n**Причина:** {reas}\n\n",
"is_online": "**{usr}** [<code>{id}</code>] вернулся в онлайн",
"now_afk": "{usr} [<code>{id}</code>] ушёл в АФК!.",
"afkdel_help": "**Использование:**\n/{cmd} [ENABLE|DISABLE] чтобы Включить/Выключить автоудаление сообщений.",
"afkdel_disable": "Автоудаление АФК сообщений сообщение Отключено.",
"afkdel_enable": "Автоудаление АФК сообщений сообщение в этом чате Включено.",
"is_afk": "{usr} [<code>{id}</code>] в АФК!"
}

View file

@ -0,0 +1,6 @@
{
"no_question": "Пожалуйста, используйте команду <code>/{cmd} [Вопрос]</code> чтобы задать Ваш вопрос ИИ.",
"find_answers_str": "Секундочку, ожидайте ответа..",
"dont_spam": "Не спамьте, пожалуйста, подождите {tm} секунд или я заблокирую Вам доступ к боту.",
"answers_too_long": "Ответ на Ваш вопрос слишком длинный, откройте ссылку, чтобы посмотреть.\n\n{answerlink}"
}

12
locales/ru-RU/dev.json Normal file
View file

@ -0,0 +1,12 @@
{
"already_up": "Уже обновлен!",
"up_and_rest": "<b>Обновлен с веткой по умолчанию, идёт перезапуск.</b>",
"cl_btn": "❌ Закрыть",
"no_eval": "__Нет eval сообщения!__",
"privacy_policy": "<b>Политика Конфидециальности Для {botname}</b>\n\n<b>Дата Вступления в Силу:</b> 02 Августа 2024\nЭта Политика Конфидециальности объясняет Как мы собираем, используем, и защищаем Вашу информацию, когда Вы взаимодействуете с Нашим Телеграм ботом, предназначенного для управления группами.\n\n<b>1. Информация что мы собираем</b>\n\nКогда Вы используете Нашего Телеграм бота, мы можем собирать следующие типы информации:\n- <b>Информация о Пользователе</b>: Основная информация, такая как: Телеграм Айди, Ваш юзернейм, и любая другая информация, что Вы решите предоставить.\n- <b>Информация о Группе</b>: Информация относящаяся к группам, которыми Вы управляете, включая Айди группы, имя группы, и информацию по участникам.\n\n<b>2. Как Мы Используем Вашу Информацию</b>\n\nМы используем собранную информацию ждя:\n- Управления и поддержки функциональных возможностей группы.\n- Оказание поддержки и улучшения Наших услуг.\n- Сообщение о Важных Обновления или Изменениях, связанных с ботом.\n\n<b>3. Как Мы Защищаем Ваши Данные</b>\n\nМы применяем разоичные меры безопасности для защиты Вашей информации от несакционированного доступа, изменения, раскрытия или уничтожения. Однако, пожалуйста, имейте в виду, что ни один способ передачи через интернет или электронное хранилище не предоставляет 100% безопасность.\n\n<b>4. Обмен Вашей Информацией</b>\n\nМы не производим обмен Вашей информации с третьими лицами, за исключением случаев, предусмотренных законом, или необходимых для предоставления описанных услуг.\n\n<b>5. Ваши Права</b>\n\nУ Вас есть право на доступ, корректирование, или удаления Ваших персональных данных. Если вы хотите воспользоваться любым из этих прав, свяжитесь с нами напрямую через бот.\n\n<b>6. Изменения в Политике конфиденциальности</b>\n\nМы можем время от времени обновлять настоящую Политику конфиденциальности. Мы будем уведомлять вас о любых существенных изменениях путем обновления политики в боте. Вы обязаны периодически просматривать эту политику.\n\n<b>7. Связь с Нами</b>\n\nЕсли у вас есть вопросы или сомнения по поводу данной Политики конфиденциальности или наших действий, пожалуйста, свяжитесь с моим владельцем.",
"run_eval": "<i>Производим eval в pyrogram..</i>",
"run_exec": "<i>Производим exec в pyrogram..</i>",
"no_cmd": "Не было дано ни одной команды для выполнения.",
"success": "Успешно",
"no_reply": "Без ответа"
}

3
locales/ru-RU/fun.json Normal file
View file

@ -0,0 +1,3 @@
{
"result": "🎲 Кубик остановился на номере: {number}"
}

View file

@ -0,0 +1,6 @@
{
"back_btn": "« Вернуться",
"no_results": "Нет Результатов.",
"unknown_id": "К сожалению, я не могу распознать этого Пользователя. Возможно я никогда не виделся с ним.",
"exp_task": "😶‍🌫️ Время истекло. Задача была остановлена!"
}

13
locales/ru-RU/genss.json Normal file
View file

@ -0,0 +1,13 @@
{
"wait_msg": "Дайте мне немного времени, чтобы обработать Ваш запрос!! 😴",
"wait_dl": "<code>Обработка, пожалуйста, подождите..</code>",
"dl_progress": "Пытаюсь скачать, пожалуйста, подождите..",
"up_progress": "Пытаюсь загрузить...",
"success_dl_msg": "Файл был скачан в <code>{path}</code>.",
"fail_open": "😟 Простите! Я не могу открыть этот файл.",
"limit_dl": "К сожалению, Загрузка ограничена до 2 ГБ, чтобы уменьшить количество флуда. Вы можете конвертировать Ваши файлы в ссылки.",
"err_ssgen": "Не удалось сгенерировать скриншот.\n\n{exc}",
"up_msg": "☑️ Успешно сгенерирован скриншот.\n\n{namma} (<code>{id}</code>)\n#️⃣ #ssgen #айди{id}\n\nСкрин сгенерирован с помощью @{bot_uname}",
"no_reply": "Ответьте на видео или документ в Телеграм, или используйте прямую ссылку после команды, чтобы создать скриншот из медиафайла!",
"choose_no_ss": "Выберите количество результатов для скриншота? 🥳.\n\nОбщая продолжительность: `{td}` (`{dur} секунд`)"
}

View file

@ -0,0 +1,13 @@
{
"sudo_join_msg": "Ого, мой крутой владелец добавил меня в группу!",
"log_bot_added": "#Новая Группа\nГруппа = {ttl}(<code>{cid}</code>)\nКол-во Участников = <code>{tot}</code>\nДобавил - {r_j}",
"support_btn": "Поддержка",
"help_btn": " Помощь",
"update_btn": "📢 Обновления",
"chat_not_allowed": "<b>ЧАТ НЕДОСТУПЕН 🐞\n\nМой владелец заблокировал меня от работы здесь! Вы можете связаться с владельцем бота..</b>",
"welcome_thanks": "<b>Спасибочки, что добавили меня в {ttl} ❣️\n\nЕсли у Вас будут какие-нибудь проблемы или предложения, Вы можете написать мне.</b>",
"capt_welc": "Приветик {umention} [<code>{uid}</code>], Добро пожаловать в группу {ttl}.",
"combot_msg": "<b>#CAS Federation Ban</b>\nПользователь {umention} [<code>{uid}</code>] был замечен за спамботом и быль заблокирован. Работает на основе <a href='https://api.cas.chat/check?user_id={u.id}'>Combot AntiSpam.</a>",
"spamwatch_msg": "<b>#SpamWatch Federation Ban</b>\nПользователь {umention} [<code>{uid}</code>] был заблокирован за <code>{reas}</code>.\n" ,
"welcpic_msg": "Добро пожаловать {userr} [{id}]"
}

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,5 @@
{
"language_changed_successfully": "Язык был успешно изменен.",
"language_changer_chat": "Здесь Вы можете изменить язык, используемый ботов в чате.\nЕсли Вашего языка нет в этом списке, и Вы хотите внести свой вклад, Вы можете открыть вопрос в моем репозитории в гитхабе.",
"language_changer_private": "Здесь Вы можете изменить язык, используемый ботов в приватном чате.\n\nЕсли Вы хотите поменять язык, пожалуйста, воспользуйтесь командой <code>/setchatlang</code> on it.\nЕсли Вашего языка нет в этом списке, и Вы хотите внести свой вклад, Вы можете открыть вопрос в моем репозитории в гитхабе."
}

4
locales/ru-RU/main.json Normal file
View file

@ -0,0 +1,4 @@
{
"language_name": "Russian",
"language_flag": "🇷🇺"
}

View file

@ -0,0 +1,15 @@
{
"sub_extr_help": "Пожалуйста, воспользуйтесь командой /{cmd} [Ссылка] чтобы посмотреть субтитры или аудио в видео-файле.",
"conv_sub_help": "Используйте команду /{cmd} ответом на .ass или .vtt файл, чтобы конвертировать файлы .ass или .vtt в srt.",
"progress_str": "⏳ Обработка запроса..",
"convert_str": "⏳ Конвертация...",
"unauth_cb": "⚠️ Отказано в доступе!",
"cancel_btn": "❌ Отмена",
"invalid_cb": "⚠️ НЕ УДАЛЯЙТЕ ВАШЕ СООБЩЕНИЕ!",
"up_str": "Загрузка файлов..",
"press_btn_msg": "Нажмите на кнопку, чтобы конвертировать субтитры/аудио. В данный момент поддерживаются только ссылки.\nОбработано за {timelog}",
"fail_extr_media": "Не удалось извлечь носитель, убедитесь, что ваша ссылка не защищена WAF или может быть недоступна для бота.",
"fail_extr_sub": "Не удалось извлечь суб-файл, возможно, это неподдерживаемый формат.\n\nСсылка: {link}\nОшибка: {e}",
"capt_extr_sub": "<b>Файл:</b> <code>{nf}</code>\n\nИзвлечено с помощью @{bot} за {timelog}",
"capt_conv_sub": "<code>{nf}.srt</code>\n\nКонвертирован с помощью @{bot}"
}

View file

@ -0,0 +1,11 @@
{
"processing_text": "`Обработка. Общее время зависит от размера Ваших файлов...`",
"wait_msg": "`Пожалуйста, подождите немного...`",
"err_link": "Похоже, что отправленная Вами ссылка недействительна, убедитесь, что это прямая ссылка и ее можно скачать.",
"media_invalid": "Пожалуйста, ответьте на действительный медиа-файл.",
"dl_limit_exceeded": "К сожалению, загрузка ограничена до 2GB для уменьшения флуда. Вы можете конвертировать Ваши файлы в ссылку.",
"dl_args_text": "Пытаюсь скачать..",
"mediainfo_help": "Используйте команду /{cmd} [ссылка], или ответьте на сообщение с медиа командой /{cmd}.",
"capt_media": " Ваши результаты медаинфо..\n\n**Запрос от:** {ment}",
"viweb": "💬 Открыть ссылку"
}

View file

@ -0,0 +1,16 @@
{
"nmd_disabled": "Ночной режим выключен.",
"nmd_not_enabled": "Ночной режим не включен в этом чате.",
"invalid_time_format": "Неверный формат времени. Используйте формат HH:MM.",
"invalid_lockdur": "Неверная продолжительность. Используйте правильный формат.\nНапример: 6h (для 6 часов), 10m для 10 минут.",
"schedule_already_on": "Задание уже запущено в этом чате. Для отключения используйте аргумент `-d`.",
"nmd_enable_success": "Успешно запущен Ночной режим в этом чате.\nГруппа будет закрыта на {st} и будет открываться в {lockdur} каждый день.",
"nmd_off_not_admin": "#Ночной_режим_ОШИБКА\nНе удалось выключить Ночной режим aв `{chat_id}`, поскольку {bname} не администратор в чате `{chat_id}`",
"nmd_off_not_present": "#Ночной_режим_ОШИБКА\nНе удалось выключить Ночной режим в `{chat_id}`, поскольку {bname} не присутствует в чате `{chat_id}`. Группа удалена из списка.",
"nmd_off_err": "#Ночной_режим_ОШИБКА\nНе удалось выключить Ночной режим в `{chat_id}`\nОШИБКА: `{e}`",
"nmd_off_success": "#Ночной_режим_ОБРАБОТЧИК\n📆 {dt}\n\n☀ Группа открыта.\nЗакроется в {close_at}",
"nmd_on_not_admin": "#Ночной_режим_ОШИБКА\nНе удалось включить Ночной режим в `{chat_id}`, поскольку {bname} не администратор в чате `{chat_id}`",
"nmd_on_not_present": "#Ночной_режим_ОШИБКА\nНе удалось включить Ночной режим в `{chat_id}`, поскольку {bname} не присутствует в чате `{chat_id}`. Группа удалена из списка.",
"nmd_on_err": "#Ночной_режим_ОШИБКА\nНе удалось включить Ночной режим в `{chat_id}`\nОШИБКА: `{e}`",
"nmd_on_success": "#Ночной_режим_ОБРАБОТЧИК\n📆 {dt}\n\n🌗 Группа закрывается.\nБудет открыта в {open_at}"
}

6
locales/ru-RU/ocr.json Normal file
View file

@ -0,0 +1,6 @@
{
"no_photo": "Ответьте на Изображение командой /{cmd} чтобы прочитать текст.",
"read_ocr": "Читаю изображение..",
"result_ocr": "Распознано:\n<code>{result}</code>",
"ocr_helper": "/ocr [Ответом на Изображение] - Прочесть Текст с Изображения"
}

View file

@ -0,0 +1,13 @@
{
"no_uname": "<code>Нет Юзернейма</code>",
"no_last_name": "<code>Нет Фамилии</code>",
"uname_change_msg": "✨ Изменил Юзернейм: {bef} ➡️ {aft}.\n",
"lastname_change_msg": "✨ Изменил Фамилию: {bef} ➡️ {aft}.\n",
"firstname_change_msg": "✨ Изменил Имя: {bef} ➡️ {aft}.\n",
"set_sangmata_help": "Используйте <code>/{cmd} on</code>, чтобы включить СангМату. Если Вы хотите выключить, используйте параметр off.",
"sangmata_already_on": "СангМата уже включена в Ваших группах.",
"sangmata_enabled": "СангМата включена в Ваших группах.",
"sangmata_already_off": "СангМата уже выключена в Ваших группах.",
"sangmata_disabled": "СангМата выключена в Ваших группах.",
"wrong_param": "Неизвестный параметр, используйте только параметры on/off."
}

View file

@ -0,0 +1,11 @@
{
"newgroup_log": "#НоваяГруппа\nГруппа = {jdl}(<code>{id}</code>)\nКол-во Участников = <code>{c}</code>",
"newuser_log": "#НовыйПользователь\nАйди - <code>{id}</code>\nИмя - {nm}",
"help_name": "Здесь помощь по **{mod}**:\n",
"help_txt": "Привет {kamuh}, Меня зовут {bot}.\nЯ бот, разработанный на pyrogram с некоторыми полезными функциями.\nТы можешь ознакомиться, кликнув по кнопке ниже.\n\nОсновные команды:\n - /start: Запустить бота\n - /help: получить это сообщение\n - /setlang: Изменить язык бота [БЕТА]",
"click_me": "Кликни",
"back_btn": "Назад",
"click_btn": "Кликни на кнопку ниже, чтобы получить помощь по {nm}",
"pm_detail": "Напиши мне в ЛС для получения Подробной Информации.",
"start_msg": "Привет {kamuh}, Напиши мне в ЛС, чтобы получить информацио о моих функциях. Ты можешь изменить язык бота, используя команду /setlang, но она всё ещё на стадии бета."
}

View file

@ -0,0 +1,19 @@
{
"no_anim_stick": "Анимационные стикеры не поддерживаются!",
"not_sticker": "Это не стикер!",
"unkang_msg": "Попытка убрать с пака..",
"unkang_success": "Стикер был убран с Вашего пака",
"unkang_error": "Не удалось убрать стикер из пака.\n\nОШИБКА: {e}",
"unkang_help": "Пожалуйста, ответьте на стикерпак, созданный {c}, чтобы удалить стикер из пака.",
"anon_warn": "Вы анон-админ, создайте стикеры в ЛС со мной.",
"kang_msg": "Пытается украсть Ваш стикер...",
"stick_no_name": "У стикера нет названия.",
"kang_help": "Хотите чтобы я угадал стикер? Пожалуйста, отметьте стикер.",
"exist_pack": "<code>Используем существующий стикерпак...</code>",
"new_packs": "<b>Создаем новый стикерпак...</b>",
"please_start_msg": "Похоже, Вы никогда не общались со мной в личном чате, Вам нужно сделать это первым...",
"click_me": "Кликни",
"pack_full": "Ваш набор стикеров полон, если Ваш набор не находится в v1 Напишите /kang 1\nЕсли он не находится в v2 Напиши /kang 2\nи так далее..",
"viewpack": "👀 Посмотреть Ваш пак",
"kang_success": "<b>Стикеры успешно украдены!</b>\n<b>Эмодзи:</b> {emot}"
}

View file

@ -0,0 +1,20 @@
{
"no_result": "К сожалению, не удалось найти никаких результатов!",
"no_result_w_query": "К сожалению, нет результатов по запросу: {kueri}",
"get_data": "⏳ Пожалуйста, подождите, получаю данные..",
"cl_btn": "❌ Закрыть",
"back_btn": "↩️ Вернуться",
"dl_text": "⬇️ Скачать",
"cat_text": "Категория",
"quality": "Качество",
"ex_data": "👇 Вытащить данные ",
"unauth": "Эта кнопка не для Вас..",
"invalid_cb": "Неверные данные обратного вызова, отправьте команду еще раз..",
"res_scrape": "<b>Собранная информация с</b> <code>{link}</code>:\n\n{kl}",
"header_with_query": "<b>#{web} Результаты Для:</b> <code>{kueri}</code>\n\n",
"header_no_query": "<b>#{web} Последнее:</b>\n🌀 Используйте /{cmd} [заголовок] чтобы начать поиск по заголовку.\n\n",
"invalid_cmd_scrape": "Используйте команду /{cmd} <b>[link]</b> для поиска ссылки на скачивание.",
"err_getweb": "ОШИБКА: Не удалось получить данные с интернета из-за {err}.",
"err_getapi": "ОШИБКА: Не удалось получить данные с АПИ",
"unsupport_dl_btn": "Некоторые результаты не отображаются в кнопке извлечения из-за неподдерживаемой ссылки."
}

6
locales/ru-RU/webss.json Normal file
View file

@ -0,0 +1,6 @@
{
"no_url": "Отправьте ссылку, чтобы сделать скриншот.",
"wait_str": "Делаю скрин...",
"str_credit": "🌞 Скриншот сгенерирован при помощи Puppeteer",
"ss_failed_str": "Не удалось сделать скриншот.\nОшибка: {err}"
}

View file

@ -0,0 +1,14 @@
{
"no_channel": "Эта команда недоступна для группы или анонимного пользователя.",
"no_query": "Пожалуйста, введите запрос..!",
"no_res": "Нет результатов по `{kweri}`",
"dl_btn": "Скачать",
"back": "Вернуться",
"yts_msg": "Опубликовано {pub}\n\n<b> Длительность:</b> {dur}\n<b> Просмотров:</b> {vi}\n<b> Загрузил:</b> <a href='{clink}'>{cname}</a>\n\n",
"invalid_link": "Пожалуйста, введите корректную ссылку для YT-DLP",
"err_parse": "Не удалось обработать ссылку, смотрите логи..",
"wait": "Пожалуйста, подождите..",
"unauth": "Это не Ваша задача..",
"endlist": "Она в конце списка",
"vip-btn": "Поскольку некоторые пользователи злоупотребляют функцией, а мой сервер не может справиться с этим, лучшее видео теперь доступно только для владельцу бота."
}

View file

@ -2,45 +2,57 @@
# * @date 2023-06-21 22:12:27 # * @date 2023-06-21 22:12:27
# * @projectName MissKatyPyro # * @projectName MissKatyPyro
# * Copyright ©YasirPedia All rights reserved # * Copyright ©YasirPedia All rights reserved
import os
import time import time
from asyncio import get_event_loop
from faulthandler import enable as faulthandler_enable
from logging import ERROR, INFO, StreamHandler, basicConfig, getLogger, handlers from logging import ERROR, INFO, StreamHandler, basicConfig, getLogger, handlers
import uvloop, uvicorn
from apscheduler.jobstores.mongodb import MongoDBJobStore from apscheduler.jobstores.mongodb import MongoDBJobStore
from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.asyncio import AsyncIOScheduler
from async_pymongo import AsyncClient from async_pymongo import AsyncClient
from pymongo import MongoClient from pymongo import MongoClient
from pyrogram import Client from pyrogram import Client
from web.webserver import api
from misskaty.core import misskaty_patch
from misskaty.vars import ( from misskaty.vars import (
API_HASH, API_HASH,
API_ID, API_ID,
BOT_TOKEN, BOT_TOKEN,
DATABASE_NAME, DATABASE_NAME,
DATABASE_URI, DATABASE_URI,
PORT,
TZ, TZ,
USER_SESSION, USER_SESSION,
) )
basicConfig( basicConfig(
level=INFO, level=INFO,
format="[%(asctime)s - %(levelname)s] - %(name)s.%(funcName)s - %(message)s", format="[%(levelname)s] - [%(asctime)s - %(name)s - %(message)s] -> [%(module)s:%(lineno)d]",
datefmt="%d-%b-%y %H:%M:%S", datefmt="%d-%b-%y %H:%M:%S",
handlers=[ handlers=[
handlers.RotatingFileHandler("MissKatyLogs.txt", mode="w+", maxBytes=1000000), handlers.RotatingFileHandler(
"MissKatyLogs.txt", mode="w+", maxBytes=5242880, backupCount=1
),
StreamHandler(), StreamHandler(),
], ],
) )
getLogger("pyrogram").setLevel(ERROR) getLogger("pyrogram").setLevel(ERROR)
getLogger("openai").setLevel(ERROR) getLogger("openai").setLevel(ERROR)
getLogger("httpx").setLevel(ERROR) getLogger("httpx").setLevel(ERROR)
getLogger("iytdl").setLevel(ERROR)
MOD_LOAD = [] MOD_LOAD = []
MOD_NOLOAD = ["subscene_dl"] MOD_NOLOAD = ["subscene_dl"]
HELPABLE = {} HELPABLE = {}
cleanmode = {} cleanmode = {}
botStartTime = time.time() botStartTime = time.time()
misskaty_version = "v2.10.2 - Stable" misskaty_version = "v2.16.1"
uvloop.install()
faulthandler_enable()
from misskaty.core import misskaty_patch
# Pyrogram Bot Client # Pyrogram Bot Client
app = Client( app = Client(
@ -48,13 +60,22 @@ app = Client(
api_id=API_ID, api_id=API_ID,
api_hash=API_HASH, api_hash=API_HASH,
bot_token=BOT_TOKEN, bot_token=BOT_TOKEN,
mongodb=dict(connection=AsyncClient(DATABASE_URI), remove_peers=False), mongodb=dict(connection=AsyncClient(DATABASE_URI), remove_peers=True),
sleep_threshold=180,
app_version="MissKatyPyro Stable",
workers=50,
max_concurrent_transmissions=4,
) )
app.db = AsyncClient(DATABASE_URI)
app.log = getLogger("MissKaty")
# Pyrogram UserBot Client # Pyrogram UserBot Client
user = Client( user = Client(
"YasirUBot", "YasirUBot",
session_string=USER_SESSION, session_string=USER_SESSION,
mongodb=dict(connection=AsyncClient(DATABASE_URI), remove_peers=False),
sleep_threshold=180,
app_version="MissKaty Ubot",
) )
jobstores = { jobstores = {
@ -64,15 +85,26 @@ jobstores = {
} }
scheduler = AsyncIOScheduler(jobstores=jobstores, timezone=TZ) scheduler = AsyncIOScheduler(jobstores=jobstores, timezone=TZ)
async def run_wsgi():
config = uvicorn.Config(api, host="0.0.0.0", port=int(PORT))
server = uvicorn.Server(config)
await server.serve()
app.start() app.start()
BOT_ID = app.me.id BOT_ID = app.me.id
BOT_NAME = app.me.first_name BOT_NAME = app.me.first_name
BOT_USERNAME = app.me.username BOT_USERNAME = app.me.username
if USER_SESSION: if USER_SESSION:
user.start() try:
UBOT_ID = user.me.id user.start()
UBOT_NAME = user.me.first_name UBOT_ID = user.me.id
UBOT_USERNAME = user.me.username UBOT_NAME = user.me.first_name
UBOT_USERNAME = user.me.username
except Exception as e:
app.log.error(f"Error while starting UBot: {e}")
UBOT_ID = None
UBOT_NAME = None
UBOT_USERNAME = None
else: else:
UBOT_ID = None UBOT_ID = None
UBOT_NAME = None UBOT_NAME = None

View file

@ -1,9 +1,10 @@
""" """
* @author yasir <yasiramunandar@gmail.com> * @author yasir <yasiramunandar@gmail.com>
* @date 2022-12-01 09:12:27 * @date 2022-12-01 09:12:27
* @projectName MissKatyPyro * @projectName MissKatyPyro
* Copyright @YasirPedia All rights reserved * Copyright @YasirPedia All rights reserved
""" """
import asyncio import asyncio
import importlib import importlib
import os import os
@ -15,14 +16,22 @@ from pyrogram import __version__, idle
from pyrogram.raw.all import layer from pyrogram.raw.all import layer
from database import dbname from database import dbname
from misskaty import BOT_NAME, BOT_USERNAME, HELPABLE, UBOT_NAME, app, scheduler from misskaty import (
BOT_NAME,
BOT_USERNAME,
HELPABLE,
UBOT_NAME,
app,
get_event_loop,
scheduler,
run_wsgi
)
from misskaty.plugins import ALL_MODULES from misskaty.plugins import ALL_MODULES
from misskaty.plugins.web_scraper import web from misskaty.plugins.web_scraper import web
from misskaty.vars import SUDO, USER_SESSION from misskaty.vars import OWNER_ID, USER_SESSION
from utils import auto_clean from utils import auto_clean
LOGGER = getLogger(__name__) LOGGER = getLogger("MissKaty")
loop = asyncio.get_event_loop()
# Run Bot # Run Bot
@ -48,25 +57,24 @@ async def start_bot():
LOGGER.info(bot_modules) LOGGER.info(bot_modules)
LOGGER.info("+===============+===============+===============+===============+") LOGGER.info("+===============+===============+===============+===============+")
LOGGER.info("[INFO]: BOT STARTED AS @%s!", BOT_USERNAME) LOGGER.info("[INFO]: BOT STARTED AS @%s!", BOT_USERNAME)
try: try:
LOGGER.info("[INFO]: SENDING ONLINE STATUS") LOGGER.info("[INFO]: SENDING ONLINE STATUS")
for i in SUDO: if USER_SESSION:
if USER_SESSION: await app.send_message(
await app.send_message( OWNER_ID,
i, f"USERBOT AND BOT STARTED with Pyrogram v{__version__}..\nUserBot: {UBOT_NAME}\nBot: {BOT_NAME}\n\nwith Pyrogram v{__version__} (Layer {layer}) started on @{BOT_USERNAME}.\n\n<code>{bot_modules}</code>",
f"USERBOT AND BOT STARTED with Pyrogram v{__version__}..\nUserBot: {UBOT_NAME}\nBot: {BOT_NAME}\n\nwith Pyrogram v{__version__} (Layer {layer}) started on @{BOT_USERNAME}.\n\n<code>{bot_modules}</code>", )
) else:
else: await app.send_message(
await app.send_message( OWNER_ID,
i, f"BOT STARTED with Pyrogram v{__version__}..\nBot: {BOT_NAME}\n\nwith Pyrogram v{__version__} (Layer {layer}) started on @{BOT_USERNAME}.\n\n<code>{bot_modules}</code>",
f"BOT STARTED with Pyrogram v{__version__} as {BOT_NAME}\n\nwith Pyrogram v{__version__} (Layer {layer}) started on @{BOT_USERNAME}.\n\n<code>{bot_modules}</code>", )
)
except Exception as e: except Exception as e:
LOGGER.error(str(e)) LOGGER.error(str(e))
scheduler.start() scheduler.start()
asyncio.create_task(run_wsgi())
if "web" not in await dbname.list_collection_names(): if "web" not in await dbname.list_collection_names():
webdb = dbname.web webdb = dbname["web"]
for key, value in web.items(): for key, value in web.items():
await webdb.insert_one({key: value}) await webdb.insert_one({key: value})
if os.path.exists("restart.pickle"): if os.path.exists("restart.pickle"):
@ -84,14 +92,15 @@ async def start_bot():
if __name__ == "__main__": if __name__ == "__main__":
try: try:
loop.run_until_complete(start_bot()) get_event_loop().run_until_complete(start_bot())
app.loop.run_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
except Exception: except Exception:
err = traceback.format_exc() err = traceback.format_exc()
LOGGER.info(err) LOGGER.info(err)
finally: finally:
loop.stop() app.loop.stop()
LOGGER.info( LOGGER.info(
"------------------------ Stopped Services ------------------------" "------------------------ Stopped Services ------------------------"
) )

View file

@ -1,5 +1,11 @@
# skipcq from .errors import capture_err
from .errors import * from .misc import asyncify, new_task
from .misc import * from .permissions import adminsOnly, require_admin
from .permissions import *
from .ratelimiter import * __all__ = [
"capture_err",
"asyncify",
"new_task",
"adminsOnly",
"require_admin",
]

View file

@ -6,7 +6,6 @@ from functools import wraps
from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden
from pyrogram.types import CallbackQuery from pyrogram.types import CallbackQuery
from misskaty import app
from misskaty.vars import LOG_CHANNEL from misskaty.vars import LOG_CHANNEL
@ -24,7 +23,7 @@ def capture_err(func):
try: try:
return await func(client, message, *args, **kwargs) return await func(client, message, *args, **kwargs)
except ChatWriteForbidden: except ChatWriteForbidden:
return await app.leave_chat(message.chat.id) return await client.leave_chat(message.chat.id)
except Exception as err: except Exception as err:
exc = traceback.format_exc() exc = traceback.format_exc()
error_feedback = "ERROR | {} | {}\n\n{}\n\n{}\n".format( error_feedback = "ERROR | {} | {}\n\n{}\n\n{}\n".format(
@ -45,7 +44,7 @@ def capture_err(func):
) as log: ) as log:
log.write(error_feedback) log.write(error_feedback)
log.close() log.close()
await app.send_document( await client.send_document(
LOG_CHANNEL, LOG_CHANNEL,
f"crash_{tgl_now.strftime('%d %B %Y')}.txt", f"crash_{tgl_now.strftime('%d %B %Y')}.txt",
caption=f"Crash Report of this Bot\n{cap_day}", caption=f"Crash Report of this Bot\n{cap_day}",

View file

@ -1,28 +1,28 @@
import asyncio import asyncio
import logging import logging
from functools import wraps from functools import wraps
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger("MissKaty")
def asyncify(func): def asyncify(func):
async def inner(*args, **kwargs): async def inner(*args, **kwargs):
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
func_out = await loop.run_in_executor(None, func, *args, **kwargs) func_out = await loop.run_in_executor(None, func, *args, **kwargs)
return func_out return func_out
return inner return inner
def new_task(func): def new_task(func):
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
try: try:
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
return loop.create_task(func(*args, **kwargs)) return loop.create_task(func(*args, **kwargs))
except Exception as e: except Exception as e:
LOGGER.error( LOGGER.error(
f"Failed to create task for {func.__name__} : {e}" f"Failed to create task for {func.__name__} : {e}"
) # skipcq: PYL-E0602 ) # skipcq: PYL-E0602
return wrapper return wrapper

View file

@ -3,13 +3,13 @@ from time import time
from traceback import format_exc as err from traceback import format_exc as err
from typing import Optional, Union from typing import Optional, Union
from cachetools import TTLCache
from pyrogram import Client, enums from pyrogram import Client, enums
from pyrogram.errors import ChannelPrivate, ChatAdminRequired, ChatWriteForbidden from pyrogram.errors import ChannelPrivate, ChatAdminRequired, ChatWriteForbidden
from pyrogram.types import CallbackQuery, Message from pyrogram.types import CallbackQuery, Message
from misskaty import app from misskaty import app
from misskaty.vars import SUDO from misskaty.helper.sqlite_helper import Cache
from misskaty.vars import SUDO, OWNER_ID
from ...helper.localization import ( from ...helper.localization import (
default_language, default_language,
@ -93,7 +93,7 @@ async def check_perms(
return False return False
admins_in_chat = TTLCache(maxsize=1000, ttl=6 * 60 * 60) admins_in_chat = Cache(filename="admin_cache.db", path="cache", in_memory=False)
async def list_admins(chat_id: int): async def list_admins(chat_id: int):
@ -103,26 +103,29 @@ async def list_admins(chat_id: int):
return admins_in_chat[chat_id]["data"] return admins_in_chat[chat_id]["data"]
try: try:
admins_in_chat[chat_id] = { admins_in_chat.add(
"last_updated_at": time(), chat_id,
"data": [ {
member.user.id "last_updated_at": time(),
async for member in app.get_chat_members( "data": [
chat_id, filter=enums.ChatMembersFilter.ADMINISTRATORS member.user.id
) async for member in app.get_chat_members(
], chat_id, filter=enums.ChatMembersFilter.ADMINISTRATORS
} )
],
},
timeout=6 * 60 * 60,
)
return admins_in_chat[chat_id]["data"] return admins_in_chat[chat_id]["data"]
except ChannelPrivate: except ChannelPrivate:
return return
async def authorised(func, subFunc2, client, message, *args, **kwargs): async def authorised(func, subFunc2, client, message, *args, **kwargs):
chatID = message.chat.id
try: try:
await func(client, message, *args, **kwargs) await func(client, message, *args, **kwargs)
except ChatWriteForbidden: except ChatWriteForbidden:
await app.leave_chat(chatID) await message.chat.leave()
except Exception as e: except Exception as e:
try: try:
await message.reply_text(str(e.MESSAGE)) await message.reply_text(str(e.MESSAGE))
@ -135,11 +138,10 @@ async def authorised(func, subFunc2, client, message, *args, **kwargs):
async def unauthorised(message: Message, permission, subFunc2): async def unauthorised(message: Message, permission, subFunc2):
text = f"You don't have the required permission to perform this action.\n**Permission:** __{permission}__" text = f"You don't have the required permission to perform this action.\n**Permission:** __{permission}__"
chatID = message.chat.id
try: try:
await message.reply_text(text) await message.reply_text(text)
except ChatWriteForbidden: except ChatWriteForbidden:
await app.leave_chat(chatID) await message.chat.leave()
return subFunc2 return subFunc2
@ -163,7 +165,7 @@ def adminsOnly(permission):
# For admins and sudo users # For admins and sudo users
userID = message.from_user.id userID = message.from_user.id
permissions = await member_permissions(chatID, userID) permissions = await member_permissions(chatID, userID)
if userID not in SUDO and permission not in permissions: if userID not in SUDO or userID != OWNER_ID and permission not in permissions:
return await unauthorised(message, permission, subFunc2) return await unauthorised(message, permission, subFunc2)
return await authorised(func, subFunc2, client, message, *args, **kwargs) return await authorised(func, subFunc2, client, message, *args, **kwargs)

View file

@ -1,48 +0,0 @@
from functools import wraps
from typing import Callable, Union
from cachetools import TTLCache
from pyrogram import Client
from pyrogram.errors import QueryIdInvalid
from pyrogram.types import CallbackQuery, Message
from ..ratelimiter_func import RateLimiter
ratelimit = RateLimiter()
# storing spammy user in cache for 1minute before allowing them to use commands again.
warned_users = TTLCache(maxsize=128, ttl=60)
warning_message = "Spam detected! ignoring your all requests for few minutes."
def ratelimiter(func: Callable) -> Callable:
"""
Restricts user's from spamming commands or pressing buttons multiple times
using leaky bucket algorithm and pyrate_limiter.
"""
@wraps(func)
async def decorator(client: Client, update: Union[Message, CallbackQuery]):
userid = update.from_user.id if update.from_user else update.sender_chat.id
is_limited = await ratelimit.acquire(userid)
if is_limited and userid not in warned_users:
if isinstance(update, Message):
await update.reply_text(warning_message)
warned_users[userid] = 1
return
elif isinstance(update, CallbackQuery):
try:
await update.answer(warning_message, show_alert=True)
except QueryIdInvalid:
warned_users[userid] = 1
return
warned_users[userid] = 1
return
elif is_limited and userid in warned_users:
pass
else:
return await func(client, update)
return decorator

View file

@ -1 +1 @@
from . import bound, listen, methods, decorators from . import bound, decorators, methods

View file

@ -6,7 +6,9 @@ from logging import getLogger
from typing import Union from typing import Union
from pyrogram.errors import ( from pyrogram.errors import (
ChannelPrivate,
ChatAdminRequired, ChatAdminRequired,
ChatSendPlainForbidden,
ChatWriteForbidden, ChatWriteForbidden,
FloodWait, FloodWait,
MessageAuthorRequired, MessageAuthorRequired,
@ -14,16 +16,16 @@ from pyrogram.errors import (
MessageIdInvalid, MessageIdInvalid,
MessageNotModified, MessageNotModified,
MessageTooLong, MessageTooLong,
TopicClosed,
) )
from pyrogram.types import Message from pyrogram.types import Message
LOGGER = getLogger(__name__) LOGGER = getLogger("MissKaty")
Message.input = property(
lambda m: m.text[m.text.find(m.command[0]) + len(m.command[0]) + 1 :] @property
if len(m.command) > 1 def parse_cmd(msg):
else None return msg.text.split(None, 1)[1] if len(msg.command) > 1 else None
)
async def reply_text( async def reply_text(
@ -86,11 +88,14 @@ async def reply_text(
await asleep(del_in) await asleep(del_in)
return bool(await msg.delete_msg()) return bool(await msg.delete_msg())
except FloodWait as e: except FloodWait as e:
LOGGER.warning(f"Got floodwait in {self.chat.id} for {e.value}'s.")
await asleep(e.value) await asleep(e.value)
return await reply_text(self, text, *args, **kwargs) return await reply_text(self, text, *args, **kwargs)
except (ChatWriteForbidden, ChatAdminRequired): except (TopicClosed, ChannelPrivate):
return
except (ChatWriteForbidden, ChatAdminRequired, ChatSendPlainForbidden):
LOGGER.info( LOGGER.info(
f"Leaving from {self.chat.title} [{self.chat.id}] because doesn't have admin permission." f"Leaving from {self.chat.title} [{self.chat.id}] because doesn't have enough permission."
) )
return await self.chat.leave() return await self.chat.leave()
@ -130,10 +135,10 @@ async def edit_text(
await asleep(del_in) await asleep(del_in)
return bool(await msg.delete_msg()) return bool(await msg.delete_msg())
except FloodWait as e: except FloodWait as e:
LOGGER.warning(str(e)) LOGGER.warning(f"Got floodwait in {self.chat.id} for {e.value}'s.")
await asleep(e.value) await asleep(e.value)
return await edit_text(self, text, *args, **kwargs) return await edit_text(self, text, *args, **kwargs)
except MessageNotModified: except (MessageNotModified, ChannelPrivate):
return False return False
except (ChatWriteForbidden, ChatAdminRequired): except (ChatWriteForbidden, ChatAdminRequired):
LOGGER.info( LOGGER.info(
@ -318,3 +323,4 @@ Message.edit_or_send_as_file = edit_or_send_as_file
Message.reply_or_send_as_file = reply_or_send_as_file Message.reply_or_send_as_file = reply_or_send_as_file
Message.reply_as_file = reply_as_file Message.reply_as_file = reply_as_file
Message.delete_msg = delete Message.delete_msg = delete
Message.input = parse_cmd

View file

@ -1,4 +1,5 @@
from .callback import callback from .adminsOnly import adminsOnly
from .command import command from .callback import callback
from .command import command
__all__ = ["callback", "command"]
__all__ = ["callback", "command", "adminsOnly"]

View file

@ -0,0 +1,208 @@
# tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram
# Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri>
# This file is part of tgEasy.
# tgEasy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# tgEasy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with tgEasy. If not, see <http://www.gnu.org/licenses/>.
import contextlib
import typing
import pyrogram
from cachetools import TTLCache
from pyrogram.methods import Decorators
from ..utils import check_rights, handle_error, is_admin
ANON = TTLCache(maxsize=250, ttl=30)
async def anonymous_admin(m: pyrogram.types.Message):
"""
Helper function for Anonymous Admin Verification
"""
keyboard = pyrogram.types.InlineKeyboardMarkup(
[
[
pyrogram.types.InlineKeyboardButton(
text="Verify!",
callback_data=f"anon.{m.id}",
),
]
]
)
return await m.reply_text(
"Click here to prove you are admin with the required rights to perform this action!",
reply_markup=keyboard,
)
async def anonymous_admin_verification(
self, CallbackQuery: pyrogram.types.CallbackQuery
):
if int(
f"{CallbackQuery.message.chat.id}{CallbackQuery.data.split('.')[1]}"
) not in set(ANON.keys()):
try:
await CallbackQuery.message.edit_text("Button has been Expired.")
except pyrogram.errors.RPCError:
with contextlib.suppress(pyrogram.errors.RPCError):
await CallbackQuery.message.delete()
return
cb = ANON.pop(
int(f"{CallbackQuery.message.chat.id}{CallbackQuery.data.split('.')[1]}")
)
try:
member = await CallbackQuery.message.chat.get_member(CallbackQuery.from_user.id)
except pyrogram.errors.exceptions.bad_request_400.UserNotParticipant:
return await CallbackQuery.answer(
"You're not member of this group.", show_alert=True
)
except pyrogram.errors.exceptions.forbidden_403.ChatAdminRequired:
return await CallbackQuery.message.edit_text(
"I must be admin to execute this task, or i will leave from this group.",
)
if member.status not in (
pyrogram.enums.ChatMemberStatus.OWNER,
pyrogram.enums.ChatMemberStatus.ADMINISTRATOR,
):
return await CallbackQuery.answer("You need to be an admin to do this.")
permission = cb[2]
if isinstance(permission, str) and not await check_rights(
CallbackQuery.message.chat.id,
CallbackQuery.from_user.id,
permission,
client=self,
):
return await CallbackQuery.message.edit_text(
f"You are Missing the following Rights to use this Command:\n{permission}",
)
if isinstance(permission, list):
permissions = ""
for perm in permission:
if not await check_rights(
CallbackQuery.message.chat.id,
CallbackQuery.from_user.id,
perm,
client=self,
):
permissions += f"\n{perm}"
if permissions != "":
return await CallbackQuery.message.edit_text(
f"You are Missing the following Rights to use this Command:{permissions}",
)
try:
await CallbackQuery.message.delete()
await cb[1](self, cb[0])
except pyrogram.errors.exceptions.forbidden_403.ChatAdminRequired:
return await CallbackQuery.message.edit_text(
"I must be admin to execute this task, or i will leave from this group.",
)
except BaseException as e:
return await handle_error(e, CallbackQuery)
def adminsOnly(
self,
permission: typing.Union[str, list],
TRUST_ANON_ADMINS: typing.Union[bool, bool] = False,
):
"""
# `tgEasy.tgClient.adminsOnly`
- A decorater for running the function only if the admin have the specified Rights.
- If the admin is Anonymous Admin, it also checks his rights by making a Callback.
- Parameters:
- permission (str):
- Permission which the User must have to use the Functions
- TRUST_ANON_ADMIN (bool) **optional**:
- If the user is an Anonymous Admin, then it bypasses his right check.
# Example
.. code-block:: python
from tgEasy import tgClient
import pyrogram
app = tgClient(pyrogram.Client())
@app.command("start")
@app.adminsOnly("can_change_info")
async def start(client, message):
await message.reply_text(f"Hello Admin {message.from_user.mention}")
"""
def wrapper(func):
async def decorator(client, message):
permissions = ""
if message.chat.type != pyrogram.enums.ChatType.SUPERGROUP:
return await message.reply_text(
"This command can be used in supergroups only.",
)
if message.sender_chat and not TRUST_ANON_ADMINS:
ANON[int(f"{message.chat.id}{message.id}")] = (
message,
func,
permission,
)
return await anonymous_admin(message)
if not await is_admin(
message.chat.id,
message.from_user.id,
client=client,
):
return await message.reply_text(
"Only admins can execute this Command!",
)
if isinstance(permission, str) and not await check_rights(
message.chat.id,
message.from_user.id,
permission,
client=client,
):
return await message.reply_text(
f"You are Missing the following Rights to use this Command:\n{permission}",
)
if isinstance(permission, list):
for perm in permission:
if not await check_rights(
message.chat.id,
message.from_user.id,
perm,
client=client,
):
permissions += f"\n{perm}"
if permissions != "":
return await message.reply_text(
f"You are Missing the following Rights to use this Command:{permissions}",
)
try:
await func(client, message)
except pyrogram.errors.exceptions.forbidden_403.ChatWriteForbidden:
await client.leave_chat(message.chat.id)
except BaseException as exception:
await handle_error(exception, message)
self.add_handler(
pyrogram.handlers.CallbackQueryHandler(
anonymous_admin_verification,
pyrogram.filters.regex("^anon."),
),
)
return decorator
return wrapper
Decorators.adminsOnly = adminsOnly

View file

@ -1,100 +1,102 @@
# tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram # tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram
# Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri> # Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri>
# This file is part of tgEasy. # This file is part of tgEasy.
# tgEasy is free software: you can redistribute it and/or modify # tgEasy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published # it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or # by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# tgEasy is distributed in the hope that it will be useful, # tgEasy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with tgEasy. If not, see <http://www.gnu.org/licenses/>. # along with tgEasy. If not, see <http://www.gnu.org/licenses/>.
import typing import typing
import pyrogram
from pyrogram.methods import Decorators import pyrogram
from ..utils import handle_error from pyrogram.methods import Decorators
def callback( from ..utils import handle_error
self,
data: typing.Union[str, list],
self_admin: typing.Union[bool, bool] = False, def callback(
filter: typing.Union[pyrogram.filters.Filter, pyrogram.filters.Filter] = None, self,
*args, data: typing.Union[str, list],
**kwargs, self_admin: typing.Union[bool, bool] = False,
): filtercb: typing.Union[pyrogram.filters.Filter] = None,
""" *args,
### `Client.callback` **kwargs,
):
- A decorater to Register Callback Quiries in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_callback_query(pyrogram.filters.regex('^data.*'))` """
- Parameters: ### `Client.callback`
- data (str || list):
- The callback query to be handled for a function - A decorater to Register Callback Quiries in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_callback_query(pyrogram.filters.regex('^data.*'))`
- Parameters:
- self_admin (bool) **optional**: - data (str || list):
- If True, the command will only executeed if the Bot is Admin in the Chat, By Default False - The callback query to be handled for a function
- filter (`~pyrogram.filters`) **optional**: - self_admin (bool) **optional**:
- Pyrogram Filters, hope you know about this, for Advaced usage. Use `and` for seaperating filters. - If True, the command will only executeed if the Bot is Admin in the Chat, By Default False
#### Example - filter (`~pyrogram.filters`) **optional**:
.. code-block:: python - Pyrogram Filters, hope you know about this, for Advaced usage. Use `and` for seaperating filters.
import pyrogram
#### Example
app = pyrogram.Client() .. code-block:: python
import pyrogram
@app.command("start")
async def start(client, message): app = pyrogram.Client()
await message.reply_text(
f"Hello {message.from_user.mention}", @app.command("start")
reply_markup=pyrogram.types.InlineKeyboardMarkup([[ async def start(client, message):
pyrogram.types.InlineKeyboardButton( await message.reply_text(
"Click Here", f"Hello {message.from_user.mention}",
"data" reply_markup=pyrogram.types.InlineKeyboardMarkup([[
) pyrogram.types.InlineKeyboardButton(
]]) "Click Here",
) "data"
)
@app.callback("data") ]])
async def data(client, CallbackQuery): )
await CallbackQuery.answer("Hello :)", show_alert=True)
""" @app.callback("data")
if filter: async def data(client, CallbackQuery):
filter = pyrogram.filters.regex(f"^{data}.*") & args["filter"] await CallbackQuery.answer("Hello :)", show_alert=True)
else: """
filter = pyrogram.filters.regex(f"^{data}.*") if filtercb:
filtercb = pyrogram.filters.regex(f"^{data}.*") & args["filter"]
def wrapper(func): else:
async def decorator(client, CallbackQuery: pyrogram.types.CallbackQuery): filtercb = pyrogram.filters.regex(f"^{data}.*")
if self_admin:
me = await client.get_chat_member( def wrapper(func):
CallbackQuery.message.chat.id, (await client.get_me()).id async def decorator(client, CallbackQuery: pyrogram.types.CallbackQuery):
) if self_admin:
if me.status not in ( me = await client.get_chat_member(
pyrogram.enums.ChatMemberStatus.OWNER, CallbackQuery.message.chat.id, (await client.get_me()).id
pyrogram.enums.ChatMemberStatus.ADMINISTRATOR, )
): if me.status not in (
return await CallbackQuery.message.edit_text( pyrogram.enums.ChatMemberStatus.OWNER,
"I must be admin to execute this Command" pyrogram.enums.ChatMemberStatus.ADMINISTRATOR,
) ):
try: return await CallbackQuery.message.edit_text(
await func(client, CallbackQuery) "I must be admin to execute this Command"
except pyrogram.errors.exceptions.forbidden_403.ChatAdminRequired: )
pass try:
except BaseException as e: await func(client, CallbackQuery)
return await handle_error(e, CallbackQuery) except pyrogram.errors.exceptions.forbidden_403.ChatAdminRequired:
pass
self.add_handler( except BaseException as e:
pyrogram.handlers.CallbackQueryHandler(decorator, filter) return await handle_error(e, CallbackQuery)
)
return decorator self.add_handler(pyrogram.handlers.CallbackQueryHandler(decorator, filtercb))
return decorator
return wrapper
return wrapper
Decorators.on_cb = callback
Decorators.on_cb = callback

View file

@ -1,129 +1,133 @@
import typing import typing
import pyrogram
from ..utils import handle_error import pyrogram
from pyrogram.methods import Decorators from pyrogram.methods import Decorators
from misskaty.vars import COMMAND_HANDLER
from misskaty.core import pyro_cooldown
def command( from misskaty.vars import COMMAND_HANDLER
self,
command: typing.Union[str, list], from ..utils import handle_error
is_disabled: typing.Union[bool, bool] = False,
pm_only: typing.Union[bool, bool] = False,
group_only: typing.Union[bool, bool] = False, def command(
self_admin: typing.Union[bool, bool] = False, self,
self_only: typing.Union[bool, bool] = False, cmd: typing.Union[str, list],
no_channel: typing.Union[bool, bool] = False, is_disabled: typing.Union[bool, bool] = False,
handler: typing.Optional[list] = None, pm_only: typing.Union[bool, bool] = False,
filter: typing.Union[pyrogram.filters.Filter, pyrogram.filters.Filter] = None, group_only: typing.Union[bool, bool] = False,
*args, self_admin: typing.Union[bool, bool] = False,
**kwargs self_only: typing.Union[bool, bool] = False,
): no_channel: typing.Union[bool, bool] = False,
""" handler: typing.Optional[list] = None,
### `tgClient.command` filtercmd: typing.Union[pyrogram.filters.Filter] = None,
- A decorater to Register Commands in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_message(pyrogram.filters.command('command'))` *args,
- Parameters: **kwargs,
- command (str || list): ):
- The command to be handled for a function """
### `tgClient.command`
- group_only (bool) **optional**: - A decorater to Register Commands in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_message(pyrogram.filters.command('command'))`
- If True, the command will only executed in Groups only, By Default False. - Parameters:
- cmd (str || list):
- pm_only (bool) **optional**: - The command to be handled for a function
- If True, the command will only executed in Private Messages only, By Default False.
- group_only (bool) **optional**:
- self_only (bool) **optional**: - If True, the command will only executed in Groups only, By Default False.
- If True, the command will only excute if used by Self only, By Default False.
- pm_only (bool) **optional**:
- handler (list) **optional**: - If True, the command will only executed in Private Messages only, By Default False.
- If set, the command will be handled by the specified Handler, By Default `Config.HANDLERS`.
- self_only (bool) **optional**:
- self_admin (bool) **optional**: - If True, the command will only excute if used by Self only, By Default False.
- If True, the command will only executeed if the Bot is Admin in the Chat, By Default False
- handler (list) **optional**:
- filter (`~pyrogram.filters`) **optional**: - If set, the command will be handled by the specified Handler, By Default `Config.HANDLERS`.
- Pyrogram Filters, hope you know about this, for Advaced usage. Use `and` for seaperating filters.
- self_admin (bool) **optional**:
#### Example - If True, the command will only executeed if the Bot is Admin in the Chat, By Default False
.. code-block:: python
import pyrogram - filtercmd (`~pyrogram.filters`) **optional**:
- Pyrogram Filters, hope you know about this, for Advaced usage. Use `and` for seaperating filters.
app = pyrogram.Client()
#### Example
@app.on_cmd("start", is_disabled=False, group_only=False, pm_only=False, self_admin=False, self_only=False, pyrogram.filters.chat("777000") and pyrogram.filters.text) .. code-block:: python
async def start(client, message): import pyrogram
await message.reply_text(f"Hello {message.from_user.mention}")
""" app = pyrogram.Client()
if handler is None:
handler = COMMAND_HANDLER @app.on_cmd("start", is_disabled=False, group_only=False, pm_only=False, self_admin=False, self_only=False, pyrogram.filters.chat("777000") and pyrogram.filters.text)
if filter: async def start(client, message):
if self_only: await message.reply_text(f"Hello {message.from_user.mention}")
filter = ( """
pyrogram.filters.command(command, prefixes=handler) if handler is None:
& filter handler = COMMAND_HANDLER
& pyrogram.filters.me if filtercmd:
) if self_only:
else: filtercmd = (
filter = ( pyrogram.filters.command(cmd, prefixes=handler)
pyrogram.filters.command(command, prefixes=handler) & filtercmd
& filter & pyrogram.filters.me
& pyrogram.filters.me )
) else:
else: filtercmd = (
if self_only: pyrogram.filters.command(cmd, prefixes=handler)
filter = ( & filtercmd
pyrogram.filters.command(command, prefixes=handler) & pyrogram.filters.me
& pyrogram.filters.me & pyro_cooldown.wait(7)
) )
else: else:
filter = pyrogram.filters.command(command, prefixes=handler) if self_only:
filtercmd = (
def wrapper(func): pyrogram.filters.command(cmd, prefixes=handler) & pyrogram.filters.me
async def decorator(client, message: pyrogram.types.Message): )
if is_disabled: else:
return await message.reply_text("Sorry, this command has been disabled by owner.") filtercmd = pyrogram.filters.command(
if not message.from_user: cmd, prefixes=handler
return await message.reply_text("I'm cannot identify user. Use my command in private chat.") ) & pyro_cooldown.wait(7)
if (
self_admin def wrapper(func):
and message.chat.type != pyrogram.enums.ChatType.SUPERGROUP async def decorator(client, message: pyrogram.types.Message):
): if is_disabled:
return await message.reply_text( return await message.reply_text(
"This command can be used in supergroups only." "Sorry, this command has been disabled by owner."
) )
if self_admin: if not message.from_user and no_channel:
me = await client.get_chat_member( return await message.reply_text(
message.chat.id, (await client.get_me()).id "I'm cannot identify user. Use my command in private chat."
) )
if me.status not in ( if self_admin and message.chat.type != pyrogram.enums.ChatType.SUPERGROUP:
pyrogram.enums.ChatMemberStatus.OWNER, return await message.reply_text(
pyrogram.enums.ChatMemberStatus.ADMINISTRATOR, "This command can be used in supergroups only."
): )
return await message.reply_text( if self_admin:
"I must be admin to execute this Command" me = await client.get_chat_member(
) message.chat.id, (await client.get_me()).id
if ( )
group_only if me.status not in (
and message.chat.type != pyrogram.enums.ChatType.SUPERGROUP pyrogram.enums.ChatMemberStatus.OWNER,
): pyrogram.enums.ChatMemberStatus.ADMINISTRATOR,
return await message.reply_text( ):
"This command can be used in supergroups only." return await message.reply_text(
) "I must be admin to execute this Command"
if pm_only and message.chat.type != pyrogram.enums.ChatType.PRIVATE: )
return await message.reply_text( if group_only and message.chat.type != pyrogram.enums.ChatType.SUPERGROUP:
"This command can be used in PMs only." return await message.reply_text(
) "This command can be used in supergroups only."
try: )
await func(client, message) if pm_only and message.chat.type != pyrogram.enums.ChatType.PRIVATE:
except pyrogram.errors.exceptions.forbidden_403.ChatWriteForbidden: return await message.reply_text("This command can be used in PMs only.")
await client.leave_chat(message.chat.id) try:
except BaseException as exception: await func(client, message)
return await handle_error(exception, message) except pyrogram.errors.exceptions.forbidden_403.ChatWriteForbidden:
await client.leave_chat(message.chat.id)
self.add_handler( except BaseException as exception:
pyrogram.handlers.MessageHandler(callback=decorator, filters=filter) return await handle_error(exception, message)
)
return decorator self.add_handler(
pyrogram.handlers.MessageHandler(callback=decorator, filters=filtercmd)
return wrapper )
return decorator
Decorators.on_cmd = command
return wrapper
Decorators.on_cmd = command

View file

@ -1,2 +0,0 @@
# skipcq
from .listen import Chat, Client, MessageHandler, User

View file

@ -1,386 +0,0 @@
"""
pyromod - A monkeypatcher add-on for Pyrogram
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
This file is part of pyromod.
pyromod is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyromod is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyromod. If not, see <https://www.gnu.org/licenses/>.
"""
import asyncio
import logging
from enum import Enum
from inspect import iscoroutinefunction
from typing import Callable, Optional, Union
import pyrogram
from ..utils import PyromodConfig, patch, patchable
logger = logging.getLogger(__name__)
class ListenerStopped(Exception):
pass
class ListenerTimeout(Exception):
pass
class ListenerTypes(Enum):
MESSAGE = "message"
CALLBACK_QUERY = "callback_query"
@patch(pyrogram.client.Client)
class Client:
@patchable()
def __init__(self, *args, **kwargs):
self.listeners = {listener_type: {} for listener_type in ListenerTypes}
self.old__init__(*args, **kwargs)
@patchable()
async def listen(
self,
identifier: tuple,
filters=None,
listener_type=ListenerTypes.MESSAGE,
timeout=None,
unallowed_click_alert=True,
):
if type(listener_type) != ListenerTypes:
raise TypeError(
"Parameter listener_type should be a"
" value from pyromod.listen.ListenerTypes"
)
future = self.loop.create_future()
future.add_done_callback(
lambda f: self.stop_listening(identifier, listener_type)
)
listener_data = {
"future": future,
"filters": filters,
"unallowed_click_alert": unallowed_click_alert,
}
self.listeners[listener_type].update({identifier: listener_data})
try:
return await asyncio.wait_for(future, timeout)
except asyncio.exceptions.TimeoutError:
if callable(PyromodConfig.timeout_handler):
PyromodConfig.timeout_handler(identifier, listener_data, timeout)
elif PyromodConfig.throw_exceptions:
raise ListenerTimeout(timeout)
@patchable()
async def ask(
self,
text,
identifier: tuple,
filters=None,
listener_type=ListenerTypes.MESSAGE,
timeout=None,
*args,
**kwargs,
):
request = await self.send_message(identifier[0], text, *args, **kwargs)
response = await self.listen(identifier, filters, listener_type, timeout)
if response:
response.request = request
return response
"""
needed for matching when message_id or
user_id is null, and to take precedence
"""
@patchable()
def match_listener(
self,
data: Optional[tuple] = None,
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
identifier_pattern: Optional[tuple] = None,
) -> tuple:
if data:
listeners = self.listeners[listener_type]
# case with 3 args on identifier
# most probably waiting for a specific user
# to click a button in a specific message
if data in listeners:
return listeners[data], data
# cases with 2 args on identifier
# (None, user, message) does not make
# sense since the message_id is not unique
elif (data[0], data[1], None) in listeners:
matched = (data[0], data[1], None)
elif (data[0], None, data[2]) in listeners:
matched = (data[0], None, data[2])
# cases with 1 arg on identifier
# (None, None, message) does not make sense as well
elif (data[0], None, None) in listeners:
matched = (data[0], None, None)
elif (None, data[1], None) in listeners:
matched = (None, data[1], None)
else:
return None, None
return listeners[matched], matched
elif identifier_pattern:
def match_identifier(pattern, identifier):
comparison = (
pattern[0] in (identifier[0], None),
pattern[1] in (identifier[1], None),
pattern[2] in (identifier[2], None),
)
return comparison == (True, True, True)
for identifier, listener in self.listeners[listener_type].items():
if match_identifier(identifier_pattern, identifier):
return listener, identifier
return None, None
@patchable()
def stop_listening(
self,
data: Optional[tuple] = None,
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
identifier_pattern: Optional[tuple] = None,
):
listener, identifier = self.match_listener(
data, listener_type, identifier_pattern
)
if not listener:
return
elif listener["future"].done():
del self.listeners[listener_type][identifier]
return
if callable(PyromodConfig.stopped_handler):
PyromodConfig.stopped_handler(identifier, listener)
elif PyromodConfig.throw_exceptions:
listener["future"].set_exception(ListenerStopped())
del self.listeners[listener_type][identifier]
@patch(pyrogram.handlers.message_handler.MessageHandler)
class MessageHandler:
@patchable()
def __init__(self, callback: Callable, filters=None):
self.registered_handler = callback
self.old__init__(self.resolve_future, filters)
@patchable()
async def check(self, client, message):
if user := getattr(message, "from_user", None):
user = user.id
try:
listener = client.match_listener(
(message.chat.id, user, message.id),
ListenerTypes.MESSAGE,
)[0]
except AttributeError as err:
logger.warning(f"Get : {err}\n\n{message}")
raise err
listener_does_match = handler_does_match = False
if listener:
filters = listener["filters"]
if callable(filters):
if iscoroutinefunction(filters.__call__):
listener_does_match = await filters(client, message)
else:
listener_does_match = await client.loop.run_in_executor(
None, filters, client, message
)
else:
listener_does_match = True
if callable(self.filters):
if iscoroutinefunction(self.filters.__call__):
handler_does_match = await self.filters(client, message)
else:
handler_does_match = await client.loop.run_in_executor(
None, self.filters, client, message
)
else:
handler_does_match = True
# let handler get the chance to handle if listener
# exists but its filters doesn't match
return listener_does_match or handler_does_match
@patchable()
async def resolve_future(self, client, message, *args):
listener_type = ListenerTypes.MESSAGE
if user := getattr(message, "from_user", None):
user = user.id
listener, identifier = client.match_listener(
(message.chat.id, user, message.id),
listener_type,
)
listener_does_match = False
if listener:
filters = listener["filters"]
if callable(filters):
if iscoroutinefunction(filters.__call__):
listener_does_match = await filters(client, message)
else:
listener_does_match = await client.loop.run_in_executor(
None, filters, client, message
)
else:
listener_does_match = True
if listener_does_match:
if not listener["future"].done():
listener["future"].set_result(message)
del client.listeners[listener_type][identifier]
raise pyrogram.StopPropagation
else:
await self.registered_handler(client, message, *args)
@patch(pyrogram.handlers.callback_query_handler.CallbackQueryHandler)
class CallbackQueryHandler:
@patchable()
def __init__(self, callback: Callable, filters=None):
self.registered_handler = callback
self.old__init__(self.resolve_future, filters)
@patchable()
async def check(self, client, query):
chatID, mID = None, None
if message := getattr(query, "message", None):
chatID, mID = message.chat.id, message.id
try:
listener = client.match_listener(
(chatID, query.from_user.id, mID),
ListenerTypes.CALLBACK_QUERY,
)[0]
except AttributeError as err:
logger.warning(f"Get : {err}\n\n{message}")
raise err
# managing unallowed user clicks
if PyromodConfig.unallowed_click_alert:
permissive_listener = client.match_listener(
identifier_pattern=(
chatID,
None,
mID,
),
listener_type=ListenerTypes.CALLBACK_QUERY,
)[0]
if (permissive_listener and not listener) and permissive_listener[
"unallowed_click_alert"
]:
alert = (
permissive_listener["unallowed_click_alert"]
if type(permissive_listener["unallowed_click_alert"]) is str
else PyromodConfig.unallowed_click_alert_text
)
await query.answer(alert)
return False
filters = listener["filters"] if listener else self.filters
if callable(filters):
if iscoroutinefunction(filters.__call__):
return await filters(client, query)
else:
return await client.loop.run_in_executor(None, filters, client, query)
else:
return True
@patchable()
async def resolve_future(self, client, query, *args):
listener_type = ListenerTypes.CALLBACK_QUERY
chatID, mID = None, None
if message := getattr(query, "message", None):
chatID, mID = message.chat.id, message.id
listener, identifier = client.match_listener(
(chatID, query.from_user.id, mID),
listener_type,
)
if listener and not listener["future"].done():
listener["future"].set_result(query)
del client.listeners[listener_type][identifier]
else:
await self.registered_handler(client, query, *args)
@patch(pyrogram.types.messages_and_media.message.Message)
class Message(pyrogram.types.messages_and_media.message.Message):
@patchable()
async def wait_for_click(
self,
from_user_id: Optional[int] = None,
timeout: Optional[int] = None,
filters=None,
alert: Union[str, bool] = True,
):
return await self._client.listen(
(self.chat.id, from_user_id, self.id),
listener_type=ListenerTypes.CALLBACK_QUERY,
timeout=timeout,
filters=filters,
unallowed_click_alert=alert,
)
@patch(pyrogram.types.user_and_chats.chat.Chat)
class Chat(pyrogram.types.Chat):
@patchable()
def listen(self, *args, **kwargs):
return self._client.listen((self.id, None, None), *args, **kwargs)
@patchable()
def ask(self, text, *args, **kwargs):
return self._client.ask(text, (self.id, None, None), *args, **kwargs)
@patchable()
def stop_listening(self, *args, **kwargs):
return self._client.stop_listening(
*args, identifier_pattern=(self.id, None, None), **kwargs
)
@patch(pyrogram.types.user_and_chats.user.User)
class User(pyrogram.types.User):
@patchable()
def listen(self, *args, **kwargs):
return self._client.listen((None, self.id, None), *args, **kwargs)
@patchable()
def ask(self, text, *args, **kwargs):
return self._client.ask(text, (self.id, self.id, None), *args, **kwargs)
@patchable()
def stop_listening(self, *args, **kwargs):
return self._client.stop_listening(
*args, identifier_pattern=(None, self.id, None), **kwargs
)

View file

@ -2,6 +2,7 @@ import asyncio
from typing import Union from typing import Union
from pyrogram import Client from pyrogram import Client
from pyrogram.types import Message
async def edit_message_text( async def edit_message_text(
@ -11,7 +12,7 @@ async def edit_message_text(
text: str, text: str,
del_in: int = 0, del_in: int = 0,
*args, *args,
**kwargs **kwargs,
) -> Union["Message", bool]: ) -> Union["Message", bool]:
"""\nExample: """\nExample:
message.edit_text("hello") message.edit_text("hello")

View file

@ -10,7 +10,6 @@ async def send_as_file(
text: str, text: str,
filename: str = "output.txt", filename: str = "output.txt",
caption: str = "", caption: str = "",
log: Union[bool, str] = False,
reply_to_message_id: Optional[int] = None, reply_to_message_id: Optional[int] = None,
) -> "Message": ) -> "Message":
"""\nYou can send large outputs as file """\nYou can send large outputs as file
@ -29,10 +28,6 @@ async def send_as_file(
file_name for output file. file_name for output file.
caption (``str``, *optional*): caption (``str``, *optional*):
caption for output file. caption for output file.
log (``bool`` | ``str``, *optional*):
If ``True``, the message will be forwarded
to the log channel.
If ``str``, the logger name will be updated.
reply_to_message_id (``int``, *optional*): reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message. If the message is a reply, ID of the original message.
Returns: Returns:

View file

@ -1,154 +1,158 @@
# tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram # tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram
# Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri> # Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri>
# This file is part of tgEasy. # This file is part of tgEasy.
# tgEasy is free software: you can redistribute it and/or modify # tgEasy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published # it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or # by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# tgEasy is distributed in the hope that it will be useful, # tgEasy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with tgEasy. If not, see <http://www.gnu.org/licenses/>. # along with tgEasy. If not, see <http://www.gnu.org/licenses/>.
import typing import typing
import pyrogram
import pyrogram
async def check_rights(
chat_id: typing.Union[int, int], async def check_rights(
user_id: typing.Union[int, int], chat_id: typing.Union[int, int],
rights: typing.Union[str, list], user_id: typing.Union[int, int],
client, rights: typing.Union[str, list],
) -> bool: client,
""" ) -> bool:
### `check_rights` """
- Checks the Rights of an User ### `check_rights`
- This is an Helper Function for `adminsOnly` - Checks the Rights of an User
- This is an Helper Function for `adminsOnly`
- Parameters:
- chat_id (int): - Parameters:
- The Chat ID of Which Chat have to check the Rights. - chat_id (int):
- The Chat ID of Which Chat have to check the Rights.
- user_id (int):
- The User ID of Whose Rights have to Check. - user_id (int):
- The User ID of Whose Rights have to Check.
- rights (str):
- The Rights have to Check. - rights (str):
- The Rights have to Check.
- client (`pyrogram.Client`):
- From which Client to Check the Rights. - client (`pyrogram.Client`):
- From which Client to Check the Rights.
- Returns:
- `True` if the User have the Right. - Returns:
- `False` if the User don't have the Right. - `True` if the User have the Right.
- `False` if the User don't have the Right.
#### Example
.. code-block:: python #### Example
import pyrogram .. code-block:: python
import pyrogram
app = pyrogram.Client()
app = pyrogram.Client()
@app.command("ban", group_only=True, self_admin=True)
async def ban(client, message): @app.command("ban", group_only=True, self_admin=True)
if not await check_rights(message.chat.id, message.from_user.id, "can_restrict_members"): async def ban(client, message):
return await message.reply_text("You don't have necessary rights to use this Command.") if not await check_rights(message.chat.id, message.from_user.id, "can_restrict_members"):
user = await get_user(message) return await message.reply_text("You don't have necessary rights to use this Command.")
await message.chat.kick_member(user.id) user = await get_user(message)
""" await message.chat.kick_member(user.id)
try: """
user = await client.get_chat_member(chat_id, user_id) try:
except Exception: user = await client.get_chat_member(chat_id, user_id)
return False except Exception:
if user.status == "user": return False
return False if user.status == "user":
if user.status in ( return False
pyrogram.enums.ChatMemberStatus.OWNER, if user.status in (
pyrogram.enums.ChatMemberStatus.ADMINISTRATOR, pyrogram.enums.ChatMemberStatus.OWNER,
): pyrogram.enums.ChatMemberStatus.ADMINISTRATOR,
permission = [] ):
if user.privileges.can_manage_chat: permission = []
permission.append("can_manage_chat") if user.privileges.can_manage_chat:
permission.append("can_manage_chat")
if user.privileges.can_delete_messages:
permission.append("can_delete_messages") if user.privileges.can_delete_messages:
permission.append("can_delete_messages")
if user.privileges.can_manage_video_chats:
permission.append("can_manage_video_chats") if user.privileges.can_manage_video_chats:
permission.append("can_manage_video_chats")
if user.privileges.can_restrict_members:
permission.append("can_restrict_members") if user.privileges.can_restrict_members:
permission.append("can_restrict_members")
if user.privileges.can_promote_members:
permission.append("can_promote_members") if user.privileges.can_promote_members:
permission.append("can_promote_members")
if user.privileges.can_change_info:
permission.append("can_change_info") if user.privileges.can_change_info:
permission.append("can_change_info")
if user.privileges.can_post_messages:
permission.append("can_post_messages") if user.privileges.can_post_messages:
permission.append("can_post_messages")
if user.privileges.can_edit_messages:
permission.append("can_edit_messages") if user.privileges.can_edit_messages:
permission.append("can_edit_messages")
if user.privileges.can_invite_users:
permission.append("can_invite_users") if user.privileges.can_invite_users:
permission.append("can_invite_users")
if user.privileges.can_pin_messages:
permission.append("can_pin_messages") if user.privileges.can_pin_messages:
permission.append("can_pin_messages")
if user.privileges.is_anonymous:
permission.append("is_anonymous") if user.privileges.is_anonymous:
permission.append("is_anonymous")
if isinstance(rights, str):
return rights in permission if isinstance(rights, str):
if isinstance(rights, list): return rights in permission
for right in rights: if isinstance(rights, list):
return right in permission for right in rights:
return False return right in permission
return False
async def is_admin(
chat_id: typing.Union[int, str], user_id: typing.Union[int, str], client async def is_admin(
) -> bool: chat_id: typing.Union[int, str], user_id: typing.Union[int, str], client
""" ) -> bool:
### `is_admin` """
- A Functions to Check if the User is Admin or not ### `is_admin`
- A Functions to Check if the User is Admin or not
- Parameters:
- chat_id (int): - Parameters:
- The Chat ID of Which Chat have to check the Admin Status. - chat_id (int):
- The Chat ID of Which Chat have to check the Admin Status.
- user_id (int):
- The User ID of Whose Admin Status have to Check. - user_id (int):
- The User ID of Whose Admin Status have to Check.
- client (`pyrogram.Client`):
- From which Client to Check the Admin Status. - client (`pyrogram.Client`):
- From which Client to Check the Admin Status.
- Returns:
- `True` if the User is Admin. - Returns:
- `False` if the User is't Admin. - `True` if the User is Admin.
#### Example - `False` if the User is't Admin.
.. code-block:: python #### Example
import pyrogram .. code-block:: python
import pyrogram
app = pyrogram.Client()
app = pyrogram.Client()
@app.command("ban", group_only=True, self_admin=True)
@app.adminsOnly("can_restrict_members") @app.command("ban", group_only=True, self_admin=True)
async def ban(client, message): @app.adminsOnly("can_restrict_members")
if await is_admin(message.chat.id, (await get_user(mesasge)).id): async def ban(client, message):
return await message.reply_text("You can't Ban Admins.") if await is_admin(message.chat.id, (await get_user(mesasge)).id):
await message.chat.kick_member((await get_user(message)).id) return await message.reply_text("You can't Ban Admins.")
await message.reply_text("User has been Banned.") await message.chat.kick_member((await get_user(message)).id)
""" await message.reply_text("User has been Banned.")
try: """
user = await client.get_chat_member(chat_id, user_id) try:
except Exception: user = await client.get_chat_member(chat_id, user_id)
return False except Exception:
return user.status in (pyrogram.enums.ChatMemberStatus.OWNER, pyrogram.enums.ChatMemberStatus.ADMINISTRATOR,) return False
return user.status in (
pyrogram.enums.ChatMemberStatus.OWNER,
pyrogram.enums.ChatMemberStatus.ADMINISTRATOR,
)

View file

@ -1,122 +1,122 @@
# tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram # tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram
# Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri> # Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri>
# This file is part of tgEasy. # This file is part of tgEasy.
# tgEasy is free software: you can redistribute it and/or modify # tgEasy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published # it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or # by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# tgEasy is distributed in the hope that it will be useful, # tgEasy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with tgEasy. If not, see <http://www.gnu.org/licenses/>. # along with tgEasy. If not, see <http://www.gnu.org/licenses/>.
import contextlib import contextlib
import typing import typing
import pyrogram import pyrogram
async def get_user( async def get_user(
m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery] m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery],
) -> pyrogram.types.User or bool: ) -> pyrogram.types.User or bool:
""" """
### `tgEasy.get_user` ### `tgEasy.get_user`
- Gets a User from Message/RepliedMessage/CallbackQuery - Gets a User from Message/RepliedMessage/CallbackQuery
- Parameters: - Parameters:
- m (`~pyrogram.types.Message` || `~pyrogram.types.CallbackQuery`) - m (`~pyrogram.types.Message` || `~pyrogram.types.CallbackQuery`)
- Returns: - Returns:
- `pyrogram.types.User` on Success - `pyrogram.types.User` on Success
- `False` on Error - `False` on Error
#### Example #### Example
.. code-block:: python .. code-block:: python
from tgEasy import get_user, command, adminsOnly from tgEasy import get_user, command, adminsOnly
@command("ban", group_only=True, self_admin=True) @command("ban", group_only=True, self_admin=True)
@adminsOnly("can_restrict_members") @adminsOnly("can_restrict_members")
async def ban(client, message): async def ban(client, message):
user = await get_user(message) user = await get_user(message)
await message.chat.kick_member(user.id) await message.chat.kick_member(user.id)
""" """
if isinstance(m, pyrogram.types.Message): if isinstance(m, pyrogram.types.Message):
message = m message = m
client = m._client client = m._client
if isinstance(m, pyrogram.types.CallbackQuery): if isinstance(m, pyrogram.types.CallbackQuery):
message = m.message message = m.message
client = message._client client = message._client
if message.reply_to_message: if message.reply_to_message:
if message.reply_to_message.sender_chat: if message.reply_to_message.sender_chat:
return False return False
return await client.get_users(message.reply_to_message.from_user.id) return await client.get_users(message.reply_to_message.from_user.id)
command = message.command[1] if len(message.command) > 1 else None command = message.command[1] if len(message.command) > 1 else None
if command and (command.startswith("@") or command.isdigit()): if command and (command.startswith("@") or command.isdigit()):
with contextlib.suppress( with contextlib.suppress(
pyrogram.errors.exceptions.bad_request_400.UsernameNotOccupied, pyrogram.errors.exceptions.bad_request_400.UsernameNotOccupied,
pyrogram.errors.exceptions.bad_request_400.UsernameInvalid, pyrogram.errors.exceptions.bad_request_400.UsernameInvalid,
pyrogram.errors.exceptions.bad_request_400.PeerIdInvalid, pyrogram.errors.exceptions.bad_request_400.PeerIdInvalid,
IndexError, IndexError,
): ):
return await client.get_users(message.command[1]) return await client.get_users(message.command[1])
if message.entities: if message.entities:
for mention in message.entities: for mention in message.entities:
if mention.type == "text_mention": if mention.type == "text_mention":
user = mention.user.id user = mention.user.id
break break
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
return await client.get_users(user) return await client.get_users(user)
return False return False
async def get_user_adv( async def get_user_adv(
m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery] m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery],
) -> pyrogram.types.User or bool: ) -> pyrogram.types.User or bool:
""" """
### `tgEasy.get_user_adv` ### `tgEasy.get_user_adv`
- A Function to Get the User from the Message/CallbackQuery, If there is None arguments, returns the From User. - A Function to Get the User from the Message/CallbackQuery, If there is None arguments, returns the From User.
- Parameters: - Parameters:
- m (`pyrogram.types.Message` || `pyrogram.types.CallbackQuery`): - m (`pyrogram.types.Message` || `pyrogram.types.CallbackQuery`):
- Message or Callbackquery. - Message or Callbackquery.
- Returns: - Returns:
- `pyrogram.types.User` on Success - `pyrogram.types.User` on Success
- `False` on Error - `False` on Error
#### Example #### Example
.. code-block:: python .. code-block:: python
from tgEasy import command, get_user_adv from tgEasy import command, get_user_adv
@command("id") @command("id")
async def id(client, message): async def id(client, message):
user = await get_user_adv(message) user = await get_user_adv(message)
await message.reply_text(f"Your ID is `{user.id}`") await message.reply_text(f"Your ID is `{user.id}`")
""" """
if isinstance(m, pyrogram.types.Message): if isinstance(m, pyrogram.types.Message):
message = m message = m
if isinstance(m, pyrogram.types.CallbackQuery): if isinstance(m, pyrogram.types.CallbackQuery):
message = m.message message = m.message
if message.sender_chat: if message.sender_chat:
return False return False
with contextlib.suppress(IndexError, AttributeError): with contextlib.suppress(IndexError, AttributeError):
if len(message.command) > 1: if len(message.command) > 1:
if message.command[1].startswith("@"): if message.command[1].startswith("@"):
return await get_user(message) return await get_user(message)
if message.command[1].isdigit(): if message.command[1].isdigit():
return await get_user(message) return await get_user(message)
if "text_mention" in message.entities: if "text_mention" in message.entities:
return await get_user(message) return await get_user(message)
if "from_user" in str(message.reply_to_message): if "from_user" in str(message.reply_to_message):
return await get_user(message) return await get_user(message)
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
if "sender_chat" in str(message.reply_to_message): if "sender_chat" in str(message.reply_to_message):
return False return False
if "from_user" in str(message.reply_to_message): if "from_user" in str(message.reply_to_message):
return await message._client.get_users( return await message._client.get_users(
message.reply_to_message.from_user.id message.reply_to_message.from_user.id
) )
return await message._client.get_users(message.from_user.id) return await message._client.get_users(message.from_user.id)

View file

@ -1,86 +1,100 @@
# tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram # tgEasy - Easy for a brighter Shine. A monkey pather add-on for Pyrogram
# Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri> # Copyright (C) 2021 - 2022 Jayant Hegde Kageri <https://github.com/jayantkageri>
# This file is part of tgEasy. # This file is part of tgEasy.
# tgEasy is free software: you can redistribute it and/or modify # tgEasy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published # it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or # by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# tgEasy is distributed in the hope that it will be useful, # tgEasy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with tgEasy. If not, see <http://www.gnu.org/licenses/>. # along with tgEasy. If not, see <http://www.gnu.org/licenses/>.
import contextlib import contextlib
import os import logging
import typing import os
import logging import traceback
import pyrogram import typing
import traceback from datetime import datetime
from datetime import datetime
from misskaty.vars import LOG_CHANNEL import pyrogram
from misskaty.vars import LOG_CHANNEL
async def handle_error(
error, m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery] LOGGER = logging.getLogger("MissKaty")
):
"""
### `handle_error` async def handle_error(
- A Function to Handle the Errors in Functions. _, m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery]
- This Sends the Error Log to the Log Group and Replies Sorry Message for the Users. ):
- This is Helper for all of the functions for handling the Errors. """
### `handle_error`
- Parameters: - A Function to Handle the Errors in Functions.
- error: - This Sends the Error Log to the Log Group and Replies Sorry Message for the Users.
- The Exceptation. - This is Helper for all of the functions for handling the Errors.
- m (`pyrogram.types.Message` or `pyrogram.types.CallbackQuery`): - Parameters:
- The Message or Callback Query where the Error occurred. - error:
- The Exceptation.
#### Exapmle
.. code-block:: python - m (`pyrogram.types.Message` or `pyrogram.types.CallbackQuery`):
import pyrogram - The Message or Callback Query where the Error occurred.
app = tgClient(pyrogram.Client()) """
@app.command("start") day = datetime.now()
async def start(client, message): tgl_now = datetime.now()
try: cap_day = f"{day.strftime('%A')}, {tgl_now.strftime('%d %B %Y %H:%M:%S')}"
await message.reply_text("Hi :D') # I intentionally made an bug for Example :/ f_errname = f"crash_{tgl_now.strftime('%d %B %Y')}.txt"
except Exceptation as e: LOGGER.error(traceback.format_exc())
return await handle_error(e, message) if isinstance(m, pyrogram.types.Message):
""" with open(f_errname, "w+", encoding="utf-8") as log:
log.write(
logging = logging.getLogger(__name__) f"✍️ Message: {m.text or m.caption}\n👱‍♂️ User: {m.from_user.id if m.from_user else m.sender_chat.id}\n\n{traceback.format_exc()}"
logging.exception(traceback.format_exc()) )
log.close()
day = datetime.now() with contextlib.suppress(Exception):
tgl_now = datetime.now() try:
cap_day = f"{day.strftime('%A')}, {tgl_now.strftime('%d %B %Y %H:%M:%S')}" await m.reply_photo(
"https://img.yasirweb.eu.org/file/3c9162b242567ae25d5af.jpg",
with open(f"crash_{tgl_now.strftime('%d %B %Y')}.txt", "w+", encoding="utf-8") as log: caption="An Internal Error Occurred while Processing your Command, the Logs have been sent to the Owners of this Bot. Sorry for Inconvenience",
log.write(traceback.format_exc()) )
log.close() except:
if isinstance(m, pyrogram.types.Message): await m.reply_msg(
with contextlib.suppress(Exception): "An Internal Error Occurred while Processing your Command, the Logs have been sent to the Owners of this Bot. Sorry for Inconvenience"
await m.reply_text( )
"An Internal Error Occurred while Processing your Command, the Logs have been sent to the Owners of this Bot. Sorry for Inconvenience" await m._client.send_document(
) LOG_CHANNEL,
await m._client.send_document( f_errname,
LOG_CHANNEL, f"crash_{tgl_now.strftime('%d %B %Y')}.txt", caption=f"Crash Report of this Bot\n{cap_day}" caption=f"Crash Report of this Bot\n{cap_day}",
) )
if isinstance(m, pyrogram.types.CallbackQuery): if isinstance(m, pyrogram.types.CallbackQuery):
with contextlib.suppress(Exception): with open(f_errname, "w+", encoding="utf-8") as log:
await m.message.delete() log.write(
await m.message.reply_text( f"✍️ Message: {m.message.text or m.message.caption}\n👱‍♂️ User: {m.from_user.id if m.from_user else m.sender_chat.id}\n\n{traceback.format_exc()}"
"An Internal Error Occurred while Processing your Command, the Logs have been sent to the Owners of this Bot. Sorry for Inconvenience" )
) log.close()
await m.message._client.send_document( with contextlib.suppress(Exception):
LOG_CHANNEL, f"crash_{tgl_now.strftime('%d %B %Y')}.txt", caption=f"Crash Report of this Bot\n{cap_day}" await m.message.delete()
) try:
os.remove(f"crash_{tgl_now.strftime('%d %B %Y')}.txt") await m.reply_photo(
return True "https://img.yasirweb.eu.org/file/3c9162b242567ae25d5af.jpg",
caption="An Internal Error Occurred while Processing your Command, the Logs have been sent to the Owners of this Bot. Sorry for Inconvenience",
)
except:
await m.message.reply_msg(
"An Internal Error Occurred while Processing your Command, the Logs have been sent to the Owners of this Bot. Sorry for Inconvenience"
)
await m.message._client.send_document(
LOG_CHANNEL,
f_errname,
caption=f"Crash Report of this Bot\n{cap_day}",
)
if os.path.exists(f_errname):
os.remove(f_errname)
return True

View file

@ -17,6 +17,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with pyromod. If not, see <https://www.gnu.org/licenses/>. along with pyromod. If not, see <https://www.gnu.org/licenses/>.
""" """
from contextlib import asynccontextmanager, contextmanager from contextlib import asynccontextmanager, contextmanager
from inspect import iscoroutinefunction from inspect import iscoroutinefunction
from logging import getLogger from logging import getLogger
@ -24,7 +25,7 @@ from typing import Callable
from pyrogram.sync import async_to_sync from pyrogram.sync import async_to_sync
logger = getLogger(__name__) logger = getLogger("MissKaty")
class PyromodConfig: class PyromodConfig:

View file

@ -1,30 +1,35 @@
import asyncio import asyncio
from pyrogram import filters from pyrogram import filters
from pyrogram.errors import MessageDeleteForbidden
from misskaty.vars import SUDO, OWNER_ID
data = {} data = {}
async def task(msg, warn=False, sec=None): async def task(msg, warn=False, sec=None):
try:
await msg.delete()
except:
pass
if warn: if warn:
user = msg.from_user user = msg.from_user or msg.sender_chat
ids = await msg.reply_msg( ids = await msg.reply_msg(
f"Sorry {user.mention} [<code>{user.id}</code>], you must wait for {sec}s before using command again.." f"Sorry {user.mention if msg.from_user else msg.sender_chat.title} [<code>{user.id}</code>], you must wait for {sec}s before using this feature again.."
) )
try:
await msg.delete_msg()
except MessageDeleteForbidden:
pass
await asyncio.sleep(sec) await asyncio.sleep(sec)
await ids.edit_msg( await ids.edit_msg(
f"Alright {user.mention} [<code>{user.id}</code>], your cooldown is over you can command again.", f"Alright {user.mention if msg.from_user else msg.sender_chat.title} [<code>{user.id}</code>], your cooldown is over you can command again.",
del_in=3, del_in=3,
) )
def wait(sec): def wait(sec):
async def ___(flt, cli, msg): async def ___(flt, _, msg):
user_id = msg.from_user.id user_id = msg.from_user.id if msg.from_user else msg.sender_chat.id
if user_id in SUDO or user_id == OWNER_ID:
return True
if user_id in data: if user_id in data:
if msg.date.timestamp() >= data[user_id]["timestamp"] + flt.data: if msg.date.timestamp() >= data[user_id]["timestamp"] + flt.data:
data[user_id] = {"timestamp": msg.date.timestamp(), "warned": False} data[user_id] = {"timestamp": msg.date.timestamp(), "warned": False}

View file

@ -1,49 +0,0 @@
from typing import Union
from pyrate_limiter import (
BucketFullException,
Duration,
Limiter,
MemoryListBucket,
RequestRate,
)
class RateLimiter:
"""
Implement rate limit logic using leaky bucket
algorithm, via pyrate_limiter.
(https://pypi.org/project/pyrate-limiter/)
"""
def __init__(self) -> None:
# 1 requests per seconds
self.second_rate = RequestRate(1, Duration.SECOND)
# 15 requests per minute.
self.minute_rate = RequestRate(15, Duration.MINUTE)
# 100 requests per hour
self.hourly_rate = RequestRate(100, Duration.HOUR)
# 500 requests per day
self.daily_rate = RequestRate(500, Duration.DAY)
self.limiter = Limiter(
self.minute_rate,
self.hourly_rate,
self.daily_rate,
bucket_class=MemoryListBucket,
)
async def acquire(self, userid: Union[int, str]) -> bool:
"""
Acquire rate limit per userid and return True / False
based on userid ratelimit status.
"""
try:
self.limiter.try_acquire(userid)
return False
except BucketFullException:
return True

View file

@ -4,9 +4,11 @@ from .functions import *
from .http import * from .http import *
from .human_read import * from .human_read import *
from .kuso_utils import * from .kuso_utils import *
from .localization import *
from .media_helper import * from .media_helper import *
from .misc import * from .misc import *
from .pyro_progress import * from .pyro_progress import *
from .sqlite_helper import Cache
from .stickerset import * from .stickerset import *
from .subscene_helper import * from .subscene_helper import *
from .time_gap import * from .time_gap import *

View file

@ -13,8 +13,8 @@ def hhmmss(seconds):
async def take_ss(video_file): async def take_ss(video_file):
out_put_file_name = f"genss{str(time.time())}.png" out_put_file_name = f"genss-{str(time.time())}.png"
cmd = f"vcsi '{video_file}' -t -w 1340 -g 4x4 --timestamp-font assets/DejaVuSans.ttf --metadata-font assets/DejaVuSans-Bold.ttf --template misskaty/helper/ssgen_template.html --quality 100 --end-delay-percent 20 --metadata-font-size 30 --timestamp-font-size 20 -o {out_put_file_name}" cmd = f"""vcsi "{video_file}" -t -w 1340 -g 4x4 --timestamp-font assets/DejaVuSans.ttf --metadata-font assets/DejaVuSans-Bold.ttf --template misskaty/helper/ssgen_template.html --quality 100 --end-delay-percent 20 --metadata-font-size 30 -o {out_put_file_name} --timestamp-font-size 20"""
await shell_exec(cmd) await shell_exec(cmd)
return out_put_file_name if os.path.lexists(out_put_file_name) else None return out_put_file_name if os.path.lexists(out_put_file_name) else None

View file

@ -17,6 +17,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
""" """
import math import math
import os import os

View file

@ -4,6 +4,7 @@ from re import sub as re_sub
from string import ascii_lowercase from string import ascii_lowercase
from pyrogram import enums from pyrogram import enums
from pyrogram.types import Message
from misskaty import app from misskaty import app
@ -16,6 +17,22 @@ def get_urls_from_text(text: str) -> bool:
return [x[0] for x in findall(regex, text)] return [x[0] for x in findall(regex, text)]
def extract_urls(reply_markup):
urls = []
if reply_markup.inline_keyboard:
buttons = reply_markup.inline_keyboard
for i, row in enumerate(buttons):
for j, button in enumerate(row):
if button.url:
name = (
"\n~\nbutton"
if i * len(row) + j == 0
else f"button{i * len(row) + j + 1}"
)
urls.append((f"{name}", button.text, button.url))
return urls
async def alpha_to_int(user_id_alphabet: str) -> int: async def alpha_to_int(user_id_alphabet: str) -> int:
alphabet = list(ascii_lowercase)[:10] alphabet = list(ascii_lowercase)[:10]
user_id = "" user_id = ""
@ -94,7 +111,7 @@ async def extract_user(message):
return (await extract_user_and_reason(message))[0] return (await extract_user_and_reason(message))[0]
async def time_converter(message, time_value: str) -> int: async def time_converter(message: Message, time_value: str) -> datetime:
unit = ["m", "h", "d"] # m == minutes | h == hours | d == days unit = ["m", "h", "d"] # m == minutes | h == hours | d == days
check_unit = "".join(list(filter(time_value[-1].lower().endswith, unit))) check_unit = "".join(list(filter(time_value[-1].lower().endswith, unit)))
currunt_time = datetime.now() currunt_time = datetime.now()
@ -109,7 +126,7 @@ async def time_converter(message, time_value: str) -> int:
temp_time = currunt_time + timedelta(days=int(time_digit)) temp_time = currunt_time + timedelta(days=int(time_digit))
else: else:
return await message.reply_text("Incorrect time specified.") return await message.reply_text("Incorrect time specified.")
return int(datetime.timestamp(temp_time)) return temp_time
def extract_text_and_keyb(ikb, text: str, row_width: int = 2): def extract_text_and_keyb(ikb, text: str, row_width: int = 2):

View file

@ -1,43 +1,41 @@
from asyncio import gather from asyncio import gather
from httpx import AsyncClient, Timeout
import httpx
from aiohttp import ClientSession
# Aiohttp Async Client
session = ClientSession()
# HTTPx Async Client # HTTPx Async Client
http = httpx.AsyncClient( fetch = AsyncClient(
http2=True,
verify=False, verify=False,
timeout=httpx.Timeout(40), headers={
"Accept-Language": "en-US,en;q=0.9,id-ID;q=0.8,id;q=0.7",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edge/107.0.1418.42",
},
timeout=Timeout(20),
) )
async def get(url: str, *args, **kwargs): async def get(url: str, *args, **kwargs):
async with session.get(url, *args, **kwargs) as resp: try:
try: resp = await fetch.get(url, *args, **kwargs)
data = await resp.json() data = await resp.json()
except Exception: except Exception:
data = await resp.text() data = await resp.text()
return data return data
async def head(url: str, *args, **kwargs): async def head(url: str, *args, **kwargs):
async with session.head(url, *args, **kwargs) as resp: try:
try: resp = await fetch.head(url, *args, **kwargs)
data = await resp.json() data = await resp.json()
except Exception: except Exception:
data = await resp.text() data = await resp.text()
return data return data
async def post(url: str, *args, **kwargs): async def post(url: str, *args, **kwargs):
async with session.post(url, *args, **kwargs) as resp: try:
try: resp = await fetch.post(url, *args, **kwargs)
data = await resp.json() data = await resp.json()
except Exception: except Exception:
data = await resp.text() data = await resp.text()
return data return data
@ -54,8 +52,8 @@ async def multipost(url: str, times: int, *args, **kwargs):
async def resp_get(url: str, *args, **kwargs): async def resp_get(url: str, *args, **kwargs):
return await session.get(url, *args, **kwargs) return await fetch.get(url, *args, **kwargs)
async def resp_post(url: str, *args, **kwargs): async def resp_post(url: str, *args, **kwargs):
return await session.post(url, *args, **kwargs) return await fetch.post(url, *args, **kwargs)

View file

@ -5,13 +5,12 @@ def get_readable_file_size(size_in_bytes) -> str:
if size_in_bytes is None: if size_in_bytes is None:
return "0B" return "0B"
index = 0 index = 0
while size_in_bytes >= 1024: while size_in_bytes >= 1024 and index < len(SIZE_UNITS) - 1:
size_in_bytes /= 1024 size_in_bytes /= 1024
index += 1 index += 1
try: return (
return f"{round(size_in_bytes, 2)}{SIZE_UNITS[index]}" f"{size_in_bytes:.2f} {SIZE_UNITS[index]}" if index > 0 else f"{size_in_bytes}B"
except IndexError: )
return "File too large"
def get_readable_time(seconds: int) -> str: def get_readable_time(seconds: int) -> str:

View file

@ -1,33 +1,29 @@
import logging import logging
import traceback import traceback
from html import escape from html import escape
from typing import Optional
import chevron import chevron
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from telegraph.aio import Telegraph from telegraph.aio import Telegraph
from misskaty import BOT_USERNAME from misskaty import BOT_USERNAME
from misskaty.helper.http import http from misskaty.helper.http import fetch
from misskaty.helper.media_helper import post_to_telegraph
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger("MissKaty")
headers = {
"Accept": "*/*",
"User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582",
}
async def kusonimeBypass(url: str, slug=None): async def kusonimeBypass(url: str):
result = {} result = {}
_url = url page = await fetch.get(url)
if slug: if page.status_code != 200:
noslug_url = "https://kusonime.com/{slug}" raise Exception("ERROR: Hostname might be blocked by server!")
_url = noslug_url.format({"slug": slug})
try: try:
page = await http.get(_url, headers=headers)
soup = BeautifulSoup(page.text, "lxml") soup = BeautifulSoup(page.text, "lxml")
thumb = soup.find("div", {"class": "post-thumb"}).find("img").get("src") thumb = soup.find("div", {"class": "post-thumb"}).find("img").get("src")
data = [] data = []
# title = soup.select("#venkonten > div.vezone > div.venser > div.venutama > div.lexot > p:nth-child(3) > strong")[0].text.strip()
try: try:
title = soup.find("h1", {"class": "jdlz"}).text # fix title njing haha title = soup.find("h1", {"class": "jdlz"}).text # fix title njing haha
season = ( season = (
@ -99,20 +95,32 @@ async def kusonimeBypass(url: str, slug=None):
0, 0,
"None", "None",
) )
num = 1
genre = [] genre = []
for _genre in soup.select( for _genre in soup.select(
"#venkonten > div.vezone > div.venser > div.venutama > div.lexot > div.info > p:nth-child(2)" "#venkonten > div.vezone > div.venser > div.venutama > div.lexot > div.info > p:nth-child(2)"
): ):
gen = _genre.text.split(":").pop().strip().split(", ") gen = _genre.text.split(":").pop().strip().split(", ")
genre = gen genre = gen
for num, smokedl in enumerate( for smokedl in soup.find("div", {"class": "dlbodz"}).find_all(
soup.find("div", {"class": "dlbodz"}).find_all( "div", {"class": "smokeddlrh"}
"div", {"class": "smokeddlrh"}
),
start=1,
): ):
if not smokedl:
continue
mendata = {"name": title, "links": []} mendata = {"name": title, "links": []}
for smokeurl in smokedl.find_all("div", {"class": "smokeurl"}):
if not smokeurl:
continue
quality = smokeurl.find("strong").text
links = []
for link in smokeurl.find_all("a"):
url = link.get("href")
client = link.text
links.append({"client": client, "url": url})
mendata["links"].append({"quality": quality, "link_download": links})
for smokeurl in smokedl.find_all("div", {"class": "smokeurlrh"}): for smokeurl in smokedl.find_all("div", {"class": "smokeurlrh"}):
if not smokeurl:
continue
quality = smokeurl.find("strong").text quality = smokeurl.find("strong").text
links = [] links = []
for link in smokeurl.find_all("a"): for link in smokeurl.find_all("a"):
@ -121,45 +129,77 @@ async def kusonimeBypass(url: str, slug=None):
links.append({"client": client, "url": url}) links.append({"client": client, "url": url})
mendata["links"].append({"quality": quality, "link_download": links}) mendata["links"].append({"quality": quality, "link_download": links})
data.append(mendata) data.append(mendata)
result |= { num += 1
"error": False, for smokedl in soup.find("div", {"class": "dlbodz"}).find_all(
"title": title, "div", {"class": "smokeddl"}
"thumb": thumb, ):
"genre": genre, if not smokedl:
"genre_string": ", ".join(genre), continue
"status_anime": status_anime, mendata = {"name": title, "links": []}
"season": season, for smokeurl in smokedl.find_all("div", {"class": "smokeurl"}):
"tipe": tipe, if not smokeurl:
"ep": ep, continue
"score": score, quality = smokeurl.find("strong").text
"duration": duration, links = []
"rilis": rilis, for link in smokeurl.find_all("a"):
"data": data, url = link.get("href")
} client = link.text
except Exception: links.append({"client": client, "url": url})
mendata["links"].append({"quality": quality, "link_download": links})
for smokeurl in smokedl.find_all("div", {"class": "smokeurlrh"}):
if not smokeurl:
continue
quality = smokeurl.find("strong").text
links = []
for link in smokeurl.find_all("a"):
url = link.get("href")
client = link.text
links.append({"client": client, "url": url})
mendata["links"].append({"quality": quality, "link_download": links})
data.append(mendata)
num += 1
result.update(
{
"title": title,
"thumb": thumb,
"genre": genre,
"genre_string": ", ".join(genre),
"status_anime": status_anime,
"season": season,
"tipe": tipe,
"ep": ep,
"score": score,
"duration": duration,
"rilis": rilis,
"data": data,
}
)
except Exception as e:
if result:
result.clear()
err = traceback.format_exc() err = traceback.format_exc()
LOGGER.error(err) LOGGER.error(f"class: {e.__class__.__name_}, {err}")
result |= {"error": True, "error_message": err} raise Exception(f"ERROR: {err}")
await http.delete(_url) finally:
return result return result
async def byPassPh(url: str, name: str): async def byPassPh(url: str, name: str) -> Optional[str]:
kusonime = await kusonimeBypass(url) kusonime = await kusonimeBypass(url)
results = {"error": True, "error_message": kusonime} if not isinstance(kusonime, dict):
if not kusonime["error"]: return kusonime
template = """ template = """
<img src={{{thumb}}}> <img src={{{thumb}}}>
<p><b>Title</b> : <code>{{title}}</code></p> <p><b>Title</b> : <code>{{title}}</code></p>
<p><b>Genre</b> : <code>{{genre_string}}</code></p> <p><b>Genre</b> : <code>{{genre_string}}</code></p>
<br><br><p><b>Season</b> : <code>{{season}}</code></p> <br><p><b>Season</b> : <code>{{season}}</code></p>
<br><br><p><b>Type</b> : <code>{{tipe}}</code></p> <br><p><b>Type</b> : <code>{{tipe}}</code></p>
<br><br><p><b>Status</b> : <code>{{status_anime}}</code></p> <br><p><b>Status</b> : <code>{{status_anime}}</code></p>
<br><br><p><b>Total Episode</b> : <code>{{ep}}</code></p> <br><p><b>Total Episode</b> : <code>{{ep}}</code></p>
<br><br><p><b>Score</b> : <code>{{score}}</code></p> <br><p><b>Score</b> : <code>{{score}}</code></p>
<br><br><p><b>Duration</b> : <code>{{duration}}</code></p> <br><p><b>Duration</b> : <code>{{duration}}</code></p>
<br><br><p><b>Released on</b> : <code>{{rilis}}</code></p> <br><p><b>Released on</b> : <code>{{rilis}}</code></p>
<br><br> <br><br>
{{#data}} {{#data}}
<h4>{{name}}</h4> <h4>{{name}}</h4>
@ -172,20 +212,15 @@ async def byPassPh(url: str, name: str):
<br> <br>
{{/data}} {{/data}}
""".strip() """.strip()
html = chevron.render(template, kusonime) return await post_to_telegraph(
telegraph = Telegraph() False,
if not telegraph.get_access_token(): f"{kusonime.get('title')} By {escape(name)}",
await telegraph.create_account(short_name=BOT_USERNAME) chevron.render(template, kusonime),
page = await telegraph.create_page( )
f"{kusonime.get('title')} By {escape(name)}", html_content=html
)
results |= {"error": False, "url": f'https://telegra.ph/{page["path"]}'}
del results["error_message"]
return results
class Kusonime: class Kusonime:
def __init__(self): def __init__(self): # skipcq
pass pass
@staticmethod @staticmethod

View file

@ -6,7 +6,7 @@ from glob import glob
from typing import Dict, List from typing import Dict, List
from pyrogram.enums import ChatType from pyrogram.enums import ChatType
from pyrogram.types import CallbackQuery, InlineQuery, Message from pyrogram.types import CallbackQuery, ChatMemberUpdated, InlineQuery, Message
from database.locale_db import get_db_lang from database.locale_db import get_db_lang
@ -15,6 +15,7 @@ enabled_locales: List[str] = [
"en-US", # English (United States) "en-US", # English (United States)
"id-ID", # Indonesian "id-ID", # Indonesian
"id-JW", # Javanese "id-JW", # Javanese
"ru-RU", # Russian
] ]
default_language: str = "en-US" default_language: str = "en-US"
@ -54,7 +55,7 @@ def get_locale_string(
async def get_lang(message) -> str: async def get_lang(message) -> str:
if isinstance(message, CallbackQuery): if isinstance(message, CallbackQuery):
chat = message.message.chat chat = message.message.chat
elif isinstance(message, Message): elif isinstance(message, (Message, ChatMemberUpdated)):
chat = message.chat chat = message.chat
elif isinstance(message, InlineQuery): elif isinstance(message, InlineQuery):
chat, chat.type = message.from_user, ChatType.PRIVATE chat, chat.type = message.from_user, ChatType.PRIVATE

View file

@ -13,17 +13,17 @@ async def post_to_telegraph(is_media: bool, title=None, content=None, media=None
if telegraph.get_access_token() is None: if telegraph.get_access_token() is None:
await telegraph.create_account(short_name=BOT_USERNAME) await telegraph.create_account(short_name=BOT_USERNAME)
if is_media: if is_media:
"""Create a Telegram Post Foto/Video""" # Create a Telegram Post Foto/Video
response = await telegraph.upload_file(media) response = await telegraph.upload_file(media)
return f"https://telegra.ph{response[0]['src']}" return f"https://img.yasirweb.eu.org{response[0]['src']}"
"""Create a Telegram Post using HTML Content""" # Create a Telegram Post using HTML Content
response = await telegraph.create_page( response = await telegraph.create_page(
title, title,
html_content=content, html_content=content,
author_url=f"https://t.me/{BOT_USERNAME}", author_url=f"https://t.me/{BOT_USERNAME}",
author_name=BOT_USERNAME, author_name=BOT_USERNAME,
) )
return response["url"] return f"https://te.legra.ph/{response['path']}"
async def run_subprocess(cmd): async def run_subprocess(cmd):
@ -49,7 +49,7 @@ async def get_media_info(file_link):
"-show_chapters", "-show_chapters",
"-show_programs", "-show_programs",
] ]
data, err = await run_subprocess(ffprobe_cmd) data, _ = await run_subprocess(ffprobe_cmd)
return data return data

View file

@ -1,226 +1,78 @@
css = """ import json
<style> import re
@font-face {
font-family: 'JetBrainsMono'; from .http import fetch
src: url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Regular.woff') format('woff'),
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/ttf/JetBrainsMono-Regular.ttf') format('truetype'); def html_builder(title: str, text: str) -> str:
font-weight: 400; """
font-style: normal; Make proper html with css from given content.
} """
.loader-pulse {
position: relative; heading = "<span class='container heading'><b>{content}</b></span>"
bottom: 0.17rem; subheading = "<span class='container subheading'><b>{content}</b></span>"
width: 25px; infobox = "<span class='container infobox'>"
height: 25px; subtitlebox = "<span class='container subtitlebox'>"
float: left; icon = "<img class='icons' src={icon_url} width='35px' height='35px' alt='' >"
border-radius: 50%; html_msg = f"<body>{heading.format(content=title)}"
background: #50fa7b;
animation: load-pulse 1s infinite linear; for line in text.splitlines():
} if ":" not in line and bool(line):
@keyframes load-pulse { if "Text #" in line:
0% { if bool(re.search("Text #1$", line)):
transform: scale(0.05); subtitle_count = len(re.findall("Text #", text))
opacity: 0; html_msg += icon.format(
} icon_url="https://te.legra.ph/file/9d4a676445544d0f2d6db.png"
50% { )
opacity: 1; html_msg += subheading.format(
} content=f"Subtitles ({subtitle_count} subtitle)"
100% { )
transform: scale(0.5); html_msg += "<span style='padding: 10px 0vw;' class='subtitle'>"
opacity: 0;
} elif "General" in line:
} html_msg += icon.format(
::-webkit-scrollbar { icon_url="https://te.legra.ph/file/638fb0416f2600e7c5aa3.png"
width: 3px; )
height: 3px; html_msg += subheading.format(content="General")
}
::-webkit-scrollbar-corner , elif "Video" in line:
::-webkit-scrollbar-track { html_msg += icon.format(
display: none; icon_url="https://te.legra.ph/file/fbc30d71cf71c9a54e59d.png"
} )
::-webkit-scrollbar-thumb { html_msg += subheading.format(content="Video")
border-radius: 8px;
background-color: #50fa7b; elif "Audio" in line:
} html_msg += icon.format(
body { icon_url="https://te.legra.ph/file/a3c431be457fedbae2286.png"
background-color: #282a36; )
font-family: "Josefin Sans", sans-serif; html_msg += subheading.format(content=f"{line.strip()}")
color: #f8f8f2;
overscroll-behaviour: contain; elif "Menu" in line:
} html_msg += "</span>"
code { html_msg += icon.format(
font-family: "Ubuntu mono", "Courier prime"; icon_url="https://te.legra.ph/file/3023b0c2bc202ec9d6d0d.png"
} )
.container { html_msg += subheading.format(content="Chapters")
display: flex;
flex-direction: column; else:
} html_msg += subheading.format(content=f"{line.strip()}")
.container.heading { html_msg += subtitlebox if "Text #" in line else infobox
display: block;
text-align: center; elif ":" in line:
line-height: 20px; if "Attachments" not in line and "ErrorDetectionType" not in line:
flex-direction: column; html_msg += f"<div><code>{line.strip()}</code></div>"
margin: auto;
margin-bottom: 50px; else:
padding: 20px 20px 20px 20px; html_msg += "</span>"
font-size: 1rem;
overflow: scroll; html_msg += "</span>"
background-color: rgba(255, 255, 255, 0.1); return html_msg
border-radius: 7px;
border-style: dashed;
font-family: "Ubuntu mono", "Courier prime"; async def mediainfo_paste(text: str, title: str) -> str:
border: 2px solid rgba(255, 255, 255, 0.1); html_content = html_builder(title, text)
} URL = "https://yasirr.eu.org/mediainfo"
.container.heading::-webkit-scrollbar { response = await fetch.post(URL, data={"content": html_content})
display: none; return (
} f"https://yasirr.eu.org/mediainfo-{json.loads(response.content)['key']}"
.container.subheading { )
margin-left: 15px;
margin-bottom:15px;
padding-top: 10px;
font-size: 1.4rem;
}
.container.infobox {
margin-bottom: 40px;
margin-left: 12px;
margin-right: 12px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
border: 2.5px solid rgba(255, 255, 255, 0.1);
padding: 10px 15px 15px 10px;
white-space: pre ;
overflow: scroll;
font-size: 0.9rem;
}
.subtitle {
display: grid;
grid-auto-flow: column;
grid-auto-columns:320px;
overflow-x: auto;
overscroll-behaviour-inline: contain;
margin-bottom: 40px;
margin-left: 7px;
margin-top: 12px;
}
.subtitle::-webkit-scrollbar {
display: none;
}
.container.subtitlebox {
margin-left: 5px;
margin-right: 15px;
margin-bottom: 0px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
border: 3px solid rgba(255, 255, 255, 0.1);
padding: 15px 15px 15px 10px;
white-space: nowrap;
overflow: scroll;
font-size: 0.9rem;
}
.icons {
float: left;
margin-left: 15px;
margin-right: 5px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
left: 0;
height: 32px;
width: 100%;
font-family: JetBrainsMono;
background-color: #212121;
}
.footer-text{
display: block;
justify-content: space-between;
font-size: 0.9rem;
padding-left: 0.7rem;
padding-right: 1.5rem;
padding-top: 0.25rem;
}
</style>
</head>
"""
import json
import re
from .http import http
def html_builder(title: str, text: str) -> str:
"""
Make proper html with css from given content.
"""
heading = "<span class='container heading'><b>{content}</b></span>"
subheading = "<span class='container subheading'><b>{content}</b></span>"
infobox = "<span class='container infobox'>"
subtitlebox = "<span class='container subtitlebox'>"
icon = "<img class='icons' src={icon_url} width='35px' height='35px' alt='' >"
html_msg = f"<body>{heading.format(content=title)}"
for line in text.splitlines():
if ":" not in line and bool(line):
if "Text #" in line:
if bool(re.search("Text #1$", line)):
subtitle_count = len(re.findall("Text #", text))
html_msg += icon.format(
icon_url="https://te.legra.ph/file/9d4a676445544d0f2d6db.png"
)
html_msg += subheading.format(
content=f"Subtitles ({subtitle_count} subtitle)"
)
html_msg += "<span style='padding: 10px 0vw;' class='subtitle'>"
elif "General" in line:
html_msg += icon.format(
icon_url="https://te.legra.ph/file/638fb0416f2600e7c5aa3.png"
)
html_msg += subheading.format(content="General")
elif "Video" in line:
html_msg += icon.format(
icon_url="https://te.legra.ph/file/fbc30d71cf71c9a54e59d.png"
)
html_msg += subheading.format(content="Video")
elif "Audio" in line:
html_msg += icon.format(
icon_url="https://te.legra.ph/file/a3c431be457fedbae2286.png"
)
html_msg += subheading.format(content=f"{line.strip()}")
elif "Menu" in line:
html_msg += "</span>"
html_msg += icon.format(
icon_url="https://te.legra.ph/file/3023b0c2bc202ec9d6d0d.png"
)
html_msg += subheading.format(content="Chapters")
else:
html_msg += subheading.format(content=f"{line.strip()}")
html_msg += subtitlebox if "Text #" in line else infobox
elif ":" in line:
if "Attachments" not in line and "ErrorDetectionType" not in line:
html_msg += f"<div><code>{line.strip()}</code></div>"
else:
html_msg += "</span>"
html_msg += "</span>"
return css + html_msg
async def mediainfo_paste(text: str, title: str) -> str:
html_content = html_builder(title, text)
URL = "https://mediainfo-1-y5870653.deta.app/api"
response = await http.post(URL, json={"content": html_content})
return (
f"https://mediainfo-1-y5870653.deta.app/{json.loads(response.content)['key']}"
)

View file

@ -75,6 +75,14 @@ def paginate_modules(page_n, module_dict, prefix, chat=None):
), ),
) )
] ]
else:
pairs = pairs[modulo_page * COLUMN_SIZE : COLUMN_SIZE * (modulo_page + 1)] + [
(
EqInlineKeyboardButton(
"Back", callback_data=f"{prefix}_home({modulo_page})"
),
)
]
return pairs return pairs

View file

@ -0,0 +1,560 @@
import os
import pickle
import sqlite3
from contextlib import suppress
from datetime import datetime, timedelta, timezone
from functools import wraps
from pathlib import Path
from threading import local
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple
__all__ = ["Cache"]
class Cache:
"""Simple SQLite Cache."""
PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL
DEFAULT_TIMEOUT = 300
DEFAULT_PRAGMA = {
"mmap_size": 2**26, # https://www.sqlite.org/pragma.html#pragma_mmap_size
"cache_size": 8192, # https://www.sqlite.org/pragma.html#pragma_cache_size
"wal_autocheckpoint": 1000, # https://www.sqlite.org/pragma.html#pragma_wal_autocheckpoint
"auto_vacuum": "none", # https://www.sqlite.org/pragma.html#pragma_auto_vacuum
"synchronous": "off", # https://www.sqlite.org/pragma.html#pragma_synchronous
"journal_mode": "wal", # https://www.sqlite.org/pragma.html#pragma_journal_mode
"temp_store": "file", # https://www.sqlite.org/pragma.html#pragma_temp_store
}
_transaction_sql = "BEGIN EXCLUSIVE TRANSACTION; {} COMMIT TRANSACTION;"
_create_sql = "CREATE TABLE IF NOT EXISTS cache (key TEXT PRIMARY KEY, value BLOB, exp FLOAT);"
_create_index_sql = "CREATE UNIQUE INDEX IF NOT EXISTS cache_key ON cache(key);"
_set_pragma = "PRAGMA {};"
_set_pragma_equal = "PRAGMA {}={};"
_add_sql = (
"INSERT INTO cache (key, value, exp) VALUES (:key, :value, :exp) "
"ON CONFLICT(key) DO UPDATE SET value = :value, exp = :exp "
"WHERE (exp <> -1.0 AND DATETIME(exp, 'unixepoch') <= DATETIME('now'));"
)
_get_sql = "SELECT value, exp FROM cache WHERE key = :key;"
_set_sql = (
"INSERT INTO cache (key, value, exp) VALUES (:key, :value, :exp) "
"ON CONFLICT(key) DO UPDATE SET value = :value, exp = :exp;"
)
_check_sql = (
"SELECT value, exp FROM cache WHERE key = :key "
"AND (exp = -1.0 OR DATETIME(exp, 'unixepoch') > DATETIME('now'));"
)
_update_sql = (
"UPDATE cache SET value = :value WHERE key = :key "
"AND (exp = -1.0 OR DATETIME(exp, 'unixepoch') > DATETIME('now'));"
)
# TODO: add 'RETURNING COUNT(*)!=0' to these when sqlite3 version >=3.35.0
_delete_sql = "DELETE FROM cache WHERE key = :key;"
_touch_sql = (
"UPDATE cache SET exp = :exp WHERE key = :key "
"AND (exp = -1.0 OR DATETIME(exp, 'unixepoch') > DATETIME('now'));"
)
_clear_sql = "DELETE FROM cache;"
_add_many_sql = (
"INSERT INTO cache (key, value, exp) VALUES {}"
"ON CONFLICT(key) DO UPDATE SET value = excluded.value, exp = excluded.exp "
"WHERE (exp <> -1.0 AND DATETIME(exp, 'unixepoch') <= DATETIME('now'));"
)
_get_many_sql = "SELECT key, value, exp FROM cache WHERE key IN ({});"
_set_many_sql = (
"INSERT INTO cache (key, value, exp) VALUES {}"
"ON CONFLICT(key) DO UPDATE SET value = excluded.value, exp = excluded.exp;"
)
_delete_many_sql = "DELETE FROM cache WHERE key IN ({});"
def __init__(
self,
*,
filename: str = ".cache",
path: str = None,
in_memory: bool = True,
timeout: int = 5,
isolation_level: Optional[
Literal["DEFERRED", "IMMEDIATE", "EXCLUSIVE"]
] = "DEFERRED",
**kwargs,
):
"""Create a cache using sqlite3.
:param filename: Cache file name.
:param path: Path string to the wanted db location. If None, use current directory.
:param in_memory: Create database in-memory only. A file is still created, but nothing is stored in it.
:param timeout: Cache connection timeout.
:param isolation_level: Controls the transaction handling performed by sqlite3.
If set to None, transactions are never implicitly opened.
https://www.sqlite.org/lang_transaction.html
:param kwargs: Pragma settings. https://www.sqlite.org/pragma.html
"""
filepath = filename if path is None else str(Path(path) / filename)
suffix = ":?mode=memory&cache=shared" if in_memory else ""
self.connection_string = f"{filepath}{suffix}"
self.pragma = {**kwargs, **self.DEFAULT_PRAGMA}
self.timeout = timeout
self.path = path
self.isolation_level = isolation_level
self.local = local()
self.local.instances = getattr(self.local, "instances", 0) + 1
self._con.execute(self._create_sql)
self._con.execute(self._create_index_sql)
self._con.commit()
@property
def _con(self) -> sqlite3.Connection:
if not os.path.exists(self.path):
os.mkdir(self.path)
try:
return self.local.con
except AttributeError:
self.local.con = sqlite3.connect(
self.connection_string,
timeout=self.timeout,
isolation_level=self.isolation_level,
)
self._apply_pragma()
return self.local.con
def __getitem__(self, item: str) -> Any:
value = self.get(item)
if value is None:
raise KeyError("Key not in cache.")
return value
def __setitem__(self, item: str, value: Any) -> None:
self.set(item, value)
def __delitem__(self, key):
self.delete(key)
def __contains__(self, key):
return self._con.execute(self._check_sql, {"key": key}).fetchone() is not None
def __enter__(self):
self._con # noqa pylint: disable=W0104
return self
def __exit__(self, *args):
self.close()
def __del__(self):
self.local.instances = getattr(self.local, "instances", 0) - 1
if self.local.instances <= 0:
self.close()
def close(self) -> None:
"""Closes the cache."""
self._con.execute(
self._set_pragma.format("optimize")
) # https://www.sqlite.org/pragma.html#pragma_optimize
self._con.close()
with suppress(AttributeError):
delattr(self.local, "con")
def _apply_pragma(self):
for key, value in self.pragma.items():
self._con.execute(self._set_pragma_equal.format(key, value))
@staticmethod
def _exp_timestamp(timeout: int = DEFAULT_TIMEOUT) -> float:
if timeout < 0:
return -1.0
return (datetime.now(tz=timezone.utc) + timedelta(seconds=timeout)).timestamp()
@staticmethod
def _exp_datetime(exp: float) -> Optional[datetime]:
return None if exp == -1.0 else datetime.utcfromtimestamp(exp)
def _stream(self, value: Any) -> bytes:
return pickle.dumps(value, protocol=self.PICKLE_PROTOCOL)
def _unstream(self, value: bytes) -> Any:
return pickle.loads(value) # noqa: S301
def add(self, key: str, value: Any, timeout: int = DEFAULT_TIMEOUT) -> None:
"""Set the value to the cache only if the key is not already in the cache,
or the found value has expired.
:param key: Cache key.
:param value: Picklable object to store.
:param timeout: How long the value is valid in the cache.
Negative numbers will keep the key in cache until manually removed.
"""
data = {
"key": key,
"value": self._stream(value),
"exp": self._exp_timestamp(timeout),
}
self._con.execute(self._add_sql, data)
self._con.commit()
def get(self, key: str, default: Any = None) -> Any:
"""Get the value under some key. Return `default` if key not in the cache or expired.
:param key: Cache key.
:param default: Value to return if key not in the cache.
"""
result: Optional[Tuple[bytes, float]] = self._con.execute(
self._get_sql, {"key": key}
).fetchone()
if result is None:
return default
exp = self._exp_datetime(result[1])
if exp is not None and datetime.utcnow() >= exp:
self._con.execute(self._delete_sql, {"key": key})
self._con.commit()
return default
return self._unstream(result[0])
def set(self, key: str, value: Any, timeout: int = DEFAULT_TIMEOUT) -> None:
"""Set a value in cache under some key.
:param key: Cache key.
:param value: Picklable object to store.
:param timeout: How long the value is valid in the cache.
Negative numbers will keep the key in cache until manually removed.
"""
data = {
"key": key,
"value": self._stream(value),
"exp": self._exp_timestamp(timeout),
}
self._con.execute(self._set_sql, data)
self._con.commit()
def update(self, key: str, value: Any) -> None:
"""Update value in the cache. Does nothing if key not in the cache or expired.
:param key: Cache key.
:param value: Picklable object to store.
"""
data = {"key": key, "value": self._stream(value)}
self._con.execute(self._update_sql, data)
self._con.commit()
def touch(self, key: str, timeout: int = DEFAULT_TIMEOUT) -> None:
"""Extend the lifetime of an object in cache. Does nothing if key is not in the cache or is expired.
:param key: Cache key.
:param timeout: How long the value is valid in the cache.
Negative numbers will keep the key in cache until manually removed.
"""
data = {"exp": self._exp_timestamp(timeout), "key": key}
self._con.execute(self._touch_sql, data)
self._con.commit()
def delete(self, key: str) -> None:
"""Remove the value under the given key from the cache. Does nothing if key is not in the cache.
:param key: Cache key.
"""
self._con.execute(self._delete_sql, {"key": key})
self._con.commit()
def add_many(self, dict_: Dict[str, Any], timeout: int = DEFAULT_TIMEOUT) -> None:
"""For all keys in the given dict, add the value to the cache only if the key is not
already in the cache, or the found value has expired.
:param dict_: Cache keys with values to add.
:param timeout: How long the value is valid in the cache.
Negative numbers will keep the key in cache until manually removed.
"""
command = self._add_many_sql.format(
", ".join([f"(:key{n}, :value{n}, :exp{n})" for n in range(len(dict_))])
)
data = {}
exp = self._exp_timestamp(timeout)
for i, (key, value) in enumerate(dict_.items()):
data[f"key{i}"] = key
data[f"value{i}"] = self._stream(value)
data[f"exp{i}"] = exp
self._con.execute(command, data)
self._con.commit()
def get_many(self, keys: List[str]) -> Dict[str, Any]:
"""Get all values that exist and aren't expired from the given cache keys, and return a dict.
:param keys: List of cache keys.
"""
seq = ", ".join([f"'{value}'" for value in keys])
fetched: List[Tuple[str, Any, float]] = self._con.execute(
self._get_many_sql.format(seq)
).fetchall()
if not fetched:
return {}
results: Dict[str, Any] = {}
to_delete: List[str] = []
for key, value, exp in fetched:
exp = self._exp_datetime(exp)
if exp is not None and datetime.utcnow() >= exp:
to_delete.append(key)
continue
results[key] = self._unstream(value)
if to_delete:
self._con.execute(
self._delete_many_sql.format(
", ".join([f"'{value}'" for value in to_delete])
)
)
self._con.commit()
return results
def set_many(self, dict_: Dict[str, Any], timeout: int = DEFAULT_TIMEOUT) -> None:
"""Set values to the cache for all keys in the given dict.
:param dict_: Cache keys with values to set.
:param timeout: How long the value is valid in the cache.
Negative numbers will keep the key in cache until manually removed.
"""
command = self._set_many_sql.format(
", ".join([f"(:key{n}, :value{n}, :exp{n})" for n in range(len(dict_))])
)
data = {}
exp = self._exp_timestamp(timeout)
for i, (key, value) in enumerate(dict_.items()):
data[f"key{i}"] = key
data[f"value{i}"] = self._stream(value)
data[f"exp{i}"] = exp
self._con.execute(command, data)
self._con.commit()
def update_many(self, dict_: Dict[str, Any]) -> None:
"""Update values to the cache for all keys in the given dict. Does nothing if key not in cache or expired.
:param dict_:Cache keys with values to update to.
"""
seq = [
{"key": key, "value": self._stream(value)} for key, value in dict_.items()
]
self._con.executemany(self._update_sql, seq)
self._con.commit()
def touch_many(self, keys: List[str], timeout: int = DEFAULT_TIMEOUT) -> None:
"""Extend the lifetime for all objects under the given keys in cache.
Does nothing if a key is not in the cache or is expired.
:param keys: List of cache keys.
:param timeout: How long the value is valid in the cache.
Negative numbers will keep the key in cache until manually removed.
"""
exp = self._exp_timestamp(timeout)
seq = [{"key": key, "exp": exp} for key in keys]
self._con.executemany(self._touch_sql, seq)
self._con.commit()
def delete_many(self, keys: List[str]) -> None:
"""Remove all the values under the given keys from the cache.
:param keys: List of cache keys.
"""
self._con.execute(
self._delete_many_sql.format(", ".join([f"'{value}'" for value in keys]))
)
self._con.commit()
def get_or_set(self, key: str, default: Any, timeout: int = DEFAULT_TIMEOUT) -> Any:
"""Get a value under some key, or set the default if key is not in cache.
:param key: Cache key.
:param default: Picklable object to store if key is not in cache.
:param timeout: How long the value is valid in the cache.
Negative numbers will keep the key in cache until manually removed.
"""
result: Optional[Tuple[bytes, float]] = self._con.execute(
self._get_sql, {"key": key}
).fetchone()
if result is not None:
exp = self._exp_datetime(result[1])
if exp is not None and datetime.utcnow() >= exp:
self._con.execute(self._delete_sql, {"key": key})
else:
return self._unstream(result[0])
data = {
"key": key,
"value": self._stream(default),
"exp": self._exp_timestamp(timeout),
}
self._con.execute(self._set_sql, data)
self._con.commit()
return default
def get_all(self) -> Dict[str, Any]:
"""Get all key-value pairs from the cache."""
all_data = self._con.execute("SELECT key, value FROM cache;").fetchall()
return {key: self._unstream(value) for key, value in all_data}
def clear(self) -> None:
"""Clear the cache from all values."""
self._con.execute(self._clear_sql)
self._con.commit()
def incr(self, key: str, delta: int = 1) -> int:
"""Increment the value in cache by the given delta.
Note that this is not an atomic transaction!
:param key: Cache key.
:param delta: How much to increment.
:raises ValueError: Value cannot be incremented.
"""
result: Optional[Tuple[bytes, float]] = self._con.execute(
self._check_sql, {"key": key}
).fetchone()
if result is None:
raise ValueError("Nonexistent or expired cache key.")
value = self._unstream(result[0])
if not isinstance(value, int):
raise ValueError("Value is not a number.")
new_value = value + delta
self._con.execute(
self._update_sql, {"key": key, "value": self._stream(new_value)}
)
self._con.commit()
return new_value
def decr(self, key: str, delta: int = 1) -> int:
"""Decrement the value in cache by the given delta.
Note that this is not an atomic transaction!
:param key: Cache key.
:param delta: How much to decrement.
:raises ValueError: Value cannot be decremented.
"""
result: Optional[Tuple[bytes, float]] = self._con.execute(
self._check_sql, {"key": key}
).fetchone()
if result is None:
raise ValueError("Nonexistent or expired cache key.")
value = self._unstream(result[0])
if not isinstance(value, int):
raise ValueError("Value is not a number.")
new_value = value - delta
self._con.execute(
self._update_sql, {"key": key, "value": self._stream(new_value)}
)
self._con.commit()
return new_value
def memoize(
self, timeout: int = DEFAULT_TIMEOUT
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
"""Save the result of the decorated function in cache. Calls with different
arguments are saved under different keys.
:param timeout: How long the value is valid in the cache.
Negative numbers will keep the key in cache until manually removed.
"""
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
def wrapper(*args, **kwargs) -> Callable[..., Any]:
result = self.get(f"{func}-{args}-{kwargs}", obj)
if result == obj:
result = func(*args, **kwargs)
self.set(f"{func}-{args}-{kwargs}", result, timeout)
return result
return wrapper
obj = object()
return decorator
memorize = memoize # for backwards compatibility
def ttl(self, key: str) -> int:
"""How long the key is still valid in the cache in seconds.
Returns `-1` if the value for the key does not expire.
Returns `-2` if the value for the key has expired, or has not been set.
:param key: Cache key.
"""
result: Optional[Tuple[bytes, float]] = self._con.execute(
self._get_sql, {"key": key}
).fetchone()
if result is None:
return -2
exp = self._exp_datetime(result[1])
if exp is None:
return -1
ttl = int((exp - datetime.utcnow()).total_seconds())
if ttl <= 0:
self._con.execute(self._delete_sql, {"key": key})
self._con.commit()
return -2
return ttl
def ttl_many(self, keys: List[str]) -> Dict[str, int]:
"""How long the given keys are still valid in the cache in seconds.
Returns `-1` if a value for the key does not expire.
Returns `-2` if a value for the key has expired, or has not been set.
:param keys: List of cache keys.
"""
seq = ", ".join([f"'{value}'" for value in keys])
fetched: List[Tuple[str, Any, float]] = self._con.execute(
self._get_many_sql.format(seq)
).fetchall()
exp_by_key: Dict[str, float] = {key: exp for key, _, exp in fetched}
results: Dict[str, int] = {}
to_delete: List[str] = []
for key in keys:
exp_ = exp_by_key.get(key)
if exp_ is None:
results[key] = -2
continue
exp = self._exp_datetime(exp_)
if exp is None:
results[key] = -1
continue
if datetime.utcnow() >= exp:
to_delete.append(key)
results[key] = -2
continue
results[key] = int((exp - datetime.utcnow()).total_seconds())
if to_delete:
self._con.execute(
self._delete_many_sql.format(
", ".join([f"'{value}'" for value in to_delete])
)
)
self._con.commit()
return results

View file

@ -17,6 +17,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
""" """
from typing import List from typing import List
from pyrogram import Client, errors, raw from pyrogram import Client, errors, raw
@ -32,7 +33,7 @@ async def get_sticker_set_by_name(
hash=0, hash=0,
) )
) )
except errors.exceptions.not_acceptable_406.StickersetInvalid: except errors.exceptions.bad_request_400.StickersetInvalid:
return None return None

View file

@ -1,20 +1,26 @@
import logging import logging
import os import os
import random import random
import re
import string import string
import time import time
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from re import match as re_match from re import match as re_match
from googletrans import Translator
from typing import Union
from urllib.parse import urlparse from urllib.parse import urlparse
import cv2
import numpy as np
import psutil import psutil
from misskaty import BOT_NAME, UBOT_NAME, botStartTime from misskaty import BOT_NAME, UBOT_NAME, botStartTime
from misskaty.helper.http import http from misskaty.core.decorator import asyncify
from misskaty.helper.http import fetch
from misskaty.helper.human_read import get_readable_time from misskaty.helper.human_read import get_readable_time
from misskaty.plugins import ALL_MODULES from misskaty.plugins import ALL_MODULES
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger("MissKaty")
URL_REGEX = r"(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])" URL_REGEX = r"(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])"
GENRES_EMOJI = { GENRES_EMOJI = {
"Action": "👊", "Action": "👊",
@ -42,6 +48,12 @@ GENRES_EMOJI = {
} }
async def gtranslate(text, source="auto", target="id"):
async with Translator() as translator:
result = await translator.translate(text, src=source, dest=target)
return result
def is_url(url): def is_url(url):
url = re_match(URL_REGEX, url) url = re_match(URL_REGEX, url)
return bool(url) return bool(url)
@ -87,7 +99,7 @@ def get_random_string(length: int = 5):
async def rentry(teks): async def rentry(teks):
# buat dapetin cookie # buat dapetin cookie
cookie = SimpleCookie() cookie = SimpleCookie()
kuki = (await http.get("https://rentry.co")).cookies kuki = (await fetch.get("https://rentry.co")).cookies
cookie.load(kuki) cookie.load(kuki)
kukidict = {key: value.value for key, value in cookie.items()} kukidict = {key: value.value for key, value in cookie.items()}
# headernya # headernya
@ -95,7 +107,7 @@ async def rentry(teks):
payload = {"csrfmiddlewaretoken": kukidict["csrftoken"], "text": teks} payload = {"csrfmiddlewaretoken": kukidict["csrftoken"], "text": teks}
return ( return (
( (
await http.post( await fetch.post(
"https://rentry.co/api/new", "https://rentry.co/api/new",
data=payload, data=payload,
headers=header, headers=header,
@ -124,11 +136,11 @@ def get_provider(url):
return pretty(netloc.split(".")) return pretty(netloc.split("."))
async def search_jw(movie_name: str, locale: str): async def search_jw(movie_name: str, locale: Union[str, None] = "ID"):
m_t_ = "" m_t_ = ""
try: try:
response = ( response = (
await http.get( await fetch.get(
f"https://yasirapi.eu.org/justwatch?q={movie_name}&locale={locale}" f"https://yasirapi.eu.org/justwatch?q={movie_name}&locale={locale}"
) )
).json() ).json()
@ -137,17 +149,73 @@ async def search_jw(movie_name: str, locale: str):
if not response.get("results"): if not response.get("results"):
LOGGER.error("JustWatch API Error or got Rate Limited.") LOGGER.error("JustWatch API Error or got Rate Limited.")
return m_t_ return m_t_
for item in response.get("results")["items"]: for item in response["results"]["data"]["popularTitles"]["edges"]:
if movie_name == item.get("title", ""): if item["node"]["content"]["title"] == movie_name:
offers = item.get("offers", [])
t_m_ = [] t_m_ = []
for offer in offers: for offer in item["node"].get("offers", []):
url = offer.get("urls").get("standard_web") url = offer["standardWebURL"]
if url not in t_m_: if url not in t_m_:
p_o = get_provider(url) p_o = get_provider(url)
m_t_ += f"<a href='{url}'>{p_o}</a> | " m_t_ += f"<a href='{url}'>{p_o}</a> | "
t_m_.append(url) t_m_.append(url)
if m_t_ != "": if m_t_ != "":
m_t_ = m_t_[:-2].strip() m_t_ = m_t_[:-2].strip()
break break
return m_t_ return m_t_
def isValidURL(str):
# Regex to check valid URL
regex = (
"((http|https)://)(www.)?"
+ "[a-zA-Z0-9@:%._\\+~#?&//=]"
+ "{2,256}\\.[a-z]"
+ "{2,6}\\b([-a-zA-Z0-9@:%"
+ "._\\+~#?&//=]*)"
)
# Compile the ReGex
p = re.compile(regex)
# If the string is empty
# return false
return False if str is None else bool((re.search(p, str)))
@asyncify
def gen_trans_image(source, path):
# load image
img = cv2.imread(source)
# convert to graky
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold input image as mask
mask = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY)[1]
# negate mask
mask = 255 - mask
# apply morphology to remove isolated extraneous noise
# use borderconstant of black since foreground touches the edges
kernel = np.ones((3, 3), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# anti-alias the mask -- blur then stretch
# blur alpha channel
mask = cv2.GaussianBlur(
mask, (0, 0), sigmaX=2, sigmaY=2, borderType=cv2.BORDER_DEFAULT
)
# linear stretch so that 127.5 goes to 0, but 255 stays 255
mask = (2 * (mask.astype(np.float32)) - 255.0).clip(0, 255).astype(np.uint8)
# put mask into alpha channel
result = img.copy()
result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA)
result[:, :, 3] = mask
# save resulting masked image
cv2.imwrite(path, result)
return path

View file

@ -1,9 +1,10 @@
""" """
* @author yasir <yasiramunandar@gmail.com> * @author yasir <yasiramunandar@gmail.com>
* @date 2022-12-01 09:12:27 * @date 2022-12-01 09:12:27
* @projectName MissKatyPyro * @projectName MissKatyPyro
* Copyright ©YasirPedia All rights reserved * Copyright ©YasirPedia All rights reserved
""" """
import glob import glob
import importlib import importlib
import sys import sys
@ -12,7 +13,7 @@ from os.path import basename, dirname, isfile
from misskaty import MOD_LOAD, MOD_NOLOAD from misskaty import MOD_LOAD, MOD_NOLOAD
LOGGER = getLogger(__name__) LOGGER = getLogger("MissKaty")
def __list_all_modules(): def __list_all_modules():

View file

@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
""" """
import asyncio import asyncio
import os import os
import re import re
@ -28,20 +29,22 @@ from logging import getLogger
from time import time from time import time
from pyrogram import Client, enums, filters from pyrogram import Client, enums, filters
from pyrogram.errors import ChatAdminRequired, FloodWait from pyrogram.errors import (
from pyrogram.types import ChatPermissions, ChatPrivileges, Message ChatAdminRequired,
FloodWait,
PeerIdInvalid,
UsernameNotOccupied,
)
from pyrogram.types import ChatMember, ChatPermissions, ChatPrivileges, Message
from database.warn_db import add_warn, get_warn, remove_warns from database.warn_db import add_warn, get_warn, remove_warns
from misskaty import app from misskaty import app
from misskaty.core.decorator.errors import capture_err from misskaty.core.decorator.errors import capture_err
from misskaty.core.decorator.permissions import ( from misskaty.core.decorator.permissions import (
admins_in_chat, admins_in_chat,
adminsOnly,
list_admins, list_admins,
member_permissions, member_permissions,
require_admin,
) )
from misskaty.core.decorator.ratelimiter import ratelimiter
from misskaty.core.keyboard import ikb from misskaty.core.keyboard import ikb
from misskaty.helper.functions import ( from misskaty.helper.functions import (
extract_user, extract_user,
@ -50,9 +53,9 @@ from misskaty.helper.functions import (
time_converter, time_converter,
) )
from misskaty.helper.localization import use_chat_lang from misskaty.helper.localization import use_chat_lang
from misskaty.vars import COMMAND_HANDLER, SUDO from misskaty.vars import COMMAND_HANDLER, SUDO, OWNER_ID
LOGGER = getLogger(__name__) LOGGER = getLogger("MissKaty")
__MODULE__ = "Admin" __MODULE__ = "Admin"
__HELP__ = """ __HELP__ = """
@ -83,11 +86,12 @@ __HELP__ = """
/set_chat_title - Change The Name Of A Group/Channel. /set_chat_title - Change The Name Of A Group/Channel.
/set_chat_photo - Change The PFP Of A Group/Channel. /set_chat_photo - Change The PFP Of A Group/Channel.
/set_user_title - Change The Administrator Title Of An Admin. /set_user_title - Change The Administrator Title Of An Admin.
/mentionall - Mention all members in a groups.
""" """
# Admin cache reload # Admin cache reload
@app.on_chat_member_updated() @app.on_chat_member_updated(filters.group, group=5)
async def admin_cache_func(_, cmu): async def admin_cache_func(_, cmu):
if cmu.old_chat_member and cmu.old_chat_member.promoted_by: if cmu.old_chat_member and cmu.old_chat_member.promoted_by:
try: try:
@ -107,12 +111,9 @@ async def admin_cache_func(_, cmu):
# Purge CMD # Purge CMD
@app.on_cmd("purge") @app.on_cmd("purge")
@require_admin(permissions=["can_delete_messages"], allow_in_private=True) @app.adminsOnly("can_delete_messages")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def purge(self: Client, ctx: Message, strings) -> "Message": async def purge(_, ctx: Message, strings):
if not ctx.from_user:
return
try: try:
repliedmsg = ctx.reply_to_message repliedmsg = ctx.reply_to_message
await ctx.delete_msg() await ctx.delete_msg()
@ -162,23 +163,23 @@ async def purge(self: Client, ctx: Message, strings) -> "Message":
# Kick members # Kick members
@app.on_cmd(["kick", "dkick"], group_only=True) @app.on_cmd(["kick", "dkick"], self_admin=True, group_only=True)
@adminsOnly("can_restrict_members") @app.adminsOnly("can_restrict_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def kickFunc(client: Client, ctx: Message, strings) -> "Message": async def kickFunc(client: Client, ctx: Message, strings) -> "Message":
if not ctx.from_user:
return
user_id, reason = await extract_user_and_reason(ctx) user_id, reason = await extract_user_and_reason(ctx)
if not user_id: if not user_id:
return await ctx.reply_msg(strings("user_not_found")) return await ctx.reply_msg(strings("user_not_found"))
if user_id == client.me.id: if user_id == client.me.id:
return await ctx.reply_msg(strings("kick_self_err")) return await ctx.reply_msg(strings("kick_self_err"))
if user_id in SUDO: if user_id in SUDO or user_id == OWNER_ID:
return await ctx.reply_msg(strings("kick_sudo_err")) return await ctx.reply_msg(strings("kick_sudo_err"))
if user_id in (await list_admins(ctx.chat.id)): if user_id in (await list_admins(ctx.chat.id)):
return await ctx.reply_msg(strings("kick_admin_err")) return await ctx.reply_msg(strings("kick_admin_err"))
user = await app.get_users(user_id) try:
user = await app.get_users(user_id)
except PeerIdInvalid:
return await ctx.reply_msg(strings("user_not_found"))
msg = strings("kick_msg").format( msg = strings("kick_msg").format(
mention=user.mention, mention=user.mention,
id=user.id, id=user.id,
@ -194,29 +195,33 @@ async def kickFunc(client: Client, ctx: Message, strings) -> "Message":
await ctx.chat.unban_member(user_id) await ctx.chat.unban_member(user_id)
except ChatAdminRequired: except ChatAdminRequired:
await ctx.reply_msg(strings("no_ban_permission")) await ctx.reply_msg(strings("no_ban_permission"))
except Exception as e:
await ctx.reply_msg(str(e))
# Ban/DBan/TBan User # Ban/DBan/TBan User
@app.on_cmd(["ban", "dban", "tban"], group_only=True) @app.on_cmd(["ban", "dban", "tban"], self_admin=True, group_only=True)
@adminsOnly("can_restrict_members") @app.adminsOnly("can_restrict_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def banFunc(client, message, strings): async def banFunc(client, message, strings):
if not message.from_user: try:
return user_id, reason = await extract_user_and_reason(message, sender_chat=True)
user_id, reason = await extract_user_and_reason(message, sender_chat=True) except UsernameNotOccupied:
return await message.reply_msg("Sorry, i didn't know that user.")
if not user_id: if not user_id:
return await message.reply_text(strings("user_not_found")) return await message.reply_text(strings("user_not_found"))
if user_id == client.me.id: if user_id == client.me.id:
return await message.reply_text(strings("ban_self_err")) return await message.reply_text(strings("ban_self_err"))
if user_id in SUDO: if user_id in SUDO or user_id == OWNER_ID:
return await message.reply_text(strings("ban_sudo_err")) return await message.reply_text(strings("ban_sudo_err"))
if user_id in (await list_admins(message.chat.id)): if user_id in (await list_admins(message.chat.id)):
return await message.reply_text(strings("ban_admin_err")) return await message.reply_text(strings("ban_admin_err"))
try: try:
mention = (await app.get_users(user_id)).mention mention = (await app.get_users(user_id)).mention
except PeerIdInvalid:
return await message.reply_text(strings("user_not_found"))
except IndexError: except IndexError:
mention = ( mention = (
message.reply_to_message.sender_chat.title message.reply_to_message.sender_chat.title
@ -253,19 +258,18 @@ async def banFunc(client, message, strings):
keyboard = ikb({"🚨 Unban 🚨": f"unban_{user_id}"}) keyboard = ikb({"🚨 Unban 🚨": f"unban_{user_id}"})
try: try:
await message.chat.ban_member(user_id) await message.chat.ban_member(user_id)
await message.reply_text(msg, reply_markup=keyboard) await message.reply_msg(msg, reply_markup=keyboard)
except Exception as err: except ChatAdminRequired:
await message.reply(f"ERROR: {err}") await message.reply("Please give me permission to banned members..!!!")
except Exception as e:
await message.reply_msg(str(e))
# Unban members # Unban members
@app.on_cmd("unban", group_only=True) @app.on_cmd("unban", self_admin=True, group_only=True)
@adminsOnly("can_restrict_members") @app.adminsOnly("can_restrict_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def unban_func(self, message, strings): async def unban_func(_, message, strings):
if not message.from_user:
return
# we don't need reasons for unban, also, we # we don't need reasons for unban, also, we
# don't need to get "text_mention" entity, because # don't need to get "text_mention" entity, because
# normal users won't get text_mention if the user # normal users won't get text_mention if the user
@ -277,26 +281,28 @@ async def unban_func(self, message, strings):
if len(message.command) == 2: if len(message.command) == 2:
user = message.text.split(None, 1)[1] user = message.text.split(None, 1)[1]
if not user.startswith("@"):
user = int(user)
elif len(message.command) == 1 and reply: elif len(message.command) == 1 and reply:
user = message.reply_to_message.from_user.id user = message.reply_to_message.from_user.id
else: else:
return await message.reply_text(strings("give_unban_user")) return await message.reply_msg(strings("give_unban_user"))
await message.chat.unban_member(user) try:
umention = (await app.get_users(user)).mention await message.chat.unban_member(user)
await message.reply_text(strings("unban_success").format(umention=umention)) umention = (await app.get_users(user)).mention
await message.reply_msg(strings("unban_success").format(umention=umention))
except PeerIdInvalid:
await message.reply_msg(strings("unknown_id", context="general"))
except ChatAdminRequired:
await message.reply("Please give me permission to unban members..!!!")
except Exception as e:
await message.reply_msg(str(e))
# Ban users listed in a message # Ban users listed in a message
@app.on_message( @app.on_message(
filters.user(SUDO) & filters.command("listban", COMMAND_HANDLER) & filters.group (filters.user(SUDO) | filters.user(OWNER_ID)) & filters.command("listban", COMMAND_HANDLER) & filters.group
) )
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def list_ban_(c, message, strings): async def list_ban_(c, message, strings):
if not message.from_user:
return
userid, msglink_reason = await extract_user_and_reason(message) userid, msglink_reason = await extract_user_and_reason(message)
if not userid or not msglink_reason: if not userid or not msglink_reason:
return await message.reply_text(strings("give_idban_with_msg_link")) return await message.reply_text(strings("give_idban_with_msg_link"))
@ -313,7 +319,7 @@ async def list_ban_(c, message, strings):
if userid == c.me.id: if userid == c.me.id:
return await message.reply_text(strings("ban_self_err")) return await message.reply_text(strings("ban_self_err"))
if userid in SUDO: if userid in SUDO or user_id == OWNER_ID:
return await message.reply_text(strings("ban_sudo_err")) return await message.reply_text(strings("ban_sudo_err"))
splitted = messagelink.split("/") splitted = messagelink.split("/")
uname, mid = splitted[-2], int(splitted[-1]) uname, mid = splitted[-2], int(splitted[-1])
@ -347,13 +353,10 @@ async def list_ban_(c, message, strings):
# Unban users listed in a message # Unban users listed in a message
@app.on_message( @app.on_message(
filters.user(SUDO) & filters.command("listunban", COMMAND_HANDLER) & filters.group (filters.user(SUDO) | filters.user(OWNER_ID)) & filters.command("listunban", COMMAND_HANDLER) & filters.group
) )
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def list_unban_(c, message, strings): async def list_unban(_, message, strings):
if not message.from_user:
return
userid, msglink = await extract_user_and_reason(message) userid, msglink = await extract_user_and_reason(message)
if not userid or not msglink: if not userid or not msglink:
return await message.reply_text(strings("give_idunban_with_msg_link")) return await message.reply_text(strings("give_idunban_with_msg_link"))
@ -388,12 +391,9 @@ async def list_unban_(c, message, strings):
# Delete messages # Delete messages
@app.on_cmd("del", group_only=True) @app.on_cmd("del", group_only=True)
@adminsOnly("can_delete_messages") @app.adminsOnly("can_delete_messages")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def deleteFunc(_, message, strings): async def deleteFunc(_, message, strings):
if not message.from_user:
return
if not message.reply_to_message: if not message.reply_to_message:
return await message.reply_text(strings("delete_no_reply")) return await message.reply_text(strings("delete_no_reply"))
try: try:
@ -404,99 +404,100 @@ async def deleteFunc(_, message, strings):
# Promote Members # Promote Members
@app.on_cmd(["promote", "fullpromote"], group_only=True) @app.on_cmd(["promote", "fullpromote"], self_admin=True, group_only=True)
@adminsOnly("can_promote_members") @app.adminsOnly("can_promote_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def promoteFunc(client, message, strings): async def promoteFunc(client, message, strings):
if not message.from_user:
return
try: try:
user_id = await extract_user(message) user_id = await extract_user(message)
umention = (await app.get_users(user_id)).mention umention = (await client.get_users(user_id)).mention
except: except:
return await message.reply(strings("invalid_id_uname")) return await message.reply(strings("invalid_id_uname"))
if not user_id: if not user_id:
return await message.reply_text(strings("user_not_found")) return await message.reply_text(strings("user_not_found"))
bot = await app.get_chat_member(message.chat.id, client.me.id) bot = (await client.get_chat_member(message.chat.id, client.me.id)).privileges
if user_id == client.me.id: if user_id == client.me.id:
return await message.reply_text(strings("promote_self_err")) return await message.reply_msg(strings("promote_self_err"))
if not bot.privileges.can_promote_members: if not bot:
return await message.reply_text(strings("no_promote_perm")) return await message.reply_msg("I'm not an admin in this chat.")
if message.command[0][0] == "f": if not bot.can_promote_members:
return await message.reply_msg(strings("no_promote_perm"))
try:
if message.command[0][0] == "f":
await message.chat.promote_member(
user_id=user_id,
privileges=ChatPrivileges(
can_change_info=bot.can_change_info,
can_invite_users=bot.can_invite_users,
can_delete_messages=bot.can_delete_messages,
can_restrict_members=bot.can_restrict_members,
can_pin_messages=bot.can_pin_messages,
can_promote_members=bot.can_promote_members,
can_manage_chat=bot.can_manage_chat,
can_manage_video_chats=bot.can_manage_video_chats,
),
)
return await message.reply_text(
strings("full_promote").format(umention=umention)
)
await message.chat.promote_member( await message.chat.promote_member(
user_id=user_id, user_id=user_id,
privileges=ChatPrivileges( privileges=ChatPrivileges(
can_change_info=bot.privileges.can_change_info, can_change_info=False,
can_invite_users=bot.privileges.can_invite_users, can_invite_users=bot.can_invite_users,
can_delete_messages=bot.privileges.can_delete_messages, can_delete_messages=bot.can_delete_messages,
can_restrict_members=bot.privileges.can_restrict_members, can_restrict_members=bot.can_restrict_members,
can_pin_messages=bot.privileges.can_pin_messages, can_pin_messages=bot.can_pin_messages,
can_promote_members=bot.privileges.can_promote_members, can_promote_members=False,
can_manage_chat=bot.privileges.can_manage_chat, can_manage_chat=bot.can_manage_chat,
can_manage_video_chats=bot.privileges.can_manage_video_chats, can_manage_video_chats=bot.can_manage_video_chats,
), ),
) )
return await message.reply_text( await message.reply_msg(strings("normal_promote").format(umention=umention))
strings("full_promote").format(umention=umention) except Exception as err:
) await message.reply_msg(err)
await message.chat.promote_member(
user_id=user_id,
privileges=ChatPrivileges(
can_change_info=False,
can_invite_users=bot.privileges.can_invite_users,
can_delete_messages=bot.privileges.can_delete_messages,
can_restrict_members=bot.privileges.can_restrict_members,
can_pin_messages=bot.privileges.can_pin_messages,
can_promote_members=False,
can_manage_chat=bot.privileges.can_manage_chat,
can_manage_video_chats=bot.privileges.can_manage_video_chats,
),
)
await message.reply_text(strings("normal_promote").format(umention=umention))
# Demote Member # Demote Member
@app.on_cmd("demote", group_only=True) @app.on_cmd("demote", self_admin=True, group_only=True)
@adminsOnly("can_restrict_members") @app.adminsOnly("can_restrict_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def demote(client, message, strings): async def demote(client, message, strings):
if not message.from_user:
return
user_id = await extract_user(message) user_id = await extract_user(message)
if not user_id: if not user_id:
return await message.reply_text(strings("user_not_found")) return await message.reply_text(strings("user_not_found"))
if user_id == client.me.id: if user_id == client.me.id:
return await message.reply_text(strings("demote_self_err")) return await message.reply_text(strings("demote_self_err"))
if user_id in SUDO: if user_id in SUDO or user_id == OWNER_ID:
return await message.reply_text(strings("demote_sudo_err")) return await message.reply_text(strings("demote_sudo_err"))
await message.chat.promote_member( try:
user_id=user_id, await message.chat.promote_member(
privileges=ChatPrivileges( user_id=user_id,
can_change_info=False, privileges=ChatPrivileges(
can_invite_users=False, can_change_info=False,
can_delete_messages=False, can_invite_users=False,
can_restrict_members=False, can_delete_messages=False,
can_pin_messages=False, can_restrict_members=False,
can_promote_members=False, can_pin_messages=False,
can_manage_chat=False, can_promote_members=False,
can_manage_video_chats=False, can_manage_chat=False,
), can_manage_video_chats=False,
) ),
umention = (await app.get_users(user_id)).mention )
await message.reply_text(f"Demoted! {umention}") umention = (await app.get_users(user_id)).mention
await message.reply_text(f"Demoted! {umention}")
except ChatAdminRequired:
await message.reply("Please give permission to demote members..")
except Exception as e:
await message.reply_msg(str(e))
# Pin Messages # Pin Messages
@app.on_cmd(["pin", "unpin"]) @app.on_cmd(["pin", "unpin"])
@adminsOnly("can_pin_messages") @app.adminsOnly("can_pin_messages")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def pin(_, message, strings): async def pin(_, message, strings):
if not message.from_user:
return
if not message.reply_to_message: if not message.reply_to_message:
return await message.reply_text(strings("pin_no_reply")) return await message.reply_text(strings("pin_no_reply"))
r = message.reply_to_message r = message.reply_to_message
@ -517,16 +518,15 @@ async def pin(_, message, strings):
strings("pin_no_perm"), strings("pin_no_perm"),
disable_web_page_preview=True, disable_web_page_preview=True,
) )
except Exception as e:
await message.reply_msg(str(e))
# Mute members # Mute members
@app.on_cmd(["mute", "tmute"], group_only=True) @app.on_cmd(["mute", "tmute"], self_admin=True, group_only=True)
@adminsOnly("can_restrict_members") @app.adminsOnly("can_restrict_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def mute(client, message, strings): async def mute(client, message, strings):
if not message.from_user:
return
try: try:
user_id, reason = await extract_user_and_reason(message) user_id, reason = await extract_user_and_reason(message)
except Exception as err: except Exception as err:
@ -535,7 +535,7 @@ async def mute(client, message, strings):
return await message.reply_text(strings("user_not_found")) return await message.reply_text(strings("user_not_found"))
if user_id == client.me.id: if user_id == client.me.id:
return await message.reply_text(strings("mute_self_err")) return await message.reply_text(strings("mute_self_err"))
if user_id in SUDO: if user_id in SUDO or user_id == OWNER_ID:
return await message.reply_text(strings("mute_sudo_err")) return await message.reply_text(strings("mute_sudo_err"))
if user_id in (await list_admins(message.chat.id)): if user_id in (await list_admins(message.chat.id)):
return await message.reply_text(strings("mute_admin_err")) return await message.reply_text(strings("mute_admin_err"))
@ -557,7 +557,7 @@ async def mute(client, message, strings):
if len(time_value[:-1]) < 3: if len(time_value[:-1]) < 3:
await message.chat.restrict_member( await message.chat.restrict_member(
user_id, user_id,
permissions=ChatPermissions(), permissions=ChatPermissions(all_perms=False),
until_date=temp_mute, until_date=temp_mute,
) )
await message.reply_text(msg, reply_markup=keyboard) await message.reply_text(msg, reply_markup=keyboard)
@ -568,40 +568,45 @@ async def mute(client, message, strings):
return return
if reason: if reason:
msg += strings("banned_reason").format(reas=reason) msg += strings("banned_reason").format(reas=reason)
await message.chat.restrict_member(user_id, permissions=ChatPermissions()) try:
await message.reply_text(msg, reply_markup=keyboard) await message.chat.restrict_member(
user_id, permissions=ChatPermissions(all_perms=False)
)
await message.reply_text(msg, reply_markup=keyboard)
except Exception as e:
await message.reply_msg(str(e))
# Unmute members # Unmute members
@app.on_cmd("unmute", group_only=True) @app.on_cmd("unmute", self_admin=True, group_only=True)
@adminsOnly("can_restrict_members") @app.adminsOnly("can_restrict_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def unmute(_, message, strings): async def unmute(_, message, strings):
if not message.from_user:
return
user_id = await extract_user(message) user_id = await extract_user(message)
if not user_id: if not user_id:
return await message.reply_text(strings("user_not_found")) return await message.reply_text(strings("user_not_found"))
await message.chat.unban_member(user_id) try:
umention = (await app.get_users(user_id)).mention await message.chat.unban_member(user_id)
await message.reply_text(strings("unmute_msg").format(umention=umention)) umention = (await app.get_users(user_id)).mention
await message.reply_msg(strings("unmute_msg").format(umention=umention))
except Exception as e:
await message.reply_msg(str(e))
@app.on_cmd(["warn", "dwarn"], group_only=True) @app.on_cmd(["warn", "dwarn"], self_admin=True, group_only=True)
@adminsOnly("can_restrict_members") @app.adminsOnly("can_restrict_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def warn_user(client, message, strings): async def warn_user(client, message, strings):
if not message.from_user: try:
return user_id, reason = await extract_user_and_reason(message)
user_id, reason = await extract_user_and_reason(message) except UsernameNotOccupied:
return await message.reply_msg("Sorry, i didn't know that user.")
chat_id = message.chat.id chat_id = message.chat.id
if not user_id: if not user_id:
return await message.reply_text(strings("user_not_found")) return await message.reply_text(strings("user_not_found"))
if user_id == client.me.id: if user_id == client.me.id:
return await message.reply_text(strings("warn_self_err")) return await message.reply_text(strings("warn_self_err"))
if user_id in SUDO: if user_id in SUDO or user_id == OWNER_ID:
return await message.reply_text(strings("warn_sudo_err")) return await message.reply_text(strings("warn_sudo_err"))
if user_id in (await list_admins(chat_id)): if user_id in (await list_admins(chat_id)):
return await message.reply_text(strings("warn_admin_err")) return await message.reply_text(strings("warn_admin_err"))
@ -615,9 +620,12 @@ async def warn_user(client, message, strings):
if message.command[0][0] == "d": if message.command[0][0] == "d":
await message.reply_to_message.delete() await message.reply_to_message.delete()
if warns >= 2: if warns >= 2:
await message.chat.ban_member(user_id) try:
await message.reply_text(strings("exceed_warn_msg").format(mention=mention)) await message.chat.ban_member(user_id)
await remove_warns(chat_id, await int_to_alpha(user_id)) await message.reply_msg(strings("exceed_warn_msg").format(mention=mention))
await remove_warns(chat_id, await int_to_alpha(user_id))
except ChatAdminRequired:
await message.reply_msg(strings("no_ban_permission"))
else: else:
warn = {"warns": warns + 1} warn = {"warns": warns + 1}
msg = strings("warn_msg").format( msg = strings("warn_msg").format(
@ -626,12 +634,11 @@ async def warn_user(client, message, strings):
reas=reason or "No Reason Provided.", reas=reason or "No Reason Provided.",
twarn=warns + 1, twarn=warns + 1,
) )
await message.reply_text(msg, reply_markup=keyboard) await message.reply_msg(msg, reply_markup=keyboard)
await add_warn(chat_id, await int_to_alpha(user_id), warn) await add_warn(chat_id, await int_to_alpha(user_id), warn)
@app.on_callback_query(filters.regex("unwarn_")) @app.on_callback_query(filters.regex("unwarn_"))
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def remove_warning(_, cq, strings): async def remove_warning(_, cq, strings):
from_user = cq.from_user from_user = cq.from_user
@ -662,7 +669,6 @@ async def remove_warning(_, cq, strings):
@app.on_callback_query(filters.regex("unmute_")) @app.on_callback_query(filters.regex("unmute_"))
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def unmute_user(_, cq, strings): async def unmute_user(_, cq, strings):
from_user = cq.from_user from_user = cq.from_user
@ -678,12 +684,14 @@ async def unmute_user(_, cq, strings):
text = cq.message.text.markdown text = cq.message.text.markdown
text = f"~~{text}~~\n\n" text = f"~~{text}~~\n\n"
text += strings("rmmute_msg").format(mention=from_user.mention) text += strings("rmmute_msg").format(mention=from_user.mention)
await cq.message.chat.unban_member(user_id) try:
await cq.message.edit(text) await cq.message.chat.unban_member(user_id)
await cq.message.edit(text)
except Exception as e:
await cq.answer(str(e))
@app.on_callback_query(filters.regex("unban_")) @app.on_callback_query(filters.regex("unban_"))
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def unban_user(_, cq, strings): async def unban_user(_, cq, strings):
from_user = cq.from_user from_user = cq.from_user
@ -707,13 +715,10 @@ async def unban_user(_, cq, strings):
# Remove Warn # Remove Warn
@app.on_cmd("rmwarn", group_only=True) @app.on_cmd("rmwarn", self_admin=True, group_only=True)
@adminsOnly("can_restrict_members") @app.adminsOnly("can_restrict_members")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def remove_warnings(_, message, strings): async def remove_warnings(_, message, strings):
if not message.from_user:
return
if not message.reply_to_message: if not message.reply_to_message:
return await message.reply_text(strings("reply_to_rm_warn")) return await message.reply_text(strings("reply_to_rm_warn"))
user_id = message.reply_to_message.from_user.id user_id = message.reply_to_message.from_user.id
@ -731,7 +736,6 @@ async def remove_warnings(_, message, strings):
# Warns # Warns
@app.on_cmd("warns", group_only=True) @app.on_cmd("warns", group_only=True)
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def check_warns(_, message, strings): async def check_warns(_, message, strings):
if not message.from_user: if not message.from_user:
@ -759,28 +763,27 @@ async def check_warns(_, message, strings):
& filters.group & filters.group
) )
@capture_err @capture_err
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def report_user(_, ctx: Message, strings) -> "Message": async def report_user(_, ctx: Message, strings) -> "Message":
if not ctx.reply_to_message: if len(ctx.text.split()) <= 1 and not ctx.reply_to_message:
return await ctx.reply_text(strings("report_no_reply")) return await ctx.reply_msg(strings("report_no_reply"))
reply = ctx.reply_to_message reply = ctx.reply_to_message if ctx.reply_to_message else ctx
reply_id = reply.from_user.id if reply.from_user else reply.sender_chat.id reply_id = reply.from_user.id if reply.from_user else reply.sender_chat.id
user_id = ctx.from_user.id if ctx.from_user else ctx.sender_chat.id user_id = ctx.from_user.id if ctx.from_user else ctx.sender_chat.id
if reply_id == user_id: if reply_id == user_id:
return await ctx.reply_text(strings("report_self_err")) return await ctx.reply_msg(strings("report_self_err"))
list_of_admins = await list_admins(ctx.chat.id) list_of_admins = await list_admins(ctx.chat.id)
linked_chat = (await app.get_chat(ctx.chat.id)).linked_chat linked_chat = (await app.get_chat(ctx.chat.id)).linked_chat
if linked_chat is None: if linked_chat is None:
if reply_id in list_of_admins or reply_id == ctx.chat.id: if reply_id in list_of_admins or reply_id == ctx.chat.id:
return await ctx.reply_text(strings("reported_is_admin")) return await ctx.reply_msg(strings("reported_is_admin"))
elif ( elif (
reply_id in list_of_admins reply_id in list_of_admins
or reply_id == ctx.chat.id or reply_id == ctx.chat.id
or reply_id == linked_chat.id or reply_id == linked_chat.id
): ):
return await ctx.reply_text(strings("reported_is_admin")) return await ctx.reply_msg(strings("reported_is_admin"))
user_mention = ( user_mention = (
reply.from_user.mention if reply.from_user else reply.sender_chat.title reply.from_user.mention if reply.from_user else reply.sender_chat.title
) )
@ -796,24 +799,27 @@ async def report_user(_, ctx: Message, strings) -> "Message":
# return bots or deleted admins # return bots or deleted admins
continue continue
text += f"<a href='tg://user?id={admin.user.id}'>\u2063</a>" text += f"<a href='tg://user?id={admin.user.id}'>\u2063</a>"
await ctx.reply_msg(text, reply_to_message_id=ctx.reply_to_message.id) await reply.reply_msg(text)
@app.on_cmd("set_chat_title", group_only=True) @app.on_cmd("set_chat_title", self_admin=True, group_only=True)
@adminsOnly("can_change_info") @app.adminsOnly("can_change_info")
async def set_chat_title(_, ctx: Message): async def set_chat_title(_, ctx: Message):
if len(ctx.command) < 2: if len(ctx.command) < 2:
return await ctx.reply_text("**Usage:**\n/set_chat_title NEW NAME") return await ctx.reply_text(f"**Usage:**\n/{ctx.command[0]} NEW NAME")
old_title = ctx.chat.title old_title = ctx.chat.title
new_title = ctx.text.split(None, 1)[1] new_title = ctx.text.split(None, 1)[1]
await ctx.chat.set_title(new_title) try:
await ctx.reply_text( await ctx.chat.set_title(new_title)
f"Successfully Changed Group Title From {old_title} To {new_title}" await ctx.reply_text(
) f"Successfully Changed Group Title From {old_title} To {new_title}"
)
except Exception as e:
await ctx.reply_msg(str(e))
@app.on_cmd("set_user_title", group_only=True) @app.on_cmd("set_user_title", self_admin=True, group_only=True)
@adminsOnly("can_change_info") @app.adminsOnly("can_change_info")
async def set_user_title(_, ctx: Message): async def set_user_title(_, ctx: Message):
if not ctx.reply_to_message: if not ctx.reply_to_message:
return await ctx.reply_text("Reply to user's message to set his admin title") return await ctx.reply_text("Reply to user's message to set his admin title")
@ -826,14 +832,17 @@ async def set_user_title(_, ctx: Message):
"**Usage:**\n/set_user_title NEW ADMINISTRATOR TITLE" "**Usage:**\n/set_user_title NEW ADMINISTRATOR TITLE"
) )
title = ctx.text.split(None, 1)[1] title = ctx.text.split(None, 1)[1]
await app.set_administrator_title(chat_id, from_user.id, title) try:
await ctx.reply_text( await app.set_administrator_title(chat_id, from_user.id, title)
f"Successfully Changed {from_user.mention}'s Admin Title To {title}" await ctx.reply_text(
) f"Successfully Changed {from_user.mention}'s Admin Title To {title}"
)
except Exception as e:
await ctx.reply_msg(str(e))
@app.on_cmd("set_chat_photo", group_only=True) @app.on_cmd("set_chat_photo", self_admin=True, group_only=True)
@adminsOnly("can_change_info") @app.adminsOnly("can_change_info")
async def set_chat_photo(_, ctx: Message): async def set_chat_photo(_, ctx: Message):
reply = ctx.reply_to_message reply = ctx.reply_to_message
@ -856,3 +865,30 @@ async def set_chat_photo(_, ctx: Message):
except Exception as err: except Exception as err:
await ctx.reply(f"Failed changed group photo. ERROR: {err}") await ctx.reply(f"Failed changed group photo. ERROR: {err}")
os.remove(photo) os.remove(photo)
@app.on_message(filters.group & filters.command("mentionall", COMMAND_HANDLER))
async def mentionall(app: Client, msg: Message):
user = await msg.chat.get_member(msg.from_user.id)
if user.status in (
enums.ChatMemberStatus.OWNER,
enums.ChatMemberStatus.ADMINISTRATOR,
):
total = []
async for member in app.get_chat_members(msg.chat.id):
member: ChatMember
if member.user.username:
total.append(f"@{member.user.username}")
else:
total.append(member.user.mention())
NUM = 4
for i in range(0, len(total), NUM):
message = " ".join(total[i : i + NUM])
await app.send_message(
msg.chat.id, message, message_thread_id=msg.message_thread_id
)
else:
await app.send_message(
msg.chat.id, "Admins only can do that !", reply_to_message_id=msg.id
)

View file

@ -19,7 +19,6 @@ from pyrogram.types import Message
from database.afk_db import add_afk, cleanmode_off, cleanmode_on, is_afk, remove_afk from database.afk_db import add_afk, cleanmode_off, cleanmode_on, is_afk, remove_afk
from misskaty import app from misskaty import app
from misskaty.core.decorator.permissions import adminsOnly from misskaty.core.decorator.permissions import adminsOnly
from misskaty.core.decorator.ratelimiter import ratelimiter
from misskaty.helper import get_readable_time2 from misskaty.helper import get_readable_time2
from misskaty.helper.localization import use_chat_lang from misskaty.helper.localization import use_chat_lang
from utils import put_cleanmode from utils import put_cleanmode
@ -33,7 +32,6 @@ Just type something in group to remove AFK Status."""
# Handle set AFK Command # Handle set AFK Command
@app.on_cmd("afk") @app.on_cmd("afk")
@ratelimiter
@use_chat_lang() @use_chat_lang()
async def active_afk(_, ctx: Message, strings): async def active_afk(_, ctx: Message, strings):
if ctx.sender_chat: if ctx.sender_chat:
@ -207,7 +205,6 @@ async def active_afk(_, ctx: Message, strings):
@app.on_cmd("afkdel", group_only=True) @app.on_cmd("afkdel", group_only=True)
@ratelimiter
@adminsOnly("can_change_info") @adminsOnly("can_change_info")
@use_chat_lang() @use_chat_lang()
async def afk_state(_, ctx: Message, strings): async def afk_state(_, ctx: Message, strings):
@ -245,10 +242,13 @@ async def afk_watcher_func(self: Client, ctx: Message, strings):
possible = ["/afk", f"/afk@{self.me.username}", "!afk"] possible = ["/afk", f"/afk@{self.me.username}", "!afk"]
message_text = ctx.text or ctx.caption message_text = ctx.text or ctx.caption
for entity in ctx.entities: for entity in ctx.entities:
if ( try:
entity.type == enums.MessageEntityType.BOT_COMMAND if (
and (message_text[0 : 0 + entity.length]).lower() in possible entity.type == enums.MessageEntityType.BOT_COMMAND
): and (message_text[0 : 0 + entity.length]).lower() in possible
):
return
except UnicodeDecodeError: # Some weird character make error
return return
msg = "" msg = ""
@ -380,7 +380,7 @@ async def afk_watcher_func(self: Client, ctx: Message, strings):
if ctx.entities: if ctx.entities:
entity = ctx.entities entity = ctx.entities
j = 0 j = 0
for x in range(len(entity)): for _ in range(len(entity)):
if (entity[j].type) == enums.MessageEntityType.MENTION: if (entity[j].type) == enums.MessageEntityType.MENTION:
found = re.findall("@([_0-9a-zA-Z]+)", ctx.text) found = re.findall("@([_0-9a-zA-Z]+)", ctx.text)
try: try:

View file

@ -167,7 +167,7 @@ def shorten(description, info="anilist.co"):
@app.on_message(filters.command("anime", COMMAND_HANDLER)) @app.on_message(filters.command("anime", COMMAND_HANDLER))
async def anime_search(_, mesg): async def anime_search(_, mesg):
search = mesg.text.split(" ", 1) search = mesg.text.split(None, 1)
reply = await mesg.reply("⏳ <i>Please wait ...</i>", quote=True) reply = await mesg.reply("⏳ <i>Please wait ...</i>", quote=True)
if len(search) == 1: if len(search) == 1:
return await reply.edit("⚠️ <b>Give Anime name please.</b>") return await reply.edit("⚠️ <b>Give Anime name please.</b>")

View file

@ -1,16 +1,16 @@
""" """
* @author yasir <yasiramunandar@gmail.com> * @author yasir <yasiramunandar@gmail.com>
* @date 2022-12-01 09:12:27 * @date 2022-12-01 09:12:27
* @projectName MissKatyPyro * @projectName MissKatyPyro
* Copyright @YasirPedia All rights reserved * Copyright @YasirPedia All rights reserved
""" """
from pyrogram import filters from pyrogram import filters
from pyrogram.errors import UserAlreadyParticipant, UserIsBlocked from pyrogram.errors import UserAlreadyParticipant, UserIsBlocked
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from misskaty import app from misskaty import app
from misskaty.core.decorator.errors import capture_err from misskaty.core.decorator.errors import capture_err
from misskaty.core.decorator.ratelimiter import ratelimiter
# Filters Approve User by bot in channel @YMovieZNew # Filters Approve User by bot in channel @YMovieZNew
@ -41,9 +41,8 @@ async def approve_join_chat(c, m):
@app.on_callback_query(filters.regex(r"^approve")) @app.on_callback_query(filters.regex(r"^approve"))
@ratelimiter
async def approve_chat(c, q): async def approve_chat(c, q):
i, chat = q.data.split("_") _, chat = q.data.split("_")
try: try:
await q.message.edit( await q.message.edit(
"Yeayy, selamat kamu bisa bergabung di Channel YMovieZ Reborn..." "Yeayy, selamat kamu bisa bergabung di Channel YMovieZ Reborn..."
@ -58,9 +57,8 @@ async def approve_chat(c, q):
@app.on_callback_query(filters.regex(r"^declined")) @app.on_callback_query(filters.regex(r"^declined"))
@ratelimiter
async def decline_chat(c, q): async def decline_chat(c, q):
i, chat = q.data.split("_") _, chat = q.data.split("_")
try: try:
await q.message.edit( await q.message.edit(
"Yahh, kamu ditolak join channel. Biasakan rajin membaca yahhh.." "Yahh, kamu ditolak join channel. Biasakan rajin membaca yahhh.."

Some files were not shown because too many files have changed in this diff Show more